2022-11-17 07:37:29 -08:00
|
|
|
package vaa
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
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
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/wormhole-foundation/wormhole-explorer/api/internal/cache"
|
|
|
|
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-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
|
|
|
}
|
|
|
|
|
2022-11-23 04:06:29 -08:00
|
|
|
// NewService create a new Service.
|
2023-01-05 11:40:24 -08:00
|
|
|
func NewService(r *Repository, getCacheFunc cache.CacheGetFunc, logger *zap.Logger) *Service {
|
|
|
|
return &Service{repo: r, getCacheFunc: getCacheFunc, logger: logger.With(zap.String("module", "VaaService"))}
|
2022-11-17 07:37:29 -08:00
|
|
|
}
|
|
|
|
|
2022-11-23 04:06:29 -08:00
|
|
|
// FindAll get all the the vaa.
|
2023-01-31 10:24:34 -08:00
|
|
|
func (s *Service) FindAll(
|
|
|
|
ctx context.Context,
|
|
|
|
p *pagination.Pagination,
|
|
|
|
txHash *vaa.Address,
|
|
|
|
includeParsedPayload bool,
|
|
|
|
) (*response.Response[[]*VaaWithPayload], error) {
|
|
|
|
|
2022-11-17 07:37:29 -08:00
|
|
|
if p == nil {
|
|
|
|
p = pagination.FirstPage()
|
|
|
|
}
|
2023-01-31 10:24:34 -08:00
|
|
|
|
2022-11-17 07:37:29 -08:00
|
|
|
query := Query().SetPagination(p)
|
2023-01-26 06:54:41 -08:00
|
|
|
if txHash != nil {
|
|
|
|
query = query.SetTxHash(txHash.String())
|
|
|
|
}
|
2023-01-31 10:24:34 -08:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2022-11-17 07:37:29 -08:00
|
|
|
}
|
|
|
|
|
2022-11-23 04:06:29 -08:00
|
|
|
// FindByChain get all the vaa by chainID.
|
|
|
|
func (s *Service) FindByChain(ctx context.Context, chain vaa.ChainID, p *pagination.Pagination) (*response.Response[[]*VaaDoc], error) {
|
2022-11-17 07:37:29 -08:00
|
|
|
query := Query().SetChain(chain).SetPagination(p)
|
|
|
|
vaas, err := s.repo.Find(ctx, query)
|
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.
|
|
|
|
func (s *Service) FindByEmitter(ctx context.Context, chain vaa.ChainID, emitter vaa.Address, p *pagination.Pagination) (*response.Response[[]*VaaDoc], error) {
|
2022-11-17 07:37:29 -08:00
|
|
|
query := Query().SetChain(chain).SetEmitter(emitter.String()).SetPagination(p)
|
|
|
|
vaas, err := s.repo.Find(ctx, query)
|
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,
|
|
|
|
emitter vaa.Address,
|
|
|
|
seq string,
|
|
|
|
payload bool,
|
|
|
|
) (*response.Response[*VaaWithPayload], error) {
|
|
|
|
|
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-01-27 08:47:17 -08:00
|
|
|
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) {
|
2022-11-17 07:37:29 -08:00
|
|
|
query := Query().SetChain(chain).SetEmitter(emitter.String()).SetSequence(seq)
|
2023-01-27 08:47:17 -08:00
|
|
|
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)
|
2023-01-31 10:24:34 -08:00
|
|
|
|
|
|
|
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
|
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 {
|
|
|
|
p = pagination.FirstPage()
|
|
|
|
}
|
|
|
|
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.
|
|
|
|
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
|
|
|
|
}
|