wormhole-explorer/api/middleware/extract_parameters.go

346 lines
9.9 KiB
Go

// package middleare contains all the middleware function to use in the API.
package middleware
import (
"fmt"
"regexp"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/pkg/errors"
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/transactions"
"github.com/wormhole-foundation/wormhole-explorer/api/response"
"github.com/wormhole-foundation/wormhole-explorer/api/types"
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
)
// ExtractChainID get chain parameter from route path.
func ExtractChainID(c *fiber.Ctx, l *zap.Logger) (sdk.ChainID, error) {
chain, err := c.ParamsInt("chain")
if err != nil {
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
l.Error("failed to get chain parameter",
zap.Error(err),
zap.Int("chain", chain),
zap.String("requestID", requestID),
)
return sdk.ChainIDUnset, response.NewInvalidParamError(c, "WRONG CHAIN ID", errors.WithStack(err))
}
return sdk.ChainID(chain), nil
}
// ExtractEmitterAddr parses the emitter address from the request path.
//
// When the parameter `chainIdHint` is not nil, this function will attempt to parse the
// native address format of the specified chain.
//
// The fallback behavior is to parse the address according to the Wormhole hex format.
func ExtractEmitterAddr(c *fiber.Ctx, l *zap.Logger, chainIdHint *sdk.ChainID) (*types.Address, error) {
emitterStr := c.Params("emitter")
// Decide whether to accept the Solana address format based on the context
var acceptSolanaFormat bool
if chainIdHint != nil && *chainIdHint == sdk.ChainIDSolana {
acceptSolanaFormat = true
}
// Attempt to parse the address
emitter, err := types.StringToAddress(emitterStr, acceptSolanaFormat)
if err != nil {
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
l.Error("failed to convert emitter to wormhole address",
zap.Error(err),
zap.String("emitterStr", emitterStr),
zap.String("requestID", requestID),
)
return nil, response.NewInvalidParamError(c, "MALFORMED EMITTER_ADDR", errors.WithStack(err))
}
return emitter, nil
}
// ExtractSequence get sequence parameter from route path.
func ExtractSequence(c *fiber.Ctx, l *zap.Logger) (uint64, error) {
sequence := c.Params("sequence")
seq, err := strconv.ParseUint(sequence, 10, 64)
if err != nil {
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
l.Error("failed to get sequence parameter",
zap.Error(err),
zap.String("sequence", sequence),
zap.String("requestID", requestID),
)
return 0, response.NewInvalidParamError(c, "MALFORMED SEQUENCE NUMBER", errors.WithStack(err))
}
return seq, nil
}
// ExtractGuardianAddress get guardian address from route path.
func ExtractGuardianAddress(c *fiber.Ctx, l *zap.Logger) (*types.Address, error) {
// read the address from query params
tmp := c.Params("guardian_address")
if tmp == "" {
return nil, response.NewInvalidParamError(c, "MALFORMED GUARDIAN ADDR", nil)
}
// Attempt to parse the address
guardianAddress, err := types.StringToAddress(tmp, false /*acceptSolanaFormat*/)
if err != nil {
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
l.Error("failed to decode guardian address",
zap.Error(err),
zap.String("requestID", requestID),
)
return nil, response.NewInvalidParamError(c, "MALFORMED GUARDIAN ADDR", errors.WithStack(err))
}
return guardianAddress, nil
}
// ExtractVAAParams get VAA chain, address from route path.
func ExtractVAAChainIDEmitter(c *fiber.Ctx, l *zap.Logger) (sdk.ChainID, *types.Address, error) {
chainID, err := ExtractChainID(c, l)
if err != nil {
return sdk.ChainIDUnset, nil, err
}
address, err := ExtractEmitterAddr(c, l, &chainID)
if err != nil {
return chainID, nil, err
}
return chainID, address, nil
}
// ExtractVAAParams get VAAA chain, address and sequence from route path.
func ExtractVAAParams(c *fiber.Ctx, l *zap.Logger) (sdk.ChainID, *types.Address, uint64, error) {
chainID, err := ExtractChainID(c, l)
if err != nil {
return sdk.ChainIDUnset, nil, 0, err
}
address, err := ExtractEmitterAddr(c, l, &chainID)
if err != nil {
return chainID, nil, 0, err
}
seq, err := ExtractSequence(c, l)
if err != nil {
return chainID, address, 0, err
}
return chainID, address, seq, nil
}
// ExtractObservationSigner get signer from route path.
func ExtractObservationSigner(c *fiber.Ctx, l *zap.Logger) (*sdk.Address, error) {
signer := c.Params("signer")
signerAddr, err := sdk.StringToAddress(signer)
if err != nil {
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
l.Error("failed to covert signer to address",
zap.Error(err),
zap.String("signer", signer),
zap.String("requestID", requestID),
)
return nil, response.NewInvalidParamError(c, "MALFORMED SIGNER", errors.WithStack(err))
}
return &signerAddr, nil
}
// ExtractObservationHash get a hash from route path.
func ExtractObservationHash(c *fiber.Ctx, l *zap.Logger) (string, error) {
hash := c.Params("hash")
if hash == "" {
return "", response.NewInvalidParamError(c, "MALFORMED HASH", nil)
}
return hash, nil
}
func ExtractAddress(c *fiber.Ctx, l *zap.Logger) (*types.Address, error) {
val := c.Params("id")
// Attempt to parse the address
addr, err := types.StringToAddress(val, true /*acceptSolanaFormat*/)
if err != nil {
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
l.Error("failed to decode address",
zap.Error(err),
zap.String("requestID", requestID),
)
return nil, response.NewInvalidParamError(c, "MALFORMED ADDR", errors.WithStack(err))
}
return addr, nil
}
// GetTxHash parses the `txHash` parameter from query params.
func GetTxHash(c *fiber.Ctx, l *zap.Logger) (*types.TxHash, error) {
value := c.Query("txHash")
if value == "" {
return nil, nil
}
txHash, err := types.ParseTxHash(value)
if err != nil {
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
l.Error("failed to parse txHash",
zap.Error(err),
zap.String("txHash", value),
zap.String("requestID", requestID),
)
return nil, response.NewInvalidParamError(c, "MALFORMED TX HASH", errors.WithStack(err))
}
return txHash, nil
}
// ExtractParsedPayload get parsedPayload query parameter.
func ExtractParsedPayload(c *fiber.Ctx, l *zap.Logger) (bool, error) {
parsedPayloadStr := c.Query("parsedPayload", "false")
parsedPayload, err := strconv.ParseBool(parsedPayloadStr)
if err != nil {
return false, response.NewInvalidQueryParamError(c, "INVALID <parsedPayload> QUERY PARAMETER", errors.WithStack(err))
}
return parsedPayload, nil
}
func ExtractAppId(c *fiber.Ctx, l *zap.Logger) string {
return c.Query("appId")
}
func ExtractTimeSpan(c *fiber.Ctx, l *zap.Logger) (string, error) {
// get the timeSpan from query params
timeSpanStr := c.Query("timeSpan", "1d")
// validate the timeSpan
if !isValidTimeSpan(timeSpanStr) {
return "", response.NewInvalidQueryParamError(c, "INVALID <timeSpan> QUERY PARAMETER", nil)
}
return timeSpanStr, nil
}
// isValidTimeSpan check that the timeSpan is valid.
func isValidTimeSpan(timeSpan string) bool {
return regexp.MustCompile(`^1d$|^1w$|^1mo$`).MatchString(timeSpan)
}
func ExtractSampleRate(c *fiber.Ctx, l *zap.Logger) (string, error) {
// get the sampleRate from query params
sampleRateStr := c.Query("sampleRate", "1h")
// validate the sampleRate
if !isValidSampleRate(sampleRateStr) {
return "", response.NewInvalidQueryParamError(c, "INVALID <sampleRate> QUERY PARAMETER", nil)
}
return sampleRateStr, nil
}
func isValidSampleRate(sampleRate string) bool {
return regexp.MustCompile(`^1h$|^1d$`).MatchString(sampleRate)
}
func ExtractTime(c *fiber.Ctx, queryParam string) (*time.Time, error) {
// get the start_time from query params
date := c.Query(queryParam, "")
if date == "" {
return nil, nil
}
t, err := time.Parse("20060102T150405Z", date)
if err != nil {
return nil, response.NewInvalidQueryParamError(c, fmt.Sprintf("INVALID <%s> QUERY PARAMETER", queryParam), nil)
}
return &t, nil
}
func ExtractApps(ctx *fiber.Ctx) ([]string, error) {
apps := ctx.Query("apps")
if apps == "" {
return nil, nil
}
return strings.Split(apps, ","), nil
}
func ExtractIsNotional(ctx *fiber.Ctx) (bool, error) {
by := ctx.Query("by")
if by == "" {
return true, nil
}
if by == "notional" {
return true, nil
}
if by == "tx" {
return false, nil
}
return false, response.NewInvalidQueryParamError(ctx, "INVALID <by> QUERY PARAMETER", nil)
}
// ExtractTopStatisticsTimeSpan parses the `timespan` parameter used on top statistics endpoints.
//
// The endpoints that accept this parameter are:
// * `GET /api/v1/top-assets-by-volume`
// * `GET /api/v1/top-chain-pairs-by-num-transfers`
func ExtractTopStatisticsTimeSpan(ctx *fiber.Ctx) (*transactions.TopStatisticsTimeSpan, error) {
s := ctx.Query("timeSpan")
timeSpan, err := transactions.ParseTopStatisticsTimeSpan(s)
if err != nil {
return nil, response.NewInvalidQueryParamError(ctx, "INVALID <timeSpan> QUERY PARAMETER", nil)
}
return timeSpan, nil
}
func ExtractTimeRange(ctx *fiber.Ctx) (*time.Time, *time.Time, error) {
startTime, err := ExtractTime(ctx, "start_time")
if err != nil {
return nil, nil, err
}
// check if start_time is in the future
if startTime != nil && startTime.After(time.Now()) {
return nil, nil, response.NewInvalidQueryParamError(ctx, "INVALID <start_time> QUERY PARAMETER, CANNOT BE GREATER THAN TODAYS DATE", nil)
}
endTime, err := ExtractTime(ctx, "end_time")
if err != nil {
return nil, nil, err
}
if startTime != nil && endTime != nil {
// check if start_time and end_time are equal
if startTime.Equal(*endTime) {
return nil, nil, response.NewInvalidQueryParamError(ctx, "INVALID <start_time>, <end_time> QUERY PARAMETER, <start_time> CANNOT BE EQUAL TO <end_time>", nil)
}
// check if start_time is greater than end_time
if startTime.After(*endTime) {
return nil, nil, response.NewInvalidQueryParamError(ctx, "INVALID <start_time>, <end_time> QUERY PARAMETER, <start_time> CANNOT BE GREATER THAN <end_time>", nil)
}
}
return startTime, endTime, nil
}