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 }