[Issue:1052] Create job for fetching contributor stats and storing in db (#1144)
* [Issue:1052] Create job for fetching contributor stats and storing in db revert unnecessary changes on api/handlers/stats revert changes in go.mod and go.sum revert change in go.work add schedule for contributors stats job change response parsing order changes due to draft-pr review move on with contributors activity implementation change to every hour fix typo change contributor stats implementation to do a single write transaction normalize to UTC contributors activity timestamp add cronjob schedule for contributors [Issue:1052][Part 2] Create endpoint to expose contributors stats and activities (#1123) * add endpoint for retrieving stats and activity * remove model.go file and move types to service file * add unit tests to contributors service * integrate new contributors controller * fix more stuff fix unit-tests changes due to pr review fix query fix unit-tests fix total_value_secure move constantes to common pkg remove extra changes rename contributor to protocols finish renames Changes for deployment adjust different response types from different protocols contributors fix controller test big refactor in activty job and stats job since protocols are returning different formats api responding fine remove uneccessary generics target dbconsts fix Delete deploy/common/env/staging-mainnet.env undo unwanted changes readd staging-mainnet.env fix unit-tests add missing protocols_stats/activity_version remove property protocols_json fix JOB_ID env var in protocols-activity.yaml fix typos in env vars configs change tu numbers changes due to own review add new line * add swagger docs
This commit is contained in:
parent
a834f3d44e
commit
175abbb11a
|
@ -979,6 +979,35 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/protocols/stats": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns the representative stats for the top protocols",
|
||||||
|
"tags": [
|
||||||
|
"wormholescan"
|
||||||
|
],
|
||||||
|
"operationId": "get-top-protocols-stats",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protocols.ProtocolTotalValuesDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protocols.ProtocolTotalValuesDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/ready": {
|
"/api/v1/ready": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Ready check",
|
"description": "Ready check",
|
||||||
|
@ -2760,6 +2789,35 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"protocols.ProtocolTotalValuesDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_day_diff_percentage": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_day_messages": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"protocol": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"total_messages": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_value_locked": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"total_value_secured": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"total_value_transferred": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"relays.DeliveryReponse": {
|
"relays.DeliveryReponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -972,6 +972,35 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/protocols/stats": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns the representative stats for the top protocols",
|
||||||
|
"tags": [
|
||||||
|
"wormholescan"
|
||||||
|
],
|
||||||
|
"operationId": "get-top-protocols-stats",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protocols.ProtocolTotalValuesDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protocols.ProtocolTotalValuesDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/ready": {
|
"/api/v1/ready": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Ready check",
|
"description": "Ready check",
|
||||||
|
@ -2753,6 +2782,35 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"protocols.ProtocolTotalValuesDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_day_diff_percentage": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_day_messages": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"protocol": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"total_messages": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_value_locked": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"total_value_secured": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"total_value_transferred": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"relays.DeliveryReponse": {
|
"relays.DeliveryReponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -522,6 +522,25 @@ definitions:
|
||||||
tokenChain:
|
tokenChain:
|
||||||
$ref: '#/definitions/vaa.ChainID'
|
$ref: '#/definitions/vaa.ChainID'
|
||||||
type: object
|
type: object
|
||||||
|
protocols.ProtocolTotalValuesDTO:
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
last_day_diff_percentage:
|
||||||
|
type: string
|
||||||
|
last_day_messages:
|
||||||
|
type: integer
|
||||||
|
protocol:
|
||||||
|
type: string
|
||||||
|
total_messages:
|
||||||
|
type: integer
|
||||||
|
total_value_locked:
|
||||||
|
type: number
|
||||||
|
total_value_secured:
|
||||||
|
type: number
|
||||||
|
total_value_transferred:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
relays.DeliveryReponse:
|
relays.DeliveryReponse:
|
||||||
properties:
|
properties:
|
||||||
budget:
|
budget:
|
||||||
|
@ -1774,6 +1793,25 @@ paths:
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
tags:
|
tags:
|
||||||
- wormholescan
|
- wormholescan
|
||||||
|
/api/v1/protocols/stats:
|
||||||
|
get:
|
||||||
|
description: Returns the representative stats for the top protocols
|
||||||
|
operationId: get-top-protocols-stats
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/protocols.ProtocolTotalValuesDTO'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/protocols.ProtocolTotalValuesDTO'
|
||||||
|
type: array
|
||||||
|
tags:
|
||||||
|
- wormholescan
|
||||||
/api/v1/ready:
|
/api/v1/ready:
|
||||||
get:
|
get:
|
||||||
description: Ready check
|
description: Ready check
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
const QueryTemplateLatestPoint = `
|
||||||
|
from(bucket: "%s")
|
||||||
|
|> range(start: -1d)
|
||||||
|
|> filter(fn: (r) => r._measurement == "%s" and r.protocol == "%s" and r.version == "%s")
|
||||||
|
|> last()
|
||||||
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||||
|
`
|
||||||
|
|
||||||
|
const QueryTemplateLast24Point = `
|
||||||
|
from(bucket: "%s")
|
||||||
|
|> range(start: -1d)
|
||||||
|
|> filter(fn: (r) => r._measurement == "%s" and r.protocol == "%s" and r.version == "%s")
|
||||||
|
|> first()
|
||||||
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||||
|
`
|
||||||
|
|
||||||
|
const QueryTemplateActivityLatestPoint = `
|
||||||
|
from(bucket: "%s")
|
||||||
|
|> range(start: -1d)
|
||||||
|
|> filter(fn: (r) => r._measurement == "%s" and r.protocol == "%s" and r.version == "%s")
|
||||||
|
|> keep(columns: ["_time","_field","protocol", "_value", "total_value_secure", "total_value_transferred"])
|
||||||
|
|> last()
|
||||||
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||||
|
`
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
queryAPI QueryDoer
|
||||||
|
logger *zap.Logger
|
||||||
|
statsBucket string
|
||||||
|
activityBucket string
|
||||||
|
statsVersion string
|
||||||
|
activityVersion string
|
||||||
|
}
|
||||||
|
|
||||||
|
type rowStat struct {
|
||||||
|
Protocol string `mapstructure:"protocol"`
|
||||||
|
TotalMessages uint64 `mapstructure:"total_messages"`
|
||||||
|
TotalValueLocked float64 `mapstructure:"total_value_locked"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type rowActivity struct {
|
||||||
|
Protocol string `mapstructure:"protocol"`
|
||||||
|
DestinationChainId string `mapstructure:"destination_chain_id"`
|
||||||
|
EmitterChainId string `mapstructure:"emitter_chain_id"`
|
||||||
|
From string `mapstructure:"from"`
|
||||||
|
TotalUsd float64 `mapstructure:"total_usd"`
|
||||||
|
TotalValueTransferred float64 `mapstructure:"total_value_transferred"`
|
||||||
|
TotalVolumeSecure 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, statsBucket, activityBucket, statsVersion, activityVersion string, logger *zap.Logger) *Repository {
|
||||||
|
return &Repository{
|
||||||
|
queryAPI: qApi,
|
||||||
|
statsBucket: statsBucket,
|
||||||
|
activityBucket: activityBucket,
|
||||||
|
statsVersion: statsVersion,
|
||||||
|
activityVersion: activityVersion,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *queryApiWrapper) Query(ctx context.Context, query string) (QueryResult, error) {
|
||||||
|
return q.qApi.Query(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns latest and last 24 hr stats for a given protocol
|
||||||
|
func (r *Repository) getProtocolStats(ctx context.Context, contributor string) (stats, error) {
|
||||||
|
// fetch latest stat
|
||||||
|
latest, err := fetchSingleRecordData[rowStat](r.logger, r.queryAPI, ctx, r.statsBucket, QueryTemplateLatestPoint, dbconsts.ProtocolsStatsMeasurement, contributor, r.statsVersion)
|
||||||
|
if err != nil {
|
||||||
|
return stats{}, err
|
||||||
|
}
|
||||||
|
// fetch last 24 hr stat
|
||||||
|
last24hr, err := fetchSingleRecordData[rowStat](r.logger, r.queryAPI, ctx, r.statsBucket, QueryTemplateLast24Point, dbconsts.ProtocolsStatsMeasurement, contributor, r.statsVersion)
|
||||||
|
return stats{
|
||||||
|
Latest: latest,
|
||||||
|
Last24: last24hr,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) getProtocolActivity(ctx context.Context, contributor string) (rowActivity, error) {
|
||||||
|
return fetchSingleRecordData[rowActivity](r.logger, r.queryAPI, ctx, r.activityBucket, QueryTemplateActivityLatestPoint, dbconsts.ProtocolsActivityMeasurement, contributor, r.activityVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchSingleRecordData[T any](logger *zap.Logger, queryAPI QueryDoer, ctx context.Context, bucket, queryTemplate, measurement, contributor, version string) (T, error) {
|
||||||
|
var res T
|
||||||
|
q := buildQuery(queryTemplate, bucket, measurement, contributor, version)
|
||||||
|
result, err := queryAPI.Query(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error executing query to fetch data", zap.Error(err), zap.String("protocol", contributor), zap.String("query", q))
|
||||||
|
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", contributor), zap.String("query", q))
|
||||||
|
return res, result.Err()
|
||||||
|
}
|
||||||
|
logger.Info("empty query response", zap.String("protocol", contributor), zap.String("query", q))
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mapstructure.Decode(result.Record().Values(), &res)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildQuery(queryTemplate, bucket, measurement, contributorName, version string) string {
|
||||||
|
return fmt.Sprintf(queryTemplate, bucket, measurement, contributorName, version)
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package protocols
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Protocols []string
|
||||||
|
repo *Repository
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProtocolTotalValuesDTO struct {
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
TotalMessages uint64 `json:"total_messages"`
|
||||||
|
TotalValueLocked float64 `json:"total_value_locked,omitempty"`
|
||||||
|
TotalValueSecured float64 `json:"total_value_secured,omitempty"`
|
||||||
|
TotalValueTransferred float64 `json:"total_value_transferred,omitempty"`
|
||||||
|
LastDayMessages uint64 `json:"last_day_messages,omitempty"`
|
||||||
|
LastDayDiffPercentage string `json:"last_day_diff_percentage,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(protocols []string, repo *Repository, logger *zap.Logger) *Service {
|
||||||
|
return &Service{
|
||||||
|
Protocols: protocols,
|
||||||
|
repo: repo,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetProtocolsTotalValues(ctx context.Context) []ProtocolTotalValuesDTO {
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(s.Protocols))
|
||||||
|
results := make(chan ProtocolTotalValuesDTO, len(s.Protocols))
|
||||||
|
|
||||||
|
for i := range s.Protocols {
|
||||||
|
go s.getProtocolTotalValues(ctx, wg, s.Protocols[i], results)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(results)
|
||||||
|
|
||||||
|
resultsSlice := make([]ProtocolTotalValuesDTO, 0, len(s.Protocols))
|
||||||
|
for r := range results {
|
||||||
|
resultsSlice = append(resultsSlice, r)
|
||||||
|
}
|
||||||
|
return resultsSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) getProtocolTotalValues(ctx context.Context, wg *sync.WaitGroup, contributor string, results chan<- ProtocolTotalValuesDTO) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
type statsResult struct {
|
||||||
|
result stats
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
statsRes := make(chan statsResult, 1)
|
||||||
|
go func() {
|
||||||
|
rowStats, errStats := s.repo.getProtocolStats(ctx, contributor)
|
||||||
|
statsRes <- statsResult{result: rowStats, Err: errStats}
|
||||||
|
close(statsRes)
|
||||||
|
}()
|
||||||
|
|
||||||
|
activity, err := s.repo.getProtocolActivity(ctx, contributor)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("error fetching protocol activity", zap.Error(err), zap.String("protocol", contributor))
|
||||||
|
results <- ProtocolTotalValuesDTO{Protocol: contributor, Error: err.Error()}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rStats := <-statsRes
|
||||||
|
if rStats.Err != nil {
|
||||||
|
s.logger.Error("error fetching protocol stats", zap.Error(rStats.Err), zap.String("protocol", contributor))
|
||||||
|
results <- ProtocolTotalValuesDTO{Protocol: contributor, Error: rStats.Err.Error()}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := ProtocolTotalValuesDTO{
|
||||||
|
Protocol: contributor,
|
||||||
|
TotalValueLocked: rStats.result.Latest.TotalValueLocked,
|
||||||
|
TotalMessages: rStats.result.Latest.TotalMessages,
|
||||||
|
TotalValueTransferred: activity.TotalValueTransferred,
|
||||||
|
TotalValueSecured: activity.TotalVolumeSecure,
|
||||||
|
}
|
||||||
|
|
||||||
|
totalMsgNow := rStats.result.Latest.TotalMessages
|
||||||
|
totalMessagesAsFromLast24hr := rStats.result.Last24.TotalMessages
|
||||||
|
if totalMessagesAsFromLast24hr != 0 {
|
||||||
|
last24HrMessages := totalMsgNow - totalMessagesAsFromLast24hr
|
||||||
|
dto.LastDayMessages = last24HrMessages
|
||||||
|
dto.LastDayDiffPercentage = strconv.FormatFloat(float64(last24HrMessages)/float64(totalMessagesAsFromLast24hr)*100, 'f', 2, 64) + "%"
|
||||||
|
}
|
||||||
|
|
||||||
|
results <- dto
|
||||||
|
}
|
|
@ -0,0 +1,186 @@
|
||||||
|
package protocols_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/influxdata/influxdb-client-go/v2/api"
|
||||||
|
"github.com/influxdata/influxdb-client-go/v2/api/query"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/test-go/testify/mock"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/protocols"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/dbconsts"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestService_GetProtocolsTotalValues(t *testing.T) {
|
||||||
|
var errNil error
|
||||||
|
respStatsLatest := &mockQueryTableResult{}
|
||||||
|
respStatsLatest.On("Next").Return(true)
|
||||||
|
respStatsLatest.On("Err").Return(errNil)
|
||||||
|
respStatsLatest.On("Close").Return(errNil)
|
||||||
|
respStatsLatest.On("Record").Return(query.NewFluxRecord(1, map[string]interface{}{
|
||||||
|
"protocol": "protocol1",
|
||||||
|
"total_messages": uint64(7),
|
||||||
|
"total_value_locked": float64(5),
|
||||||
|
}))
|
||||||
|
|
||||||
|
respStatsLastDay := &mockQueryTableResult{}
|
||||||
|
respStatsLastDay.On("Next").Return(true)
|
||||||
|
respStatsLastDay.On("Err").Return(errNil)
|
||||||
|
respStatsLastDay.On("Close").Return(errNil)
|
||||||
|
respStatsLastDay.On("Record").Return(query.NewFluxRecord(1, map[string]interface{}{
|
||||||
|
"protocol": "protocol1",
|
||||||
|
"total_messages": uint64(4),
|
||||||
|
"total_value_locked": float64(5),
|
||||||
|
}))
|
||||||
|
|
||||||
|
respActivityLast := &mockQueryTableResult{}
|
||||||
|
respActivityLast.On("Next").Return(true)
|
||||||
|
respActivityLast.On("Err").Return(errNil)
|
||||||
|
respActivityLast.On("Close").Return(errNil)
|
||||||
|
respActivityLast.On("Record").Return(query.NewFluxRecord(1, map[string]interface{}{
|
||||||
|
"protocol": "protocol1",
|
||||||
|
"total_messages": uint64(4),
|
||||||
|
"total_value_transferred": float64(7),
|
||||||
|
"total_value_secure": float64(9),
|
||||||
|
}))
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
queryAPI := &mockQueryAPI{}
|
||||||
|
queryAPI.On("Query", ctx, fmt.Sprintf(protocols.QueryTemplateLatestPoint, "protocols_bucket", dbconsts.ProtocolsStatsMeasurement, "protocol1", "v1")).Return(respStatsLatest, nil)
|
||||||
|
queryAPI.On("Query", ctx, fmt.Sprintf(protocols.QueryTemplateLast24Point, "protocols_bucket", dbconsts.ProtocolsStatsMeasurement, "protocol1", "v1")).Return(respStatsLastDay, nil)
|
||||||
|
|
||||||
|
activityQuery := fmt.Sprintf(protocols.QueryTemplateActivityLatestPoint, "protocols_bucket", dbconsts.ProtocolsActivityMeasurement, "protocol1", "v1")
|
||||||
|
queryAPI.On("Query", ctx, activityQuery).Return(respActivityLast, nil)
|
||||||
|
|
||||||
|
repository := protocols.NewRepository(queryAPI, "protocols_bucket", "protocols_bucket", "v1", "v1", zap.NewNop())
|
||||||
|
service := protocols.NewService([]string{"protocol1"}, repository, zap.NewNop())
|
||||||
|
|
||||||
|
values := service.GetProtocolsTotalValues(ctx)
|
||||||
|
assert.Equal(t, 1, len(values))
|
||||||
|
assert.Equal(t, "protocol1", values[0].Protocol)
|
||||||
|
assert.Equal(t, 5.00, values[0].TotalValueLocked)
|
||||||
|
assert.Equal(t, uint64(7), values[0].TotalMessages)
|
||||||
|
assert.Equal(t, 9.00, values[0].TotalValueSecured)
|
||||||
|
assert.Equal(t, 7.00, values[0].TotalValueTransferred)
|
||||||
|
assert.Equal(t, uint64(3), values[0].LastDayMessages)
|
||||||
|
assert.Equal(t, "75.00%", values[0].LastDayDiffPercentage)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestService_GetProtocolsTotalValues_FailedFetchingActivity(t *testing.T) {
|
||||||
|
var errNil error
|
||||||
|
respStatsLatest := &mockQueryTableResult{}
|
||||||
|
respStatsLatest.On("Next").Return(true)
|
||||||
|
respStatsLatest.On("Err").Return(errNil)
|
||||||
|
respStatsLatest.On("Close").Return(errNil)
|
||||||
|
respStatsLatest.On("Record").Return(query.NewFluxRecord(1, map[string]interface{}{
|
||||||
|
"protocol": "protocol1",
|
||||||
|
"total_messages": uint64(7),
|
||||||
|
"total_value_locked": float64(5),
|
||||||
|
}))
|
||||||
|
|
||||||
|
respStatsLastDay := &mockQueryTableResult{}
|
||||||
|
respStatsLastDay.On("Next").Return(true)
|
||||||
|
respStatsLastDay.On("Err").Return(errNil)
|
||||||
|
respStatsLastDay.On("Close").Return(errNil)
|
||||||
|
respStatsLastDay.On("Record").Return(query.NewFluxRecord(1, map[string]interface{}{
|
||||||
|
"protocol": "protocol1",
|
||||||
|
"total_messages": uint64(4),
|
||||||
|
"total_value_locked": float64(5),
|
||||||
|
}))
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
queryAPI := &mockQueryAPI{}
|
||||||
|
queryAPI.On("Query", ctx, fmt.Sprintf(protocols.QueryTemplateLatestPoint, "protocols_bucket", dbconsts.ProtocolsStatsMeasurement, "protocol1", "v1")).Return(respStatsLatest, nil)
|
||||||
|
queryAPI.On("Query", ctx, fmt.Sprintf(protocols.QueryTemplateLast24Point, "protocols_bucket", dbconsts.ProtocolsStatsMeasurement, "protocol1", "v1")).Return(respStatsLastDay, nil)
|
||||||
|
|
||||||
|
activityQuery := fmt.Sprintf(protocols.QueryTemplateActivityLatestPoint, "protocols_bucket", dbconsts.ProtocolsActivityMeasurement, "protocol1", "v1")
|
||||||
|
queryAPI.On("Query", ctx, activityQuery).Return(&api.QueryTableResult{}, errors.New("mocked_fetching_activity_error"))
|
||||||
|
|
||||||
|
repository := protocols.NewRepository(queryAPI, "protocols_bucket", "protocols_bucket", "v1", "v1", zap.NewNop())
|
||||||
|
service := protocols.NewService([]string{"protocol1"}, repository, zap.NewNop())
|
||||||
|
|
||||||
|
values := service.GetProtocolsTotalValues(ctx)
|
||||||
|
assert.Equal(t, 1, len(values))
|
||||||
|
assert.Equal(t, "protocol1", values[0].Protocol)
|
||||||
|
assert.NotNil(t, values[0].Error)
|
||||||
|
assert.Equal(t, "mocked_fetching_activity_error", values[0].Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestService_GetProtocolsTotalValues_FailedFetchingStats(t *testing.T) {
|
||||||
|
var errNil error
|
||||||
|
|
||||||
|
respStatsLastDay := &mockQueryTableResult{}
|
||||||
|
respStatsLastDay.On("Next").Return(true)
|
||||||
|
respStatsLastDay.On("Err").Return(errNil)
|
||||||
|
respStatsLastDay.On("Close").Return(errNil)
|
||||||
|
respStatsLastDay.On("Record").Return(query.NewFluxRecord(1, map[string]interface{}{
|
||||||
|
"protocol": "protocol1",
|
||||||
|
"total_messages": uint64(4),
|
||||||
|
"total_value_locked": float64(5),
|
||||||
|
}))
|
||||||
|
|
||||||
|
respActivityLast := &mockQueryTableResult{}
|
||||||
|
respActivityLast.On("Next").Return(true)
|
||||||
|
respActivityLast.On("Err").Return(errNil)
|
||||||
|
respActivityLast.On("Close").Return(errNil)
|
||||||
|
respActivityLast.On("Record").Return(query.NewFluxRecord(1, map[string]interface{}{
|
||||||
|
"protocol": "protocol1",
|
||||||
|
"total_messages": uint64(4),
|
||||||
|
"total_value_transferred": float64(7),
|
||||||
|
"total_volume_secure": float64(9),
|
||||||
|
}))
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
queryAPI := &mockQueryAPI{}
|
||||||
|
queryAPI.On("Query", ctx, fmt.Sprintf(protocols.QueryTemplateLatestPoint, "protocols_bucket", dbconsts.ProtocolsStatsMeasurement, "protocol1", "v1")).Return(&api.QueryTableResult{}, errors.New("mocked_fetching_stats_error"))
|
||||||
|
queryAPI.On("Query", ctx, fmt.Sprintf(protocols.QueryTemplateLast24Point, "protocols_bucket", dbconsts.ProtocolsStatsMeasurement, "protocol1", "v1")).Return(respStatsLastDay, nil)
|
||||||
|
|
||||||
|
activityQuery := fmt.Sprintf(protocols.QueryTemplateActivityLatestPoint, "protocols_bucket", dbconsts.ProtocolsActivityMeasurement, "protocol1", "v1")
|
||||||
|
queryAPI.On("Query", ctx, activityQuery).Return(respActivityLast, errNil)
|
||||||
|
|
||||||
|
repository := protocols.NewRepository(queryAPI, "protocols_bucket", "protocols_bucket", "v1", "v1", zap.NewNop())
|
||||||
|
service := protocols.NewService([]string{"protocol1"}, repository, zap.NewNop())
|
||||||
|
|
||||||
|
values := service.GetProtocolsTotalValues(ctx)
|
||||||
|
assert.Equal(t, 1, len(values))
|
||||||
|
assert.Equal(t, "protocol1", values[0].Protocol)
|
||||||
|
assert.NotNil(t, values[0].Error)
|
||||||
|
assert.Equal(t, "mocked_fetching_stats_error", values[0].Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockQueryAPI struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockQueryAPI) Query(ctx context.Context, q string) (protocols.QueryResult, error) {
|
||||||
|
args := m.Called(ctx, q)
|
||||||
|
return args.Get(0).(protocols.QueryResult), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockQueryTableResult struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockQueryTableResult) Next() bool {
|
||||||
|
args := m.Called()
|
||||||
|
return args.Bool(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockQueryTableResult) Record() *query.FluxRecord {
|
||||||
|
args := m.Called()
|
||||||
|
return args.Get(0).(*query.FluxRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockQueryTableResult) Err() error {
|
||||||
|
args := m.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockQueryTableResult) Close() error {
|
||||||
|
args := m.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
|
@ -67,6 +67,9 @@ type AppConfig struct {
|
||||||
//Api Tokens
|
//Api Tokens
|
||||||
Tokens string
|
Tokens string
|
||||||
}
|
}
|
||||||
|
Protocols []string
|
||||||
|
ProtocolsStatsVersion string
|
||||||
|
ProtocolsActivityVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogLevel get zapcore.Level define in the configuraion.
|
// GetLogLevel get zapcore.Level define in the configuraion.
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/protocols"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -158,6 +159,7 @@ func main() {
|
||||||
relaysRepo := relays.NewRepository(db.Database, rootLogger)
|
relaysRepo := relays.NewRepository(db.Database, rootLogger)
|
||||||
operationsRepo := operations.NewRepository(db.Database, rootLogger)
|
operationsRepo := operations.NewRepository(db.Database, rootLogger)
|
||||||
statsRepo := stats.NewRepository(influxCli, cfg.Influx.Organization, cfg.Influx.Bucket24Hours, rootLogger)
|
statsRepo := stats.NewRepository(influxCli, cfg.Influx.Organization, cfg.Influx.Bucket24Hours, rootLogger)
|
||||||
|
protocolsRepo := protocols.NewRepository(protocols.WrapQueryAPI(influxCli.QueryAPI(cfg.Influx.Organization)), cfg.Influx.Bucket30Days, cfg.Influx.Bucket30Days, cfg.ProtocolsStatsVersion, cfg.ProtocolsActivityVersion, rootLogger)
|
||||||
|
|
||||||
// create token provider
|
// create token provider
|
||||||
tokenProvider := domain.NewTokenProvider(cfg.P2pNetwork)
|
tokenProvider := domain.NewTokenProvider(cfg.P2pNetwork)
|
||||||
|
@ -177,6 +179,7 @@ func main() {
|
||||||
relaysService := relays.NewService(relaysRepo, rootLogger)
|
relaysService := relays.NewService(relaysRepo, rootLogger)
|
||||||
operationsService := operations.NewService(operationsRepo, rootLogger)
|
operationsService := operations.NewService(operationsRepo, rootLogger)
|
||||||
statsService := stats.NewService(statsRepo, cache, expirationTime, metrics, rootLogger)
|
statsService := stats.NewService(statsRepo, cache, expirationTime, metrics, rootLogger)
|
||||||
|
protocolsService := protocols.NewService(cfg.Protocols, protocolsRepo, rootLogger)
|
||||||
|
|
||||||
// Set up a custom error handler
|
// Set up a custom error handler
|
||||||
response.SetEnableStackTrace(*cfg)
|
response.SetEnableStackTrace(*cfg)
|
||||||
|
@ -218,7 +221,7 @@ func main() {
|
||||||
|
|
||||||
// Set up route handlers
|
// Set up route handlers
|
||||||
app.Get("/swagger.json", GetSwagger)
|
app.Get("/swagger.json", GetSwagger)
|
||||||
wormscan.RegisterRoutes(app, rootLogger, addressService, vaaService, obsService, governorService, infrastructureService, transactionsService, relaysService, operationsService, statsService)
|
wormscan.RegisterRoutes(app, rootLogger, addressService, vaaService, obsService, governorService, infrastructureService, transactionsService, relaysService, operationsService, statsService, protocolsService)
|
||||||
guardian.RegisterRoutes(cfg, app, rootLogger, vaaService, governorService, heartbeatsService)
|
guardian.RegisterRoutes(cfg, app, rootLogger, vaaService, governorService, heartbeatsService)
|
||||||
|
|
||||||
// Set up gRPC handlers
|
// Set up gRPC handlers
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package protocols
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/protocols"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
srv service
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type service interface {
|
||||||
|
GetProtocolsTotalValues(ctx context.Context) []protocols.ProtocolTotalValuesDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(logger *zap.Logger, service service) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
logger: logger.With(zap.String("module", "ContributorsController")),
|
||||||
|
srv: service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProtocolsTotalValues godoc
|
||||||
|
// @Description Returns the representative stats for the top protocols
|
||||||
|
// @Tags wormholescan
|
||||||
|
// @ID get-top-protocols-stats
|
||||||
|
// @Success 200 {object} []protocols.ProtocolTotalValuesDTO
|
||||||
|
// @Failure 500 {object} []protocols.ProtocolTotalValuesDTO
|
||||||
|
// @Router /api/v1/protocols/stats [get]
|
||||||
|
func (c *Controller) GetProtocolsTotalValues(ctx *fiber.Ctx) error {
|
||||||
|
values := c.srv.GetProtocolsTotalValues(ctx.Context())
|
||||||
|
allFailed := true
|
||||||
|
for i := range values {
|
||||||
|
allFailed = allFailed && len(values[i].Error) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ctx.JSON(values)
|
||||||
|
if allFailed && len(values) > 0 {
|
||||||
|
return ctx.SendStatus(fiber.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package protocols_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
contributorsHandlerPkg "github.com/wormhole-foundation/wormhole-explorer/api/handlers/protocols"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/protocols"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetContributorsTotalValues(t *testing.T) {
|
||||||
|
|
||||||
|
app := fiber.New()
|
||||||
|
defer app.Shutdown()
|
||||||
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
|
||||||
|
input := []struct {
|
||||||
|
testName string
|
||||||
|
mockError string
|
||||||
|
expectedStatusCode int
|
||||||
|
expectedResponseBody string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "succeed scenario",
|
||||||
|
mockError: "",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedResponseBody: "[{\"protocol\":\"protocol1\",\"total_messages\":0}]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "fail scenario",
|
||||||
|
mockError: errors.New("mock_error").Error(),
|
||||||
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
expectedResponseBody: "[{\"protocol\":\"protocol1\",\"total_messages\":0,\"error\":\"mock_error\"}]",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, inputArgs := range input {
|
||||||
|
t.Run(inputArgs.testName, func(t *testing.T) {
|
||||||
|
|
||||||
|
service := mockService(func(ctx context.Context) []contributorsHandlerPkg.ProtocolTotalValuesDTO {
|
||||||
|
return []contributorsHandlerPkg.ProtocolTotalValuesDTO{
|
||||||
|
{
|
||||||
|
Protocol: "protocol1",
|
||||||
|
Error: inputArgs.mockError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
controller := protocols.NewController(zap.NewNop(), service)
|
||||||
|
err := controller.GetProtocolsTotalValues(c)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, inputArgs.expectedStatusCode, c.Response().StatusCode())
|
||||||
|
assert.Equal(t, inputArgs.expectedResponseBody, string(c.Response().Body()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockService func(ctx context.Context) []contributorsHandlerPkg.ProtocolTotalValuesDTO
|
||||||
|
|
||||||
|
func (m mockService) GetProtocolsTotalValues(ctx context.Context) []contributorsHandlerPkg.ProtocolTotalValuesDTO {
|
||||||
|
return m(ctx)
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import (
|
||||||
infrasvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/infrastructure"
|
infrasvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/infrastructure"
|
||||||
obssvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/observations"
|
obssvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/observations"
|
||||||
opsvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/operations"
|
opsvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/operations"
|
||||||
|
protocolssvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/protocols"
|
||||||
relayssvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/relays"
|
relayssvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/relays"
|
||||||
statssvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/stats"
|
statssvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/stats"
|
||||||
trxsvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/transactions"
|
trxsvc "github.com/wormhole-foundation/wormhole-explorer/api/handlers/transactions"
|
||||||
|
@ -20,6 +21,7 @@ import (
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/infrastructure"
|
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/infrastructure"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/observations"
|
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/observations"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/operations"
|
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/operations"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/protocols"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/relays"
|
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/relays"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/stats"
|
"github.com/wormhole-foundation/wormhole-explorer/api/routes/wormscan/stats"
|
||||||
|
|
||||||
|
@ -51,6 +53,7 @@ func RegisterRoutes(
|
||||||
relaysService *relayssvc.Service,
|
relaysService *relayssvc.Service,
|
||||||
operationsService *opsvc.Service,
|
operationsService *opsvc.Service,
|
||||||
statsService *statssvc.Service,
|
statsService *statssvc.Service,
|
||||||
|
protocolsService *protocolssvc.Service,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Set up controllers
|
// Set up controllers
|
||||||
|
@ -62,7 +65,8 @@ func RegisterRoutes(
|
||||||
transactionCtrl := transactions.NewController(transactionsService, rootLogger)
|
transactionCtrl := transactions.NewController(transactionsService, rootLogger)
|
||||||
relaysCtrl := relays.NewController(relaysService, rootLogger)
|
relaysCtrl := relays.NewController(relaysService, rootLogger)
|
||||||
opsCtrl := operations.NewController(operationsService, rootLogger)
|
opsCtrl := operations.NewController(operationsService, rootLogger)
|
||||||
statsCrtl := stats.NewController(statsService, rootLogger)
|
statsCtrl := stats.NewController(statsService, rootLogger)
|
||||||
|
contributorsCtrl := protocols.NewController(rootLogger, protocolsService)
|
||||||
|
|
||||||
// Set up route handlers
|
// Set up route handlers
|
||||||
api := app.Group("/api/v1")
|
api := app.Group("/api/v1")
|
||||||
|
@ -88,8 +92,9 @@ func RegisterRoutes(
|
||||||
api.Get("/transactions/:chain/:emitter/:sequence", transactionCtrl.GetTransactionByID)
|
api.Get("/transactions/:chain/:emitter/:sequence", transactionCtrl.GetTransactionByID)
|
||||||
|
|
||||||
// stats custom endpoints
|
// stats custom endpoints
|
||||||
api.Get("/top-symbols-by-volume", statsCrtl.GetTopSymbolsByVolume)
|
api.Get("/top-symbols-by-volume", statsCtrl.GetTopSymbolsByVolume)
|
||||||
api.Get("/top-100-corridors", statsCrtl.GetTopCorridors)
|
api.Get("/top-100-corridors", statsCtrl.GetTopCorridors)
|
||||||
|
api.Get("/protocols/stats", contributorsCtrl.GetProtocolsTotalValues)
|
||||||
|
|
||||||
// operations resource
|
// operations resource
|
||||||
operations := api.Group("/operations")
|
operations := api.Group("/operations")
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dbconsts
|
||||||
|
|
||||||
|
// influx-db constants
|
||||||
|
const (
|
||||||
|
ProtocolsActivityMeasurement = "protocols_activity"
|
||||||
|
ProtocolsStatsMeasurement = "protocols_stats_v1"
|
||||||
|
)
|
|
@ -139,6 +139,18 @@ spec:
|
||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: config
|
name: config
|
||||||
key: influxdb-bucket-24-hours
|
key: influxdb-bucket-24-hours
|
||||||
|
- name: WORMSCAN_PROTOCOLSSTATSVERSION
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: protocols-stats-version
|
||||||
|
- name: WORMSCAN_PROTOCOLSACTIVITYVERSION
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: protocols-activity-version
|
||||||
|
- name: WORMSCAN_PROTOCOLS
|
||||||
|
value: {{ .WORMSCAN_PROTOCOLS }}
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: {{ .RESOURCES_LIMITS_MEMORY }}
|
memory: {{ .RESOURCES_LIMITS_MEMORY }}
|
||||||
|
|
|
@ -20,3 +20,4 @@ WORMSCAN_RATELIMIT_MAX=1000
|
||||||
WORMSCAN_VAAPAYLOADPARSER_URL=
|
WORMSCAN_VAAPAYLOADPARSER_URL=
|
||||||
WORMSCAN_VAAPAYLOADPARSER_TIMEOUT=10
|
WORMSCAN_VAAPAYLOADPARSER_TIMEOUT=10
|
||||||
WORMSCAN_VAAPAYLOADPARSER_ENABLED=true
|
WORMSCAN_VAAPAYLOADPARSER_ENABLED=true
|
||||||
|
WORMSCAN_PROTOCOLS=
|
||||||
|
|
|
@ -20,3 +20,4 @@ WORMSCAN_RATELIMIT_MAX=100
|
||||||
WORMSCAN_VAAPAYLOADPARSER_URL=
|
WORMSCAN_VAAPAYLOADPARSER_URL=
|
||||||
WORMSCAN_VAAPAYLOADPARSER_TIMEOUT=10
|
WORMSCAN_VAAPAYLOADPARSER_TIMEOUT=10
|
||||||
WORMSCAN_VAAPAYLOADPARSER_ENABLED=true
|
WORMSCAN_VAAPAYLOADPARSER_ENABLED=true
|
||||||
|
WORMSCAN_PROTOCOLS=
|
|
@ -20,3 +20,4 @@ WORMSCAN_RATELIMIT_MAX=100
|
||||||
WORMSCAN_VAAPAYLOADPARSER_URL=
|
WORMSCAN_VAAPAYLOADPARSER_URL=
|
||||||
WORMSCAN_VAAPAYLOADPARSER_TIMEOUT=10
|
WORMSCAN_VAAPAYLOADPARSER_TIMEOUT=10
|
||||||
WORMSCAN_VAAPAYLOADPARSER_ENABLED=true
|
WORMSCAN_VAAPAYLOADPARSER_ENABLED=true
|
||||||
|
WORMSCAN_PROTOCOLS=allbridge,mayan
|
|
@ -20,3 +20,4 @@ WORMSCAN_RATELIMIT_MAX=100
|
||||||
WORMSCAN_VAAPAYLOADPARSER_URL=
|
WORMSCAN_VAAPAYLOADPARSER_URL=
|
||||||
WORMSCAN_VAAPAYLOADPARSER_TIMEOUT=10
|
WORMSCAN_VAAPAYLOADPARSER_TIMEOUT=10
|
||||||
WORMSCAN_VAAPAYLOADPARSER_ENABLED=true
|
WORMSCAN_VAAPAYLOADPARSER_ENABLED=true
|
||||||
|
WORMSCAN_PROTOCOLS=
|
|
@ -13,3 +13,5 @@ data:
|
||||||
influxdb-bucket-infinite: {{ .INFLUX_BUCKET_INFINITE }}
|
influxdb-bucket-infinite: {{ .INFLUX_BUCKET_INFINITE }}
|
||||||
influxdb-bucket-30-days: {{ .INFLUX_BUCKET_30_DAYS }}
|
influxdb-bucket-30-days: {{ .INFLUX_BUCKET_30_DAYS }}
|
||||||
influxdb-bucket-24-hours: {{ .INFLUX_BUCKET_24_HOURS }}
|
influxdb-bucket-24-hours: {{ .INFLUX_BUCKET_24_HOURS }}
|
||||||
|
protocols-activity-version: {{ .PROTOCOLS_ACTIVITY_VERSION }}
|
||||||
|
protocols-stats-version: {{ .PROTOCOLS_STATS_VERSION }}
|
||||||
|
|
|
@ -30,3 +30,6 @@ SEI_URL=
|
||||||
SOLANA_URL=
|
SOLANA_URL=
|
||||||
SUI_URL=
|
SUI_URL=
|
||||||
TERRA_URL=
|
TERRA_URL=
|
||||||
|
# protocols jobs
|
||||||
|
PROTOCOLS_STATS_VERSION=v1
|
||||||
|
PROTOCOLS_ACTIVITY_VERSION=v1
|
|
@ -34,3 +34,6 @@ SEI_URL=
|
||||||
SOLANA_URL=
|
SOLANA_URL=
|
||||||
SUI_URL=
|
SUI_URL=
|
||||||
TERRA_URL=
|
TERRA_URL=
|
||||||
|
# protocols jobs
|
||||||
|
PROTOCOLS_STATS_VERSION=v1
|
||||||
|
PROTOCOLS_ACTIVITY_VERSION=v1
|
|
@ -30,3 +30,6 @@ SEI_URL=
|
||||||
SOLANA_URL=
|
SOLANA_URL=
|
||||||
SUI_URL=
|
SUI_URL=
|
||||||
TERRA_URL=
|
TERRA_URL=
|
||||||
|
# protocols jobs
|
||||||
|
PROTOCOLS_STATS_VERSION=v1
|
||||||
|
PROTOCOLS_ACTIVITY_VERSION=v1
|
|
@ -34,3 +34,6 @@ SEI_URL=
|
||||||
SOLANA_URL=
|
SOLANA_URL=
|
||||||
SUI_URL=
|
SUI_URL=
|
||||||
TERRA_URL=
|
TERRA_URL=
|
||||||
|
# protocols jobs
|
||||||
|
PROTOCOLS_STATS_VERSION=v1
|
||||||
|
PROTOCOLS_ACTIVITY_VERSION=v1
|
|
@ -22,3 +22,8 @@ PRICES_URI=http://wormscan-notional.wormscan
|
||||||
OUTPUT_PATH=
|
OUTPUT_PATH=
|
||||||
#migrate vaa to origintx job
|
#migrate vaa to origintx job
|
||||||
TX_TRACKER_URL=
|
TX_TRACKER_URL=
|
||||||
|
#protocols stats job: every hour
|
||||||
|
PROTOCOLS_STATS_CRONTAB_SCHEDULE='0 * * * *'
|
||||||
|
#protocols activity job:every hour
|
||||||
|
PROTOCOLS_ACTIVITY_CRONTAB_SCHEDULE='0 * * * *'
|
||||||
|
PROTOCOLS_JSON=
|
||||||
|
|
|
@ -22,3 +22,8 @@ PRICES_URI=http://wormscan-notional.wormscan-testnet
|
||||||
OUTPUT_PATH=
|
OUTPUT_PATH=
|
||||||
#migrate vaa to origintx job
|
#migrate vaa to origintx job
|
||||||
TX_TRACKER_URL=
|
TX_TRACKER_URL=
|
||||||
|
#protocols stats job: every hour
|
||||||
|
PROTOCOLS_STATS_CRONTAB_SCHEDULE='0 * * * *'
|
||||||
|
#protocols activity job:every hour
|
||||||
|
PROTOCOLS_ACTIVITY_CRONTAB_SCHEDULE='0 * * * *'
|
||||||
|
PROTOCOLS_JSON=
|
||||||
|
|
|
@ -22,3 +22,8 @@ PRICES_URI=http://wormscan-notional.wormscan
|
||||||
OUTPUT_PATH=
|
OUTPUT_PATH=
|
||||||
#migrate vaa to origintx job
|
#migrate vaa to origintx job
|
||||||
TX_TRACKER_URL=
|
TX_TRACKER_URL=
|
||||||
|
#protocols stats job: every hour
|
||||||
|
PROTOCOLS_STATS_CRONTAB_SCHEDULE=0 * * * *
|
||||||
|
#protocols activity job:every hour
|
||||||
|
PROTOCOLS_ACTIVITY_CRONTAB_SCHEDULE=0 * * * *
|
||||||
|
PROTOCOLS_JSON=
|
||||||
|
|
|
@ -22,3 +22,8 @@ PRICES_URI=http://wormscan-notional.wormscan-testnet
|
||||||
OUTPUT_PATH=
|
OUTPUT_PATH=
|
||||||
#migrate vaas to origintx job
|
#migrate vaas to origintx job
|
||||||
TX_TRACKER_URL=
|
TX_TRACKER_URL=
|
||||||
|
#protocols stats job: every hour
|
||||||
|
PROTOCOLS_STATS_CRONTAB_SCHEDULE='0 * * * *'
|
||||||
|
#protocols activity job:every hour
|
||||||
|
PROTOCOLS_ACTIVITY_CRONTAB_SCHEDULE='0 * * * *'
|
||||||
|
PROTOCOLS_JSON=
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: protocols-activity-1h
|
||||||
|
namespace: {{ .NAMESPACE }}
|
||||||
|
spec: #cronjob specs
|
||||||
|
schedule: {{ .PROTOCOLS_ACTIVITY_CRONTAB_SCHEDULE }}
|
||||||
|
jobTemplate:
|
||||||
|
spec: # job specs
|
||||||
|
template:
|
||||||
|
spec: # pod specs
|
||||||
|
containers:
|
||||||
|
- name: protocols-activity-1h
|
||||||
|
image: {{ .IMAGE_NAME }}
|
||||||
|
imagePullPolicy: Always
|
||||||
|
env:
|
||||||
|
- name: ENVIRONMENT
|
||||||
|
value: {{ .ENVIRONMENT }}
|
||||||
|
- name: LOG_LEVEL
|
||||||
|
value: {{ .LOG_LEVEL }}
|
||||||
|
- name: JOB_ID
|
||||||
|
value: JOB_PROTOCOLS_ACTIVITY
|
||||||
|
- name: INFLUX_URL
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: influxdb-url
|
||||||
|
- name: INFLUX_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: influxdb
|
||||||
|
key: token
|
||||||
|
- name: INFLUX_ORGANIZATION
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: influxdb-organization
|
||||||
|
- name: INFLUX_BUCKET_30_DAYS
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: influxdb-bucket-30-days
|
||||||
|
- name: ACTIVITY_VERSION
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: protocols-activity-version
|
||||||
|
- name: PROTOCOLS_JSON
|
||||||
|
value: {{ .PROTOCOLS_JSON }}
|
||||||
|
restartPolicy: OnFailure
|
|
@ -0,0 +1,50 @@
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: protocols-stats-1h
|
||||||
|
namespace: {{ .NAMESPACE }}
|
||||||
|
spec: #cronjob specs
|
||||||
|
schedule: {{ .PROTOCOLS_STATS_CRONTAB_SCHEDULE }}
|
||||||
|
jobTemplate:
|
||||||
|
spec: # job specs
|
||||||
|
template:
|
||||||
|
spec: # pod specs
|
||||||
|
containers:
|
||||||
|
- name: protocols-stats-1h
|
||||||
|
image: {{ .IMAGE_NAME }}
|
||||||
|
imagePullPolicy: Always
|
||||||
|
env:
|
||||||
|
- name: ENVIRONMENT
|
||||||
|
value: {{ .ENVIRONMENT }}
|
||||||
|
- name: LOG_LEVEL
|
||||||
|
value: {{ .LOG_LEVEL }}
|
||||||
|
- name: JOB_ID
|
||||||
|
value: JOB_PROTOCOLS_STATS
|
||||||
|
- name: INFLUX_URL
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: influxdb-url
|
||||||
|
- name: INFLUX_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: influxdb
|
||||||
|
key: token
|
||||||
|
- name: INFLUX_ORGANIZATION
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: influxdb-organization
|
||||||
|
- name: INFLUX_BUCKET_30_DAYS
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: influxdb-bucket-30-days
|
||||||
|
- name: STATS_VERSION
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: protocols-stats-version
|
||||||
|
- name: PROTOCOLS_JSON
|
||||||
|
value: {{ .PROTOCOLS_JSON }}
|
||||||
|
restartPolicy: OnFailure
|
|
@ -2,7 +2,13 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/configuration"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/activity"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/stats"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -29,10 +35,10 @@ type exitCode int
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
defer handleExit()
|
defer handleExit()
|
||||||
context := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// get the config
|
// get the config
|
||||||
cfg, errConf := config.New(context)
|
cfg, errConf := config.New(ctx)
|
||||||
if errConf != nil {
|
if errConf != nil {
|
||||||
log.Fatal("error creating config", errConf)
|
log.Fatal("error creating config", errConf)
|
||||||
}
|
}
|
||||||
|
@ -43,36 +49,45 @@ func main() {
|
||||||
var err error
|
var err error
|
||||||
switch cfg.JobID {
|
switch cfg.JobID {
|
||||||
case jobs.JobIDNotional:
|
case jobs.JobIDNotional:
|
||||||
nCfg, errCfg := config.NewNotionalConfiguration(context)
|
nCfg, errCfg := config.NewNotionalConfiguration(ctx)
|
||||||
if errCfg != nil {
|
if errCfg != nil {
|
||||||
log.Fatal("error creating config", errCfg)
|
log.Fatal("error creating config", errCfg)
|
||||||
}
|
}
|
||||||
notionalJob := initNotionalJob(context, nCfg, logger)
|
notionalJob := initNotionalJob(ctx, nCfg, logger)
|
||||||
err = notionalJob.Run()
|
err = notionalJob.Run()
|
||||||
|
|
||||||
case jobs.JobIDTransferReport:
|
case jobs.JobIDTransferReport:
|
||||||
aCfg, errCfg := config.NewTransferReportConfiguration(context)
|
aCfg, errCfg := config.NewTransferReportConfiguration(ctx)
|
||||||
if errCfg != nil {
|
if errCfg != nil {
|
||||||
log.Fatal("error creating config", errCfg)
|
log.Fatal("error creating config", errCfg)
|
||||||
}
|
}
|
||||||
transferReport := initTransferReportJob(context, aCfg, logger)
|
transferReport := initTransferReportJob(ctx, aCfg, logger)
|
||||||
err = transferReport.Run(context)
|
err = transferReport.Run(ctx)
|
||||||
|
|
||||||
case jobs.JobIDHistoricalPrices:
|
case jobs.JobIDHistoricalPrices:
|
||||||
hCfg, errCfg := config.NewHistoricalPricesConfiguration(context)
|
hCfg, errCfg := config.NewHistoricalPricesConfiguration(ctx)
|
||||||
if errCfg != nil {
|
if errCfg != nil {
|
||||||
log.Fatal("error creating config", errCfg)
|
log.Fatal("error creating config", errCfg)
|
||||||
}
|
}
|
||||||
historyPrices := initHistoricalPricesJob(context, hCfg, logger)
|
historyPrices := initHistoricalPricesJob(ctx, hCfg, logger)
|
||||||
err = historyPrices.Run(context)
|
err = historyPrices.Run(ctx)
|
||||||
|
|
||||||
case jobs.JobIDMigrationSourceTx:
|
case jobs.JobIDMigrationSourceTx:
|
||||||
mCfg, errCfg := config.NewMigrateSourceTxConfiguration(context)
|
mCfg, errCfg := config.NewMigrateSourceTxConfiguration(ctx)
|
||||||
if errCfg != nil {
|
if errCfg != nil {
|
||||||
log.Fatal("error creating config", errCfg)
|
log.Fatal("error creating config", errCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
chainID := sdk.ChainID(mCfg.ChainID)
|
chainID := sdk.ChainID(mCfg.ChainID)
|
||||||
migrationJob := initMigrateSourceTxJob(context, mCfg, chainID, logger)
|
migrationJob := initMigrateSourceTxJob(ctx, mCfg, chainID, logger)
|
||||||
err = migrationJob.Run(context)
|
err = migrationJob.Run(ctx)
|
||||||
|
|
||||||
|
case jobs.JobIDProtocolsStats:
|
||||||
|
statsJob := initProtocolStatsJob(ctx, logger)
|
||||||
|
err = statsJob.Run(ctx)
|
||||||
|
case jobs.JobIDProtocolsActivity:
|
||||||
|
activityJob := initProtocolActivityJob(ctx, logger)
|
||||||
|
err = activityJob.Run(ctx)
|
||||||
default:
|
default:
|
||||||
logger.Fatal("Invalid job id", zap.String("job_id", cfg.JobID))
|
logger.Fatal("Invalid job id", zap.String("job_id", cfg.JobID))
|
||||||
}
|
}
|
||||||
|
@ -156,6 +171,52 @@ func initMigrateSourceTxJob(ctx context.Context, cfg *config.MigrateSourceTxConf
|
||||||
return migration.NewMigrationSourceChainTx(db.Database, cfg.PageSize, sdk.ChainID(cfg.ChainID), fromDate, toDate, txTrackerAPIClient, sleepTime, logger)
|
return migration.NewMigrationSourceChainTx(db.Database, cfg.PageSize, sdk.ChainID(cfg.ChainID), fromDate, toDate, txTrackerAPIClient, sleepTime, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initProtocolStatsJob(ctx context.Context, logger *zap.Logger) *stats.ProtocolsStatsJob {
|
||||||
|
cfgJob, errCfg := configuration.LoadFromEnv[config.ProtocolsStatsConfiguration](ctx)
|
||||||
|
if errCfg != nil {
|
||||||
|
log.Fatal("error creating config", errCfg)
|
||||||
|
}
|
||||||
|
errUnmarshal := json.Unmarshal([]byte(cfgJob.ProtocolsJson), &cfgJob.Protocols)
|
||||||
|
if errUnmarshal != nil {
|
||||||
|
log.Fatal("error unmarshalling protocols config", errUnmarshal)
|
||||||
|
}
|
||||||
|
dbClient := influxdb2.NewClient(cfgJob.InfluxUrl, cfgJob.InfluxToken)
|
||||||
|
dbWriter := dbClient.WriteAPIBlocking(cfgJob.InfluxOrganization, cfgJob.InfluxBucket30Days)
|
||||||
|
statsFetchers := make([]stats.ClientStats, 0, len(cfgJob.Protocols))
|
||||||
|
for _, c := range cfgJob.Protocols {
|
||||||
|
cs := stats.NewHttpRestClientStats(c.Name,
|
||||||
|
c.Url,
|
||||||
|
logger.With(zap.String("protocol", c.Name), zap.String("url", c.Url)),
|
||||||
|
&http.Client{},
|
||||||
|
)
|
||||||
|
statsFetchers = append(statsFetchers, cs)
|
||||||
|
}
|
||||||
|
return stats.NewProtocolsStatsJob(dbWriter, logger, cfgJob.StatsVersion, statsFetchers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initProtocolActivityJob(ctx context.Context, logger *zap.Logger) *activity.ProtocolsActivityJob {
|
||||||
|
cfgJob, errCfg := configuration.LoadFromEnv[config.ProtocolsStatsConfiguration](ctx)
|
||||||
|
if errCfg != nil {
|
||||||
|
log.Fatal("error creating config", errCfg)
|
||||||
|
}
|
||||||
|
errUnmarshal := json.Unmarshal([]byte(cfgJob.ProtocolsJson), &cfgJob.Protocols)
|
||||||
|
if errUnmarshal != nil {
|
||||||
|
log.Fatal("error unmarshalling protocols config", errUnmarshal)
|
||||||
|
}
|
||||||
|
dbClient := influxdb2.NewClient(cfgJob.InfluxUrl, cfgJob.InfluxToken)
|
||||||
|
dbWriter := dbClient.WriteAPIBlocking(cfgJob.InfluxOrganization, cfgJob.InfluxBucket30Days)
|
||||||
|
activityFetchers := make([]activity.ClientActivity, 0, len(cfgJob.Protocols))
|
||||||
|
for _, c := range cfgJob.Protocols {
|
||||||
|
builder, ok := activity.ActivitiesClientsFactory[c.Name]
|
||||||
|
if !ok {
|
||||||
|
log.Fatal("error creating protocol activity fetcher. Unknown protocol:", c.Name, errCfg)
|
||||||
|
}
|
||||||
|
cs := builder(c.Name, c.Url, logger.With(zap.String("protocol", c.Name), zap.String("url", c.Url)))
|
||||||
|
activityFetchers = append(activityFetchers, cs)
|
||||||
|
}
|
||||||
|
return activity.NewProtocolActivityJob(dbWriter, logger, cfgJob.ActivityVersion, activityFetchers...)
|
||||||
|
}
|
||||||
|
|
||||||
func handleExit() {
|
func handleExit() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
if e, ok := r.(exitCode); ok {
|
if e, ok := r.(exitCode); ok {
|
||||||
|
|
|
@ -57,15 +57,33 @@ type MigrateSourceTxConfiguration struct {
|
||||||
SleepTimeSeconds int64 `env:"SLEEP_TIME_SECONDS,default=5"`
|
SleepTimeSeconds int64 `env:"SLEEP_TIME_SECONDS,default=5"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProtocolsStatsConfiguration struct {
|
||||||
|
InfluxUrl string `env:"INFLUX_URL"`
|
||||||
|
InfluxToken string `env:"INFLUX_TOKEN"`
|
||||||
|
InfluxOrganization string `env:"INFLUX_ORGANIZATION"`
|
||||||
|
InfluxBucket30Days string `env:"INFLUX_BUCKET_30_DAYS"`
|
||||||
|
StatsVersion string `env:"STATS_VERSION"`
|
||||||
|
ActivityVersion string `env:"ACTIVITY_VERSION"`
|
||||||
|
ProtocolsJson string `env:"PROTOCOLS_JSON"`
|
||||||
|
Protocols []Protocol `json:"PROTOCOLS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Protocol struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProtocolsActivityConfiguration struct {
|
||||||
|
ProtocolsStatsConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
// New creates a default configuration with the values from .env file and environment variables.
|
// New creates a default configuration with the values from .env file and environment variables.
|
||||||
func New(ctx context.Context) (*Configuration, error) {
|
func New(ctx context.Context) (*Configuration, error) {
|
||||||
_ = godotenv.Load(".env", "../.env")
|
_ = godotenv.Load(".env", "../.env")
|
||||||
|
|
||||||
var configuration Configuration
|
var configuration Configuration
|
||||||
if err := envconfig.Process(ctx, &configuration); err != nil {
|
if err := envconfig.Process(ctx, &configuration); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &configuration, nil
|
return &configuration, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
42
jobs/go.mod
42
jobs/go.mod
|
@ -5,43 +5,55 @@ go 1.19
|
||||||
require (
|
require (
|
||||||
github.com/go-redis/redis v6.15.9+incompatible
|
github.com/go-redis/redis v6.15.9+incompatible
|
||||||
github.com/go-resty/resty/v2 v2.10.0
|
github.com/go-resty/resty/v2 v2.10.0
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/influxdata/influxdb-client-go/v2 v2.12.2
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/sethvargo/go-envconfig v1.0.0
|
github.com/sethvargo/go-envconfig v1.0.0
|
||||||
github.com/shopspring/decimal v1.3.1
|
github.com/shopspring/decimal v1.3.1
|
||||||
|
github.com/stretchr/testify v1.8.4
|
||||||
|
github.com/test-go/testify v1.1.4
|
||||||
github.com/wormhole-foundation/wormhole-explorer/common v0.0.0-20230713181709-0425a89e7533
|
github.com/wormhole-foundation/wormhole-explorer/common v0.0.0-20230713181709-0425a89e7533
|
||||||
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20240109172745-cc0cd9fc5229
|
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20240109172745-cc0cd9fc5229
|
||||||
go.mongodb.org/mongo-driver v1.11.2
|
go.mongodb.org/mongo-driver v1.11.2
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/algorand/go-algorand-sdk v1.23.0 // indirect
|
github.com/algorand/go-algorand-sdk v1.23.0 // indirect
|
||||||
github.com/algorand/go-codec/codec v1.1.8 // indirect
|
github.com/algorand/go-codec/codec v1.1.8 // indirect
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
|
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/cosmos/btcutil v1.0.5 // indirect
|
github.com/cosmos/btcutil v1.0.5 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||||
|
github.com/deepmap/oapi-codegen v1.8.2 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/ethereum/go-ethereum v1.10.21 // indirect
|
github.com/ethereum/go-ethereum v1.11.3 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/holiman/uint256 v1.2.1 // indirect
|
github.com/holiman/uint256 v1.2.1 // indirect
|
||||||
github.com/klauspost/compress v1.16.3 // indirect
|
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
|
github.com/montanaflynn/stats v0.7.0 // indirect
|
||||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||||
github.com/onsi/gomega v1.27.6 // indirect
|
github.com/onsi/gomega v1.27.8 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.1 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
|
||||||
golang.org/x/crypto v0.14.0 // indirect
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
golang.org/x/net v0.17.0 // indirect
|
golang.org/x/net v0.17.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/wormhole-foundation/wormhole-explorer/common => ../common
|
replace github.com/wormhole-foundation/wormhole-explorer/common => ../common
|
||||||
|
|
138
jobs/go.sum
138
jobs/go.sum
|
@ -3,26 +3,35 @@ github.com/algorand/go-algorand-sdk v1.23.0/go.mod h1:7i2peZBcE48kfoxNZnLA+mklKh
|
||||||
github.com/algorand/go-codec v1.1.8/go.mod h1:XhzVs6VVyWMLu6cApb9/192gBjGRVGm5cX5j203Heg4=
|
github.com/algorand/go-codec v1.1.8/go.mod h1:XhzVs6VVyWMLu6cApb9/192gBjGRVGm5cX5j203Heg4=
|
||||||
github.com/algorand/go-codec/codec v1.1.8 h1:lsFuhcOH2LiEhpBH3BVUUkdevVmwCRyvb7FCAAPeY6U=
|
github.com/algorand/go-codec/codec v1.1.8 h1:lsFuhcOH2LiEhpBH3BVUUkdevVmwCRyvb7FCAAPeY6U=
|
||||||
github.com/algorand/go-codec/codec v1.1.8/go.mod h1:tQ3zAJ6ijTps6V+wp8KsGDnPC2uhHVC7ANyrtkIY0bA=
|
github.com/algorand/go-codec/codec v1.1.8/go.mod h1:tQ3zAJ6ijTps6V+wp8KsGDnPC2uhHVC7ANyrtkIY0bA=
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
|
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk=
|
github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk=
|
||||||
github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis=
|
github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis=
|
||||||
|
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
|
||||||
|
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/ethereum/go-ethereum v1.10.21 h1:5lqsEx92ZaZzRyOqBEXux4/UR06m296RGzN3ol3teJY=
|
github.com/ethereum/go-ethereum v1.11.3 h1:uuBkYUJW9aY5JYi3+sqLHz+XWyo5fmn/ab9XcbtVDTU=
|
||||||
github.com/ethereum/go-ethereum v1.10.21/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
|
github.com/ethereum/go-ethereum v1.11.3/go.mod h1:rBUvAl5cdVrAei9q5lgOU7RSEuPJk1nlBDnS/YSoKQE=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
|
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
|
@ -32,65 +41,108 @@ github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gto8n9hAmRxKxoar0iE6BLucjw=
|
||||||
github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o=
|
github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o=
|
||||||
github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
|
github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
|
||||||
|
github.com/influxdata/influxdb-client-go/v2 v2.12.2 h1:uYABKdrEKlYm+++qfKdbgaHKBPmoWR5wpbmj6MBB/2g=
|
||||||
|
github.com/influxdata/influxdb-client-go/v2 v2.12.2/go.mod h1:YteV91FiQxRdccyJ2cHvj2f/5sq4y4Njqu1fQzsQCOU=
|
||||||
|
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 h1:vilfsDSy7TDxedi9gyBkMvAirat/oRcL0lFdJBf6tdM=
|
||||||
|
github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
|
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||||
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
|
||||||
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
|
github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU=
|
||||||
|
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/sethvargo/go-envconfig v1.0.0 h1:1C66wzy4QrROf5ew4KdVw942CQDa55qmlYmw9FZxZdU=
|
github.com/sethvargo/go-envconfig v1.0.0 h1:1C66wzy4QrROf5ew4KdVw942CQDa55qmlYmw9FZxZdU=
|
||||||
github.com/sethvargo/go-envconfig v1.0.0/go.mod h1:Lzc75ghUn5ucmcRGIdGQ33DKJrcjk4kihFYgSTBmjIc=
|
github.com/sethvargo/go-envconfig v1.0.0/go.mod h1:Lzc75ghUn5ucmcRGIdGQ33DKJrcjk4kihFYgSTBmjIc=
|
||||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
|
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
|
||||||
|
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20240109172745-cc0cd9fc5229 h1:fqcC4qwEVaJfcpqUVKi5+imz+JpxviQYPW4qu3zILz4=
|
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20240109172745-cc0cd9fc5229 h1:fqcC4qwEVaJfcpqUVKi5+imz+JpxviQYPW4qu3zILz4=
|
||||||
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20240109172745-cc0cd9fc5229/go.mod h1:pE/jYet19kY4P3V6mE2+01zvEfxdyBqv6L6HsnSa5uc=
|
github.com/wormhole-foundation/wormhole/sdk v0.0.0-20240109172745-cc0cd9fc5229/go.mod h1:pE/jYet19kY4P3V6mE2+01zvEfxdyBqv6L6HsnSa5uc=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
|
|
||||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
|
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||||
|
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||||
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw=
|
go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw=
|
||||||
go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
|
go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
|
||||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
|
||||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
|
@ -98,7 +150,10 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
@ -110,10 +165,20 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
@ -123,6 +188,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
@ -130,16 +196,21 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
@ -149,7 +220,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
// Package jobs define an interface to execute jobs
|
// Package jobs define an interface to execute jobs
|
||||||
package jobs
|
package jobs
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
// JobIDNotional is the job id for notional job.
|
// JobIDNotional is the job id for notional job.
|
||||||
const (
|
const (
|
||||||
JobIDNotional = "JOB_NOTIONAL_USD"
|
JobIDNotional = "JOB_NOTIONAL_USD"
|
||||||
JobIDTransferReport = "JOB_TRANSFER_REPORT"
|
JobIDTransferReport = "JOB_TRANSFER_REPORT"
|
||||||
JobIDHistoricalPrices = "JOB_HISTORICAL_PRICES"
|
JobIDHistoricalPrices = "JOB_HISTORICAL_PRICES"
|
||||||
JobIDMigrationSourceTx = "JOB_MIGRATE_SOURCE_TX"
|
JobIDMigrationSourceTx = "JOB_MIGRATE_SOURCE_TX"
|
||||||
|
JobIDProtocolsStats = "JOB_PROTOCOLS_STATS"
|
||||||
|
JobIDProtocolsActivity = "JOB_PROTOCOLS_ACTIVITY"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Job is the interface for jobs.
|
// Job is the interface for jobs.
|
||||||
type Job interface {
|
type Job interface {
|
||||||
Run() error
|
Run(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||||
|
"github.com/influxdata/influxdb-client-go/v2/api"
|
||||||
|
"github.com/influxdata/influxdb-client-go/v2/api/write"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/dbconsts"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/activity/internal/repositories"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewProtocolActivityJob creates an instance of the job implementation.
|
||||||
|
func NewProtocolActivityJob(statsDB api.WriteAPIBlocking, logger *zap.Logger, version string, activityFetchers ...ClientActivity) *ProtocolsActivityJob {
|
||||||
|
return &ProtocolsActivityJob{
|
||||||
|
statsDB: statsDB,
|
||||||
|
logger: logger.With(zap.String("module", "ProtocolsActivityJob")),
|
||||||
|
activityFetchers: activityFetchers,
|
||||||
|
version: version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ProtocolsActivityJob) Run(ctx context.Context) error {
|
||||||
|
|
||||||
|
clientsQty := len(m.activityFetchers)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(clientsQty)
|
||||||
|
errs := make(chan error, clientsQty)
|
||||||
|
ts := time.Now().UTC().Truncate(time.Hour) // make minutes and seconds zero, so we only work with date and hour
|
||||||
|
from := ts.Add(-1 * time.Hour)
|
||||||
|
m.logger.Info("running protocols activity job ", zap.Time("from", from), zap.Time("to", ts))
|
||||||
|
for _, cs := range m.activityFetchers {
|
||||||
|
go func(c ClientActivity) {
|
||||||
|
defer wg.Done()
|
||||||
|
activity, err := c.Get(ctx, from, ts)
|
||||||
|
if err != nil {
|
||||||
|
errs <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errs <- m.updateActivity(ctx, c.ProtocolName(), m.version, activity, from)
|
||||||
|
}(cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(errs)
|
||||||
|
for err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ProtocolsActivityJob) updateActivity(ctx context.Context, protocol, version string, activity repositories.ProtocolActivity, ts time.Time) error {
|
||||||
|
|
||||||
|
points := make([]*write.Point, 0, len(activity.Activities))
|
||||||
|
|
||||||
|
for i := range activity.Activities {
|
||||||
|
point := influxdb2.
|
||||||
|
NewPointWithMeasurement(dbconsts.ProtocolsActivityMeasurement).
|
||||||
|
AddTag("protocol", protocol).
|
||||||
|
AddTag("emitter_chain_id", strconv.FormatUint(activity.Activities[i].EmitterChainID, 10)).
|
||||||
|
AddTag("destination_chain_id", strconv.FormatUint(activity.Activities[i].DestinationChainID, 10)).
|
||||||
|
AddTag("version", version).
|
||||||
|
AddField("total_value_secure", activity.TotalValueSecure).
|
||||||
|
AddField("total_value_transferred", activity.TotalValueTransferred).
|
||||||
|
AddField("txs", activity.Activities[i].Txs).
|
||||||
|
AddField("total_usd", activity.Activities[i].TotalUSD).
|
||||||
|
SetTime(ts)
|
||||||
|
points = append(points, point)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := m.statsDB.WritePoint(ctx, points...)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("failed updating protocol Activities in influxdb", zap.Error(err), zap.String("protocol", protocol))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package activity_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/test-go/testify/mock"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/activity"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/activity/internal/repositories"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/internal/commons/mocks"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ProtocolsActivityJob_Succeed(t *testing.T) {
|
||||||
|
var mockErr error
|
||||||
|
activityFetcher := &mockActivityFetch{}
|
||||||
|
act := repositories.ProtocolActivity{
|
||||||
|
Activities: []repositories.Activity{
|
||||||
|
{
|
||||||
|
EmitterChainID: 1,
|
||||||
|
DestinationChainID: 2,
|
||||||
|
Txs: 150,
|
||||||
|
TotalUSD: 250000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
activityFetcher.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(act, mockErr)
|
||||||
|
activityFetcher.On("ProtocolName", mock.Anything).Return("protocol_test")
|
||||||
|
mockWriterDB := &mocks.MockWriterApi{}
|
||||||
|
mockWriterDB.On("WritePoint", mock.Anything, mock.Anything).Return(mockErr)
|
||||||
|
|
||||||
|
job := activity.NewProtocolActivityJob(mockWriterDB, zap.NewNop(), "v1", activityFetcher)
|
||||||
|
resultErr := job.Run(context.Background())
|
||||||
|
assert.Nil(t, resultErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ProtocolsActivityJob_FailFetching(t *testing.T) {
|
||||||
|
var mockErr error
|
||||||
|
activityFetcher := &mockActivityFetch{}
|
||||||
|
activityFetcher.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(repositories.ProtocolActivity{}, errors.New("mocked_error_fetch"))
|
||||||
|
activityFetcher.On("ProtocolName", mock.Anything).Return("protocol_test")
|
||||||
|
mockWriterDB := &mocks.MockWriterApi{}
|
||||||
|
mockWriterDB.On("WritePoint", mock.Anything, mock.Anything).Return(mockErr)
|
||||||
|
|
||||||
|
job := activity.NewProtocolActivityJob(mockWriterDB, zap.NewNop(), "v1", activityFetcher)
|
||||||
|
resultErr := job.Run(context.Background())
|
||||||
|
assert.NotNil(t, resultErr)
|
||||||
|
assert.Equal(t, "mocked_error_fetch", resultErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ProtocolsActivityJob_FailedUpdatingDB(t *testing.T) {
|
||||||
|
var mockErr error
|
||||||
|
activityFetcher := &mockActivityFetch{}
|
||||||
|
activityFetcher.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(repositories.ProtocolActivity{}, mockErr)
|
||||||
|
activityFetcher.On("ProtocolName", mock.Anything).Return("protocol_test")
|
||||||
|
mockWriterDB := &mocks.MockWriterApi{}
|
||||||
|
mockWriterDB.On("WritePoint", mock.Anything, mock.Anything).Return(errors.New("mocked_error_update_db"))
|
||||||
|
|
||||||
|
job := activity.NewProtocolActivityJob(mockWriterDB, zap.NewNop(), "v1", activityFetcher)
|
||||||
|
resultErr := job.Run(context.Background())
|
||||||
|
assert.NotNil(t, resultErr)
|
||||||
|
assert.Equal(t, "mocked_error_update_db", resultErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockActivityFetch struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockActivityFetch) Get(ctx context.Context, from, to time.Time) (repositories.ProtocolActivity, error) {
|
||||||
|
args := m.Called(ctx, from, to)
|
||||||
|
return args.Get(0).(repositories.ProtocolActivity), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockActivityFetch) ProtocolName() string {
|
||||||
|
args := m.Called()
|
||||||
|
return args.String(0)
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/internal/commons"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAllBridgeRestClient(name, url string, logger *zap.Logger, httpClient commons.HttpDo) *AllBridgeRestClient {
|
||||||
|
return &AllBridgeRestClient{
|
||||||
|
name: name,
|
||||||
|
url: url,
|
||||||
|
logger: logger,
|
||||||
|
client: httpClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllBridgeRestClient struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
client commons.HttpDo
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AllBridgeRestClient) ProtocolName() string {
|
||||||
|
return d.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AllBridgeRestClient) Get(ctx context.Context, from, to time.Time) (ProtocolActivity, error) {
|
||||||
|
decoratedLogger := d.logger
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, d.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed creating http request for retrieving protocol Activities",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return ProtocolActivity{}, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Set("from", from.Format(time.RFC3339))
|
||||||
|
q.Set("to", to.Format(time.RFC3339))
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
reqId := uuid.New().String()
|
||||||
|
req.Header.Set("X-Request-ID", reqId)
|
||||||
|
decoratedLogger = decoratedLogger.With(zap.String("requestID", reqId))
|
||||||
|
|
||||||
|
resp, err := d.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed retrieving protocol Activities",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return ProtocolActivity{}, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
decoratedLogger = decoratedLogger.
|
||||||
|
With(zap.String("status_code", http.StatusText(resp.StatusCode))).
|
||||||
|
With(zap.String("response_headers", commons.ToJson(resp.Header)))
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
decoratedLogger.Error("error retrieving protocol Activities: got an invalid response status code",
|
||||||
|
zap.String("response_body", string(body)),
|
||||||
|
)
|
||||||
|
return ProtocolActivity{}, errors.Errorf("failed retrieving protocol Activities from url:%s - status_code:%d - response_body:%s", d.url, resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed reading response body", zap.Error(err))
|
||||||
|
return ProtocolActivity{}, errors.Wrapf(errors.WithStack(err), "failed reading response body from protocol Activities. url:%s - status_code:%d", d.url, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var temp allBridgeActivity
|
||||||
|
err = json.Unmarshal(body, &temp)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed reading response body", zap.Error(err), zap.String("response_body", string(body)))
|
||||||
|
return ProtocolActivity{}, errors.Wrapf(errors.WithStack(err), "failed unmarshalling response body from protocol Activities. url:%s - status_code:%d - response_body:%s", d.url, resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp.toProtocolActivity()
|
||||||
|
}
|
||||||
|
|
||||||
|
type allBridgeActivity struct {
|
||||||
|
TotalValueSecured string `json:"total_value_secure"`
|
||||||
|
TotalValueTransferred string `json:"total_value_transferred"`
|
||||||
|
Activities []struct {
|
||||||
|
EmitterChainID uint64 `json:"emitter_chain_id"`
|
||||||
|
DestinationChainID uint64 `json:"destination_chain_id"`
|
||||||
|
Txs string `json:"txs"`
|
||||||
|
TotalUSD string `json:"total_usd"`
|
||||||
|
} `json:"activity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *allBridgeActivity) toProtocolActivity() (ProtocolActivity, error) {
|
||||||
|
result := ProtocolActivity{}
|
||||||
|
|
||||||
|
totalValueSecured, err := strconv.ParseFloat(m.TotalValueSecured, 64)
|
||||||
|
if err != nil {
|
||||||
|
return result, errors.Wrap(err, "failed parsing string TotalValueSecure to float64")
|
||||||
|
}
|
||||||
|
result.TotalValueSecure = totalValueSecured
|
||||||
|
|
||||||
|
totalValueTransferred, err := strconv.ParseFloat(m.TotalValueTransferred, 64)
|
||||||
|
if err != nil {
|
||||||
|
return result, errors.Wrap(err, "failed parsing string TotalValueTransferred to float64")
|
||||||
|
}
|
||||||
|
result.TotalValueTransferred = totalValueTransferred
|
||||||
|
|
||||||
|
for i := range m.Activities {
|
||||||
|
|
||||||
|
act := m.Activities[i]
|
||||||
|
txs, errTxs := strconv.ParseUint(act.Txs, 10, 64)
|
||||||
|
if errTxs != nil {
|
||||||
|
return result, errors.Wrap(errTxs, "failed parsing string txs to uint64")
|
||||||
|
}
|
||||||
|
|
||||||
|
totalUSD, errTotalUSD := strconv.ParseFloat(act.TotalUSD, 64)
|
||||||
|
if errTotalUSD != nil {
|
||||||
|
return result, errors.Wrap(errTxs, "failed parsing string total_usd to float64")
|
||||||
|
}
|
||||||
|
|
||||||
|
a := Activity{
|
||||||
|
EmitterChainID: m.Activities[i].EmitterChainID,
|
||||||
|
DestinationChainID: m.Activities[i].DestinationChainID,
|
||||||
|
Txs: txs,
|
||||||
|
TotalUSD: totalUSD,
|
||||||
|
}
|
||||||
|
result.Activities = append(result.Activities, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/internal/commons/mocks"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AllbridgeRestClientActivity_FailRequestCreation(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewAllBridgeRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return nil, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(nil, time.Now(), time.Now()) // passing ctx nil to force request creation error
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AllbridgeRestClientActivity_FailedRequestExecution(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewAllBridgeRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return nil, errors.New("mocked_http_client_do")
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "mocked_http_client_do", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AllbridgeRestClientActivity_Status500(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewAllBridgeRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("response_body_test")),
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "failed retrieving protocol Activities from url:localhost - status_code:500 - response_body:response_body_test", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AllbridgeRestClientActivity_Status200_FailedReadBody(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewAllBridgeRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: &mocks.MockFailReadCloser{},
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "failed reading response body from protocol Activities. url:localhost - status_code:200: mocked_fail_read", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AllbridgeRestClientActivity_Status200_FailedParsing(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewAllBridgeRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("this should be a json")),
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "failed unmarshalling response body from protocol Activities. url:localhost - status_code:200 - response_body:this should be a json: invalid character 'h' in literal true (expecting 'r')", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AllbridgeRestClientActivity_Status200_Succeed(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewAllBridgeRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("{\"activity\":[{\"emitter_chain_id\":5,\"destination_chain_id\":1,\"txs\":\"1827\",\"total_usd\":\"445743.185719500000\"}],\"total_value_secure\":\"0\",\"total_value_transferred\":\"5734947.136079277\"}")),
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
resp, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, float64(0), resp.TotalValueSecure)
|
||||||
|
assert.Equal(t, 5734947.136079277, resp.TotalValueTransferred)
|
||||||
|
assert.Equal(t, 1, len(resp.Activities))
|
||||||
|
assert.Equal(t, uint64(5), resp.Activities[0].EmitterChainID)
|
||||||
|
assert.Equal(t, uint64(1), resp.Activities[0].DestinationChainID)
|
||||||
|
assert.Equal(t, uint64(1827), resp.Activities[0].Txs)
|
||||||
|
assert.Equal(t, 445743.185719500000, resp.Activities[0].TotalUSD)
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/internal/commons"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewMayanRestClient(name, url string, logger *zap.Logger, httpClient commons.HttpDo) *MayanRestClient {
|
||||||
|
return &MayanRestClient{
|
||||||
|
name: name,
|
||||||
|
url: url,
|
||||||
|
logger: logger,
|
||||||
|
client: httpClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MayanRestClient struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
client commons.HttpDo
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MayanRestClient) ProtocolName() string {
|
||||||
|
return d.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MayanRestClient) Get(ctx context.Context, from, to time.Time) (ProtocolActivity, error) {
|
||||||
|
decoratedLogger := d.logger
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, d.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed creating http request for retrieving protocol Activities",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return ProtocolActivity{}, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Set("from", from.Format(time.RFC3339))
|
||||||
|
q.Set("to", to.Format(time.RFC3339))
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
reqId := uuid.New().String()
|
||||||
|
req.Header.Set("X-Request-ID", reqId)
|
||||||
|
decoratedLogger = decoratedLogger.With(zap.String("requestID", reqId))
|
||||||
|
|
||||||
|
resp, err := d.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed retrieving protocol Activities",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return ProtocolActivity{}, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
decoratedLogger = decoratedLogger.
|
||||||
|
With(zap.String("status_code", http.StatusText(resp.StatusCode))).
|
||||||
|
With(zap.String("response_headers", commons.ToJson(resp.Header)))
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
decoratedLogger.Error("error retrieving protocol Activities: got an invalid response status code",
|
||||||
|
zap.String("response_body", string(body)),
|
||||||
|
)
|
||||||
|
return ProtocolActivity{}, errors.Errorf("failed retrieving protocol Activities from url:%s - status_code:%d - response_body:%s", d.url, resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed reading response body", zap.Error(err))
|
||||||
|
return ProtocolActivity{}, errors.Wrapf(errors.WithStack(err), "failed reading response body from protocol Activities. url:%s - status_code:%d", d.url, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mayanActivity struct {
|
||||||
|
ProtocolActivity
|
||||||
|
Activities []struct {
|
||||||
|
AlternativeEmitterChainID string `json:"emmiter_chain_id"` // typo is on purpose due to mayan-api returning in that format
|
||||||
|
DestinationChainID string `json:"destination_chain_id"`
|
||||||
|
Txs uint64 `json:"txs"`
|
||||||
|
TotalUSD float64 `json:"total_usd"`
|
||||||
|
} `json:"activity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var mayanResp mayanActivity
|
||||||
|
err = json.Unmarshal(body, &mayanResp)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed reading response body", zap.Error(err), zap.String("response_body", string(body)))
|
||||||
|
return ProtocolActivity{}, errors.Wrapf(errors.WithStack(err), "failed unmarshalling response body from protocol Activities. url:%s - status_code:%d - response_body:%s", d.url, resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ProtocolActivity{
|
||||||
|
TotalValueTransferred: mayanResp.TotalValueTransferred,
|
||||||
|
TotalValueSecure: mayanResp.TotalValueSecure,
|
||||||
|
TotalMessages: mayanResp.TotalMessages,
|
||||||
|
Volume: mayanResp.Volume,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, act := range mayanResp.Activities {
|
||||||
|
|
||||||
|
emitterChainId, errEmitter := strconv.ParseUint(act.AlternativeEmitterChainID, 10, 64)
|
||||||
|
if errEmitter != nil {
|
||||||
|
return ProtocolActivity{}, errors.Wrap(errEmitter, "failed parsing protocol activity emitter chain id from string to uint64")
|
||||||
|
}
|
||||||
|
|
||||||
|
destChainId, errDest := strconv.ParseUint(act.DestinationChainID, 10, 64)
|
||||||
|
if errDest != nil {
|
||||||
|
return ProtocolActivity{}, errors.Wrap(errDest, "failed parsing protocol activity destination chain id from string to uint64")
|
||||||
|
}
|
||||||
|
|
||||||
|
val := Activity{
|
||||||
|
EmitterChainID: emitterChainId,
|
||||||
|
DestinationChainID: destChainId,
|
||||||
|
Txs: act.Txs,
|
||||||
|
TotalUSD: act.TotalUSD,
|
||||||
|
}
|
||||||
|
result.Activities = append(result.Activities, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/internal/commons/mocks"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_HttpRestClientActivity_FailRequestCreation(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewMayanRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return nil, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(nil, time.Now(), time.Now()) // passing ctx nil to force request creation error
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientActivity_FailedRequestExecution(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewMayanRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return nil, errors.New("mocked_http_client_do")
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "mocked_http_client_do", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientActivity_Status500(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewMayanRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("response_body_test")),
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "failed retrieving protocol Activities from url:localhost - status_code:500 - response_body:response_body_test", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientActivity_Status200_FailedReadBody(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewMayanRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: &mocks.MockFailReadCloser{},
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "failed reading response body from protocol Activities. url:localhost - status_code:200: mocked_fail_read", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientActivity_Status200_FailedParsing(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewMayanRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("this should be a json")),
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "failed unmarshalling response body from protocol Activities. url:localhost - status_code:200 - response_body:this should be a json: invalid character 'h' in literal true (expecting 'r')", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientActivity_Status200_Succeed(t *testing.T) {
|
||||||
|
|
||||||
|
a := NewMayanRestClient("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mocks.MockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("{\"total_value_secure\":1640898.7106282723,\"total_value_transferred\":2600395.040031102,\"total_messages\":2225,\"activity\":[{\"emmiter_chain_id\":\"1\",\"destination_chain_id\":\"2\",\"txs\":88,\"total_usd\":648500.9762709612}],\"volume\":2761848.9678057004}")),
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
resp, err := a.Get(context.Background(), time.Now(), time.Now())
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 1640898.7106282723, resp.TotalValueSecure)
|
||||||
|
assert.Equal(t, 2600395.040031102, resp.TotalValueTransferred)
|
||||||
|
assert.Equal(t, uint64(2225), resp.TotalMessages)
|
||||||
|
assert.Equal(t, 2761848.9678057004, resp.Volume)
|
||||||
|
assert.Equal(t, 1, len(resp.Activities))
|
||||||
|
assert.Equal(t, uint64(1), resp.Activities[0].EmitterChainID)
|
||||||
|
assert.Equal(t, uint64(2), resp.Activities[0].DestinationChainID)
|
||||||
|
assert.Equal(t, uint64(88), resp.Activities[0].Txs)
|
||||||
|
assert.Equal(t, 648500.9762709612, resp.Activities[0].TotalUSD)
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProtocolActivityRepository interface {
|
||||||
|
Get(ctx context.Context, from, to time.Time) (ProtocolActivity, error)
|
||||||
|
ProtocolName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProtocolActivity struct {
|
||||||
|
TotalValueSecure float64 `json:"total_value_secure"`
|
||||||
|
TotalValueTransferred float64 `json:"total_value_transferred"`
|
||||||
|
Volume float64 `json:"volume"`
|
||||||
|
TotalMessages uint64 `json:"total_messages"`
|
||||||
|
Activities []Activity `json:"activity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Activity struct {
|
||||||
|
EmitterChainID uint64 `json:"emitter_chain_id"`
|
||||||
|
DestinationChainID uint64 `json:"destination_chain_id"`
|
||||||
|
Txs uint64 `json:"txs"`
|
||||||
|
TotalUSD float64 `json:"total_usd"`
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/influxdata/influxdb-client-go/v2/api"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/activity/internal/repositories"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Protocols
|
||||||
|
const (
|
||||||
|
MayanProtocol = "mayan"
|
||||||
|
AllBridgeProtocol = "allbridge"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProtocolsActivityJob struct {
|
||||||
|
statsDB api.WriteAPIBlocking
|
||||||
|
logger *zap.Logger
|
||||||
|
activityFetchers []ClientActivity
|
||||||
|
version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientActivity Abstraction for fetching protocol Activity since each client may have different implementation details.
|
||||||
|
type ClientActivity interface {
|
||||||
|
Get(ctx context.Context, from, to time.Time) (repositories.ProtocolActivity, error)
|
||||||
|
ProtocolName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivitiesClientsFactory RestClient Factory to create the right client for each protocol.
|
||||||
|
var ActivitiesClientsFactory = map[string]func(name, url string, logger *zap.Logger) ClientActivity{
|
||||||
|
|
||||||
|
MayanProtocol: func(name, url string, logger *zap.Logger) ClientActivity {
|
||||||
|
return repositories.NewMayanRestClient(name, url, logger, &http.Client{})
|
||||||
|
},
|
||||||
|
|
||||||
|
AllBridgeProtocol: func(name, url string, logger *zap.Logger) ClientActivity {
|
||||||
|
return repositories.NewAllBridgeRestClient(name, url, logger, &http.Client{})
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package commons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToJson(headers http.Header) string {
|
||||||
|
bytes, _ := json.Marshal(headers)
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpDo interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/influxdata/influxdb-client-go/v2/api/write"
|
||||||
|
"github.com/test-go/testify/mock"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockWriterApi mock influxdb WriterApiBlocking interface
|
||||||
|
type MockWriterApi struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWriterApi) WriteRecord(ctx context.Context, line ...string) error {
|
||||||
|
args := m.Called(ctx, line)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWriterApi) WritePoint(ctx context.Context, point ...*write.Point) error {
|
||||||
|
args := m.Called(ctx, point)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWriterApi) EnableBatching() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockWriterApi) Flush(ctx context.Context) error {
|
||||||
|
args := m.Called(ctx)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockHttpClient func(req *http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (m MockHttpClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
return m(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockFailReadCloser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockFailReadCloser) Read(_ []byte) (n int, err error) {
|
||||||
|
return 0, errors.New("mocked_fail_read")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockFailReadCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
package stats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||||
|
"github.com/influxdata/influxdb-client-go/v2/api"
|
||||||
|
"github.com/influxdata/influxdb-client-go/v2/api/write"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/dbconsts"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProtocolsStatsJob struct {
|
||||||
|
statsDB api.WriteAPIBlocking
|
||||||
|
logger *zap.Logger
|
||||||
|
statsClientsFetchers []ClientStats
|
||||||
|
version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientStats Abstraction for fetching stats since each protocol may have different implementation details.
|
||||||
|
type ClientStats interface {
|
||||||
|
Get(ctx context.Context) (Stats, error)
|
||||||
|
ProtocolName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type protocolStats struct {
|
||||||
|
Stats
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stats struct {
|
||||||
|
TotalValueLocked float64
|
||||||
|
TotalMessages uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProtocolsStatsJob creates an instance of the job implementation.
|
||||||
|
func NewProtocolsStatsJob(statsDB api.WriteAPIBlocking, logger *zap.Logger, version string, statsFetchers ...ClientStats) *ProtocolsStatsJob {
|
||||||
|
return &ProtocolsStatsJob{
|
||||||
|
statsDB: statsDB,
|
||||||
|
logger: logger.With(zap.String("module", "ProtocolsStatsJob")),
|
||||||
|
statsClientsFetchers: statsFetchers,
|
||||||
|
version: version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProtocolsStatsJob) Run(ctx context.Context) error {
|
||||||
|
|
||||||
|
clientsQty := len(s.statsClientsFetchers)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(clientsQty)
|
||||||
|
stats := make(chan protocolStats, clientsQty)
|
||||||
|
var anyError error
|
||||||
|
|
||||||
|
for _, cs := range s.statsClientsFetchers {
|
||||||
|
go func(c ClientStats) {
|
||||||
|
defer wg.Done()
|
||||||
|
st, err := c.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
anyError = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stats <- protocolStats{st, c.ProtocolName()}
|
||||||
|
}(cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(stats)
|
||||||
|
|
||||||
|
err := s.updateStats(ctx, stats)
|
||||||
|
if err != nil {
|
||||||
|
anyError = err
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProtocolsStatsJob) updateStats(ctx context.Context, stats <-chan protocolStats) error {
|
||||||
|
|
||||||
|
ts := time.Now().UTC().Truncate(time.Hour) // make minutes and seconds zero, so we only work with date and hour
|
||||||
|
points := make([]*write.Point, 0, len(stats))
|
||||||
|
|
||||||
|
for st := range stats {
|
||||||
|
point := influxdb2.
|
||||||
|
NewPointWithMeasurement(dbconsts.ProtocolsStatsMeasurement).
|
||||||
|
AddTag("protocol", st.Name).
|
||||||
|
AddTag("version", s.version).
|
||||||
|
AddField("total_messages", st.TotalMessages).
|
||||||
|
AddField("total_value_locked", st.TotalValueLocked).
|
||||||
|
SetTime(ts)
|
||||||
|
|
||||||
|
points = append(points, point)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.statsDB.WritePoint(ctx, points...)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed updating protocol stats in influxdb", zap.Error(err))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default implementation of ClientStats interface. Encapsulate the url and http.client for calling a specific external service to retrieve stats
|
||||||
|
type httpRestClientStats struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
client httpDo
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpDo interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHttpRestClientStats(name, url string, logger *zap.Logger, httpClient httpDo) ClientStats {
|
||||||
|
return &httpRestClientStats{
|
||||||
|
name: name,
|
||||||
|
url: url,
|
||||||
|
logger: logger,
|
||||||
|
client: httpClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *httpRestClientStats) ProtocolName() string {
|
||||||
|
return d.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *httpRestClientStats) Get(ctx context.Context) (Stats, error) {
|
||||||
|
|
||||||
|
decoratedLogger := d.logger
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, d.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed creating http request for retrieving client stats",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return Stats{}, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqId := uuid.New().String()
|
||||||
|
req.Header.Set("X-Request-ID", reqId)
|
||||||
|
decoratedLogger = decoratedLogger.With(zap.String("requestID", reqId))
|
||||||
|
|
||||||
|
resp, err := d.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed retrieving client stats",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return Stats{}, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
decoratedLogger = decoratedLogger.
|
||||||
|
With(zap.String("status_code", http.StatusText(resp.StatusCode))).
|
||||||
|
With(zap.String("response_headers", toJson(resp.Header)))
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
decoratedLogger.Error("error retrieving client stats: got an invalid response status code",
|
||||||
|
zap.String("response_body", string(body)),
|
||||||
|
)
|
||||||
|
return Stats{}, errors.Errorf("failed retrieving client stats from url:%s - status_code:%d - response_body:%s", d.url, resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed reading response body", zap.Error(err))
|
||||||
|
return Stats{}, errors.Wrapf(errors.WithStack(err), "failed reading response body from client stats. url:%s - status_code:%d", d.url, resp.StatusCode)
|
||||||
|
}
|
||||||
|
var stats Stats
|
||||||
|
err = json.Unmarshal(body, &stats)
|
||||||
|
if err != nil {
|
||||||
|
decoratedLogger.Error("failed reading response body", zap.Error(err), zap.String("response_body", string(body)))
|
||||||
|
return Stats{}, errors.Wrapf(errors.WithStack(err), "failed unmarshalling response body from client stats. url:%s - status_code:%d - response_body:%s", d.url, resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func toJson(headers http.Header) string {
|
||||||
|
bytes, _ := json.Marshal(headers)
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rd *Stats) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
|
temp := struct {
|
||||||
|
TotalValueLocked json.RawMessage `json:"total_value_locked"`
|
||||||
|
TotalMessages json.RawMessage `json:"total_messages"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &temp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseJSONNumber(temp.TotalValueLocked, &rd.TotalValueLocked); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalMsg float64
|
||||||
|
if err := parseJSONNumber(temp.TotalMessages, &totalMsg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rd.TotalMessages = uint64(totalMsg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseJSONNumber helps to support both string and numeric JSON values since different protocols return different types for the same fields.
|
||||||
|
func parseJSONNumber(raw json.RawMessage, dest *float64) error {
|
||||||
|
var strVal string
|
||||||
|
if err := json.Unmarshal(raw, &strVal); err == nil {
|
||||||
|
val, err1 := strconv.ParseFloat(strVal, 64)
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
*dest = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal(raw, dest)
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
package stats_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/test-go/testify/mock"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/internal/commons/mocks"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/jobs/jobs/protocols/stats"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ProtocolsStatsJob_Succeed(t *testing.T) {
|
||||||
|
var mockErr error
|
||||||
|
statsFetcher := &mockStatsFetch{}
|
||||||
|
statsFetcher.On("Get", mock.Anything).Return(stats.Stats{}, mockErr)
|
||||||
|
statsFetcher.On("ProtocolName", mock.Anything).Return("protocol_test")
|
||||||
|
mockWriterDB := &mocks.MockWriterApi{}
|
||||||
|
mockWriterDB.On("WritePoint", mock.Anything, mock.Anything).Return(mockErr)
|
||||||
|
|
||||||
|
job := stats.NewProtocolsStatsJob(mockWriterDB, zap.NewNop(), "v1", statsFetcher)
|
||||||
|
resultErr := job.Run(context.Background())
|
||||||
|
assert.Nil(t, resultErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ProtocolsStatsJob_FailFetching(t *testing.T) {
|
||||||
|
var mockErr error
|
||||||
|
statsFetcher := &mockStatsFetch{}
|
||||||
|
statsFetcher.On("Get", mock.Anything).Return(stats.Stats{}, errors.New("mocked_error_fetch"))
|
||||||
|
statsFetcher.On("ProtocolName", mock.Anything).Return("protocol_test")
|
||||||
|
mockWriterDB := &mocks.MockWriterApi{}
|
||||||
|
mockWriterDB.On("WritePoint", mock.Anything, mock.Anything).Return(mockErr)
|
||||||
|
|
||||||
|
job := stats.NewProtocolsStatsJob(mockWriterDB, zap.NewNop(), "v1", statsFetcher)
|
||||||
|
resultErr := job.Run(context.Background())
|
||||||
|
assert.NotNil(t, resultErr)
|
||||||
|
assert.Equal(t, "mocked_error_fetch", resultErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ProtocolsStatsJob_FailedUpdatingDB(t *testing.T) {
|
||||||
|
var mockErr error
|
||||||
|
statsFetcher := &mockStatsFetch{}
|
||||||
|
statsFetcher.On("Get", mock.Anything).Return(stats.Stats{}, mockErr)
|
||||||
|
statsFetcher.On("ProtocolName", mock.Anything).Return("protocol_test")
|
||||||
|
mockWriterDB := &mocks.MockWriterApi{}
|
||||||
|
mockWriterDB.On("WritePoint", mock.Anything, mock.Anything).Return(errors.New("mocked_error_update_db"))
|
||||||
|
|
||||||
|
job := stats.NewProtocolsStatsJob(mockWriterDB, zap.NewNop(), "v1", statsFetcher)
|
||||||
|
resultErr := job.Run(context.Background())
|
||||||
|
assert.NotNil(t, resultErr)
|
||||||
|
assert.Equal(t, "mocked_error_update_db", resultErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientStats_FailRequestCreation(t *testing.T) {
|
||||||
|
|
||||||
|
a := stats.NewHttpRestClientStats("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return nil, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(nil) // passing ctx nil to force request creation error
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientStats_FailedRequestExecution(t *testing.T) {
|
||||||
|
|
||||||
|
a := stats.NewHttpRestClientStats("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return nil, errors.New("mocked_http_client_do")
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "mocked_http_client_do", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientStats_Status500(t *testing.T) {
|
||||||
|
|
||||||
|
a := stats.NewHttpRestClientStats("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("response_body_test")),
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "failed retrieving client stats from url:localhost - status_code:500 - response_body:response_body_test", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientStats_Status200_FailedReadBody(t *testing.T) {
|
||||||
|
|
||||||
|
a := stats.NewHttpRestClientStats("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: &mockFailReadCloser{},
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "failed reading response body from client stats. url:localhost - status_code:200: mocked_fail_read", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientStats_Status200_FailedParsing(t *testing.T) {
|
||||||
|
|
||||||
|
a := stats.NewHttpRestClientStats("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("this should be a json")),
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
_, err := a.Get(context.Background())
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, "failed unmarshalling response body from client stats. url:localhost - status_code:200 - response_body:this should be a json: invalid character 'h' in literal true (expecting 'r')", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HttpRestClientStats_Status200_Succeed(t *testing.T) {
|
||||||
|
|
||||||
|
a := stats.NewHttpRestClientStats("protocol_test", "localhost", zap.NewNop(),
|
||||||
|
mockHttpClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString("{\"total_value_locked\":\"123\",\"total_messages\":\"456\"}")),
|
||||||
|
}, nil
|
||||||
|
}))
|
||||||
|
resp, err := a.Get(context.Background())
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, float64(123), resp.TotalValueLocked)
|
||||||
|
assert.Equal(t, uint64(456), resp.TotalMessages)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockStatsFetch struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockStatsFetch) Get(ctx context.Context) (stats.Stats, error) {
|
||||||
|
args := m.Called(ctx)
|
||||||
|
return args.Get(0).(stats.Stats), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockStatsFetch) ProtocolName() string {
|
||||||
|
args := m.Called()
|
||||||
|
return args.String(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockHttpClient func(req *http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (m mockHttpClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
return m(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockFailReadCloser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFailReadCloser) Read(p []byte) (n int, err error) {
|
||||||
|
return 0, errors.New("mocked_fail_read")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFailReadCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue