2022-11-17 07:37:29 -08:00
|
|
|
package vaa
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-05-04 16:17:03 -07:00
|
|
|
"errors"
|
2023-01-05 11:40:24 -08:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
2022-11-17 07:37:29 -08:00
|
|
|
|
2023-01-05 11:40:24 -08:00
|
|
|
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
|
2022-11-23 04:06:29 -08:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
|
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/api/response"
|
2023-02-28 12:50:23 -08:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/api/types"
|
2023-05-04 16:17:03 -07:00
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache"
|
2023-01-23 06:45:09 -08:00
|
|
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
2022-11-17 07:37:29 -08:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
2022-11-23 04:06:29 -08:00
|
|
|
// Service definition.
|
2022-11-17 07:37:29 -08:00
|
|
|
type Service struct {
|
2023-01-05 11:40:24 -08:00
|
|
|
repo *Repository
|
|
|
|
getCacheFunc cache.CacheGetFunc
|
|
|
|
logger *zap.Logger
|
2022-11-17 07:37:29 -08:00
|
|
|
}
|
|
|
|
|
2023-02-01 04:59:51 -08:00
|
|
|
// NewService creates a new VAA Service.
|
2023-01-05 11:40:24 -08:00
|
|
|
func NewService(r *Repository, getCacheFunc cache.CacheGetFunc, logger *zap.Logger) *Service {
|
2023-05-04 16:17:03 -07:00
|
|
|
|
|
|
|
s := Service{
|
|
|
|
repo: r,
|
|
|
|
getCacheFunc: getCacheFunc,
|
|
|
|
logger: logger.With(zap.String("module", "VaaService")),
|
|
|
|
}
|
|
|
|
|
|
|
|
return &s
|
2022-11-17 07:37:29 -08:00
|
|
|
}
|
|
|
|
|
2023-02-01 04:59:51 -08:00
|
|
|
// FindAllParams passes input data to the function `FindAll`.
|
|
|
|
type FindAllParams struct {
|
|
|
|
Pagination *pagination.Pagination
|
2023-04-05 06:33:28 -07:00
|
|
|
TxHash *types.TxHash
|
2023-02-01 04:59:51 -08:00
|
|
|
IncludeParsedPayload bool
|
|
|
|
AppId string
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindAll returns all VAAs.
|
2023-01-31 10:24:34 -08:00
|
|
|
func (s *Service) FindAll(
|
|
|
|
ctx context.Context,
|
2023-02-01 04:59:51 -08:00
|
|
|
params *FindAllParams,
|
2023-02-03 11:46:02 -08:00
|
|
|
) (*response.Response[[]*VaaDoc], error) {
|
2023-01-31 10:24:34 -08:00
|
|
|
|
2023-02-03 11:46:02 -08:00
|
|
|
// set up query parameters
|
2023-04-05 06:33:28 -07:00
|
|
|
query := Query().
|
|
|
|
IncludeParsedPayload(params.IncludeParsedPayload)
|
2023-02-01 04:59:51 -08:00
|
|
|
if params.Pagination != nil {
|
|
|
|
query.SetPagination(params.Pagination)
|
2022-11-17 07:37:29 -08:00
|
|
|
}
|
2023-02-01 04:59:51 -08:00
|
|
|
if params.TxHash != nil {
|
|
|
|
query.SetTxHash(params.TxHash.String())
|
2023-01-26 06:54:41 -08:00
|
|
|
}
|
2023-02-01 04:59:51 -08:00
|
|
|
if params.AppId != "" {
|
|
|
|
query.SetAppId(params.AppId)
|
|
|
|
}
|
|
|
|
|
2023-02-03 11:46:02 -08:00
|
|
|
// execute the database query
|
|
|
|
var err error
|
|
|
|
var vaas []*VaaDoc
|
2023-04-05 06:33:28 -07:00
|
|
|
if params.TxHash != nil && params.TxHash.IsSolanaTxHash() {
|
|
|
|
vaas, err = s.repo.FindVaasBySolanaTxHash(ctx, params.TxHash.String(), params.IncludeParsedPayload)
|
2023-01-31 10:24:34 -08:00
|
|
|
} else {
|
2023-04-05 06:33:28 -07:00
|
|
|
vaas, err = s.repo.FindVaas(ctx, query)
|
2023-02-03 11:46:02 -08:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-01-31 10:24:34 -08:00
|
|
|
}
|
2023-02-03 11:46:02 -08:00
|
|
|
|
|
|
|
// return the matching documents
|
|
|
|
res := response.Response[[]*VaaDoc]{Data: vaas}
|
|
|
|
return &res, nil
|
2022-11-17 07:37:29 -08:00
|
|
|
}
|
|
|
|
|
2022-11-23 04:06:29 -08:00
|
|
|
// FindByChain get all the vaa by chainID.
|
2023-02-03 11:46:02 -08:00
|
|
|
func (s *Service) FindByChain(
|
|
|
|
ctx context.Context,
|
|
|
|
chain vaa.ChainID,
|
|
|
|
p *pagination.Pagination,
|
|
|
|
) (*response.Response[[]*VaaDoc], error) {
|
|
|
|
|
|
|
|
query := Query().
|
|
|
|
SetChain(chain).
|
2023-04-05 06:33:28 -07:00
|
|
|
SetPagination(p).
|
|
|
|
IncludeParsedPayload(false)
|
2023-02-03 11:46:02 -08:00
|
|
|
|
2023-04-05 06:33:28 -07:00
|
|
|
vaas, err := s.repo.FindVaas(ctx, query)
|
2023-02-03 11:46:02 -08:00
|
|
|
|
2022-11-23 04:06:29 -08:00
|
|
|
res := response.Response[[]*VaaDoc]{Data: vaas}
|
2022-11-17 07:37:29 -08:00
|
|
|
return &res, err
|
|
|
|
}
|
|
|
|
|
2022-11-23 04:06:29 -08:00
|
|
|
// FindByEmitter get all the vaa by chainID and emitter address.
|
2023-02-03 11:46:02 -08:00
|
|
|
func (s *Service) FindByEmitter(
|
|
|
|
ctx context.Context,
|
|
|
|
chain vaa.ChainID,
|
2023-02-28 12:50:23 -08:00
|
|
|
emitter *types.Address,
|
2023-02-03 11:46:02 -08:00
|
|
|
p *pagination.Pagination,
|
|
|
|
) (*response.Response[[]*VaaDoc], error) {
|
|
|
|
|
|
|
|
query := Query().
|
|
|
|
SetChain(chain).
|
2023-03-16 11:42:34 -07:00
|
|
|
SetEmitter(emitter.Hex()).
|
2023-04-05 06:33:28 -07:00
|
|
|
SetPagination(p).
|
|
|
|
IncludeParsedPayload(false)
|
2023-02-03 11:46:02 -08:00
|
|
|
|
2023-04-05 06:33:28 -07:00
|
|
|
vaas, err := s.repo.FindVaas(ctx, query)
|
2023-02-03 11:46:02 -08:00
|
|
|
|
2022-11-23 04:06:29 -08:00
|
|
|
res := response.Response[[]*VaaDoc]{Data: vaas}
|
2022-11-17 07:37:29 -08:00
|
|
|
return &res, err
|
|
|
|
}
|
|
|
|
|
2023-01-27 08:47:17 -08:00
|
|
|
// If the parameter [payload] is true, the parse payload is added in the response.
|
2023-01-31 10:24:34 -08:00
|
|
|
func (s *Service) FindById(
|
|
|
|
ctx context.Context,
|
|
|
|
chain vaa.ChainID,
|
2023-02-28 12:50:23 -08:00
|
|
|
emitter *types.Address,
|
2023-01-31 10:24:34 -08:00
|
|
|
seq string,
|
2023-02-01 04:59:51 -08:00
|
|
|
includeParsedPayload bool,
|
2023-02-03 11:46:02 -08:00
|
|
|
) (*response.Response[*VaaDoc], error) {
|
2023-01-31 10:24:34 -08:00
|
|
|
|
2023-01-05 11:40:24 -08:00
|
|
|
// check vaa sequence indexed
|
|
|
|
isVaaNotIndexed := s.discardVaaNotIndexed(ctx, chain, emitter, seq)
|
|
|
|
if isVaaNotIndexed {
|
|
|
|
return nil, errs.ErrNotFound
|
|
|
|
}
|
|
|
|
|
2023-02-03 11:46:02 -08:00
|
|
|
// execute the database query
|
2023-04-05 06:33:28 -07:00
|
|
|
vaa, err := s.findById(ctx, chain, emitter, seq, includeParsedPayload)
|
2023-02-03 11:46:02 -08:00
|
|
|
if err != nil {
|
|
|
|
return &response.Response[*VaaDoc]{}, err
|
2023-01-27 08:47:17 -08:00
|
|
|
}
|
2023-02-03 11:46:02 -08:00
|
|
|
|
|
|
|
// return matching documents
|
|
|
|
resp := response.Response[*VaaDoc]{Data: vaa}
|
|
|
|
return &resp, err
|
2023-01-27 08:47:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// findById get a vaa by chainID, emitter address and sequence number.
|
2023-02-03 11:46:02 -08:00
|
|
|
func (s *Service) findById(
|
|
|
|
ctx context.Context,
|
|
|
|
chain vaa.ChainID,
|
2023-02-28 12:50:23 -08:00
|
|
|
emitter *types.Address,
|
2023-02-03 11:46:02 -08:00
|
|
|
seq string,
|
2023-04-05 06:33:28 -07:00
|
|
|
includeParsedPayload bool,
|
2023-02-03 11:46:02 -08:00
|
|
|
) (*VaaDoc, error) {
|
|
|
|
|
2023-04-05 06:33:28 -07:00
|
|
|
// query matching documents from the database
|
2023-02-03 11:46:02 -08:00
|
|
|
query := Query().
|
|
|
|
SetChain(chain).
|
2023-03-16 11:42:34 -07:00
|
|
|
SetEmitter(emitter.Hex()).
|
2023-04-05 06:33:28 -07:00
|
|
|
SetSequence(seq).
|
|
|
|
IncludeParsedPayload(includeParsedPayload)
|
|
|
|
docs, err := s.repo.FindVaas(ctx, query)
|
2023-01-31 10:24:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-04-05 06:33:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// we're expecting exactly one document
|
|
|
|
if len(docs) == 0 {
|
2023-01-31 10:24:34 -08:00
|
|
|
return nil, errs.ErrNotFound
|
2023-04-05 06:33:28 -07:00
|
|
|
}
|
|
|
|
if len(docs) > 1 {
|
2023-01-31 10:24:34 -08:00
|
|
|
requestID := fmt.Sprintf("%v", ctx.Value("requestid"))
|
|
|
|
s.logger.Error("can not get more that one vaa by chainID/address/sequence",
|
|
|
|
zap.Any("q", query),
|
2023-04-05 06:33:28 -07:00
|
|
|
zap.String("requestID", requestID))
|
2023-01-31 10:24:34 -08:00
|
|
|
return nil, errs.ErrInternalError
|
|
|
|
}
|
|
|
|
|
2023-04-05 06:33:28 -07:00
|
|
|
return docs[0], nil
|
2022-11-17 07:37:29 -08:00
|
|
|
}
|
|
|
|
|
2022-11-23 05:15:16 -08:00
|
|
|
// GetVaaCount get a list a list of vaa count grouped by chainID.
|
|
|
|
func (s *Service) GetVaaCount(ctx context.Context, p *pagination.Pagination) (*response.Response[[]*VaaStats], error) {
|
|
|
|
if p == nil {
|
2023-02-09 09:28:39 -08:00
|
|
|
p = pagination.Default()
|
2022-11-23 05:15:16 -08:00
|
|
|
}
|
|
|
|
query := Query().SetPagination(p)
|
|
|
|
stats, err := s.repo.GetVaaCount(ctx, query)
|
2022-11-23 04:06:29 -08:00
|
|
|
res := response.Response[[]*VaaStats]{Data: stats}
|
2022-11-17 07:37:29 -08:00
|
|
|
return &res, err
|
|
|
|
}
|
2023-01-05 11:40:24 -08:00
|
|
|
|
|
|
|
// discardVaaNotIndexed discard a vaa request if the input sequence for a chainID, address is greatter than or equals
|
|
|
|
// the cached value of the sequence for this chainID, address.
|
|
|
|
// If the sequence does not exist we can not discard the request.
|
2023-02-28 12:50:23 -08:00
|
|
|
func (s *Service) discardVaaNotIndexed(ctx context.Context, chain vaa.ChainID, emitter *types.Address, seq string) bool {
|
2023-03-16 11:42:34 -07:00
|
|
|
key := fmt.Sprintf("%s:%d:%s", "wormscan:vaa-max-sequence", chain, emitter.Hex())
|
2023-01-05 11:40:24 -08:00
|
|
|
sequence, err := s.getCacheFunc(ctx, key)
|
|
|
|
if err != nil {
|
2023-05-04 16:17:03 -07:00
|
|
|
if errors.Is(err, cache.ErrInternal) {
|
2023-01-05 11:40:24 -08:00
|
|
|
requestID := fmt.Sprintf("%v", ctx.Value("requestid"))
|
2023-05-04 16:17:03 -07:00
|
|
|
s.logger.Error("encountered an internal error while getting value from cache",
|
|
|
|
zap.Error(err),
|
|
|
|
zap.String("requestID", requestID),
|
|
|
|
)
|
2023-01-05 11:40:24 -08:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
inputSquence, err := strconv.ParseUint(seq, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
requestID := fmt.Sprintf("%v", ctx.Value("requestid"))
|
|
|
|
s.logger.Error("error invalid input sequence number",
|
|
|
|
zap.Error(err), zap.String("seq", seq), zap.String("requestID", requestID))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
cacheSequence, err := strconv.ParseUint(sequence, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
requestID := fmt.Sprintf("%v", ctx.Value("requestid"))
|
|
|
|
s.logger.Error("error invalid cached sequence number",
|
|
|
|
zap.Error(err), zap.String("sequence", sequence), zap.String("requestID", requestID))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the input sequence is indexed.
|
|
|
|
if cacheSequence >= inputSquence {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|