wormhole-explorer/api/handlers/vaa/service.go

214 lines
6.8 KiB
Go

package vaa
import (
"context"
"fmt"
"strconv"
"github.com/pkg/errors"
"github.com/wormhole-foundation/wormhole-explorer/api/internal/cache"
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
"github.com/wormhole-foundation/wormhole-explorer/api/response"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)
// Service definition.
type Service struct {
repo *Repository
getCacheFunc cache.CacheGetFunc
logger *zap.Logger
}
// NewService create a new Service.
func NewService(r *Repository, getCacheFunc cache.CacheGetFunc, logger *zap.Logger) *Service {
return &Service{repo: r, getCacheFunc: getCacheFunc, logger: logger.With(zap.String("module", "VaaService"))}
}
// FindAll get all the the vaa.
func (s *Service) FindAll(
ctx context.Context,
p *pagination.Pagination,
txHash *vaa.Address,
includeParsedPayload bool,
) (*response.Response[[]*VaaWithPayload], error) {
if p == nil {
p = pagination.FirstPage()
}
query := Query().SetPagination(p)
if txHash != nil {
query = query.SetTxHash(txHash.String())
}
if includeParsedPayload {
vaas, err := s.repo.FindVaasWithPayload(ctx, query)
if err != nil {
return nil, err
}
return &response.Response[[]*VaaWithPayload]{Data: vaas}, nil
} else {
vaas, err := s.repo.Find(ctx, query)
if err != nil {
return nil, err
}
var vaasWithPayload []*VaaWithPayload
for i := range vaas {
vaaWithPayload := VaaWithPayload{
ID: vaas[i].ID,
Version: vaas[i].Version,
EmitterChain: vaas[i].EmitterChain,
EmitterAddr: vaas[i].EmitterAddr,
Sequence: vaas[i].Sequence,
GuardianSetIndex: vaas[i].GuardianSetIndex,
Timestamp: vaas[i].Timestamp,
IndexedAt: vaas[i].IndexedAt,
UpdatedAt: vaas[i].UpdatedAt,
Vaa: vaas[i].Vaa,
}
vaasWithPayload = append(vaasWithPayload, &vaaWithPayload)
}
resp := response.Response[[]*VaaWithPayload]{Data: vaasWithPayload}
return &resp, err
}
}
// FindByChain get all the vaa by chainID.
func (s *Service) FindByChain(ctx context.Context, chain vaa.ChainID, p *pagination.Pagination) (*response.Response[[]*VaaDoc], error) {
query := Query().SetChain(chain).SetPagination(p)
vaas, err := s.repo.Find(ctx, query)
res := response.Response[[]*VaaDoc]{Data: vaas}
return &res, err
}
// FindByEmitter get all the vaa by chainID and emitter address.
func (s *Service) FindByEmitter(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, p *pagination.Pagination) (*response.Response[[]*VaaDoc], error) {
query := Query().SetChain(chain).SetEmitter(emitter.String()).SetPagination(p)
vaas, err := s.repo.Find(ctx, query)
res := response.Response[[]*VaaDoc]{Data: vaas}
return &res, err
}
// If the parameter [payload] is true, the parse payload is added in the response.
func (s *Service) FindById(
ctx context.Context,
chain vaa.ChainID,
emitter vaa.Address,
seq string,
payload bool,
) (*response.Response[*VaaWithPayload], error) {
// check vaa sequence indexed
isVaaNotIndexed := s.discardVaaNotIndexed(ctx, chain, emitter, seq)
if isVaaNotIndexed {
return nil, errs.ErrNotFound
}
if payload {
vaaWithPayload, err := s.findByIdWithPayload(ctx, chain, emitter, seq)
resp := response.Response[*VaaWithPayload]{Data: vaaWithPayload}
return &resp, err
} else {
vaa, err := s.findById(ctx, chain, emitter, seq)
if err != nil {
return &response.Response[*VaaWithPayload]{}, err
}
vaaWithPayload := VaaWithPayload{
ID: vaa.ID,
Version: vaa.Version,
EmitterChain: vaa.EmitterChain,
EmitterAddr: vaa.EmitterAddr,
Sequence: vaa.Sequence,
GuardianSetIndex: vaa.GuardianSetIndex,
Timestamp: vaa.Timestamp,
IndexedAt: vaa.IndexedAt,
UpdatedAt: vaa.UpdatedAt,
Vaa: vaa.Vaa,
}
resp := response.Response[*VaaWithPayload]{Data: &vaaWithPayload}
return &resp, err
}
}
// findById get a vaa by chainID, emitter address and sequence number.
func (s *Service) findById(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, seq string) (*VaaDoc, error) {
query := Query().SetChain(chain).SetEmitter(emitter.String()).SetSequence(seq)
return s.repo.FindOne(ctx, query)
}
// findByIdWithPayload get a vaa with payload data by chainID, emitter address and sequence number.
func (s *Service) findByIdWithPayload(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, seq string) (*VaaWithPayload, error) {
query := Query().SetChain(chain).SetEmitter(emitter.String()).SetSequence(seq)
vaas, err := s.repo.FindVaasWithPayload(ctx, query)
if err != nil {
return nil, err
} else if len(vaas) == 0 {
return nil, errs.ErrNotFound
} else if len(vaas) > 1 {
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),
zap.String("requestID", requestID),
)
return nil, errs.ErrInternalError
}
return vaas[0], nil
}
// 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 {
p = pagination.FirstPage()
}
query := Query().SetPagination(p)
stats, err := s.repo.GetVaaCount(ctx, query)
res := response.Response[[]*VaaStats]{Data: stats}
return &res, err
}
// 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.
func (s *Service) discardVaaNotIndexed(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, seq string) bool {
key := fmt.Sprintf("%s:%d:%s", "wormscan:vaa-max-sequence", chain, emitter.String())
sequence, err := s.getCacheFunc(ctx, key)
if err != nil {
if errors.Is(err, errs.ErrInternalError) {
requestID := fmt.Sprintf("%v", ctx.Value("requestid"))
s.logger.Error("error getting value from cache",
zap.Error(err), zap.String("requestID", requestID))
}
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
}