wormhole-explorer/tx-tracker/chains/tx.go

104 lines
2.5 KiB
Go
Raw Normal View History

package chains
import (
"context"
"errors"
"fmt"
"math"
"time"
"github.com/wormhole-foundation/wormhole-explorer/txtracker/config"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
)
const requestTimeout = 30 * time.Second
var (
ErrChainNotSupported = errors.New("chain id not supported")
ErrTransactionNotFound = errors.New("transaction not found")
)
type TxDetail struct {
// Signer is the address that signed the transaction, encoded in the chain's native format.
Signer string
// Timestamp indicates the time at which the transaction was confirmed.
Timestamp time.Time
// NativeTxHash contains the transaction hash, encoded in the chain's native format.
NativeTxHash string
}
var tickers = struct {
ankr *time.Ticker
solana *time.Ticker
terra *time.Ticker
}{}
func Initialize(cfg *config.Settings) {
// f converts "requests per minute" into the associated time.Duration
f := func(requestsPerMinute uint16) time.Duration {
division := float64(time.Minute) / float64(time.Duration(requestsPerMinute))
roundedUp := math.Ceil(division)
return time.Duration(roundedUp)
}
tickers.ankr = time.NewTicker(f(cfg.AnkrRequestsPerMinute))
tickers.terra = time.NewTicker(f(cfg.TerraRequestsPerMinute))
// the Solana adapter sends 2 requests per txHash
tickers.solana = time.NewTicker(f(cfg.SolanaRequestsPerMinute / 2))
}
func FetchTx(
ctx context.Context,
cfg *config.Settings,
chainId vaa.ChainID,
txHash string,
) (*TxDetail, error) {
var fetchFunc func(context.Context, *config.Settings, string) (*TxDetail, error)
var rateLimiter time.Ticker
// decide which RPC/API service to use based on chain ID
switch chainId {
case vaa.ChainIDSolana:
fetchFunc = fetchSolanaTx
rateLimiter = *tickers.solana
case vaa.ChainIDTerra:
fetchFunc = fetchTerraTx
rateLimiter = *tickers.terra
// most EVM-compatible chains use the same RPC service
case vaa.ChainIDEthereum,
vaa.ChainIDBSC,
vaa.ChainIDPolygon,
vaa.ChainIDAvalanche,
vaa.ChainIDFantom,
vaa.ChainIDArbitrum,
vaa.ChainIDOptimism:
fetchFunc = ankrFetchTx
rateLimiter = *tickers.ankr
default:
return nil, ErrChainNotSupported
}
// wait for rate limit - fail fast if context was cancelled
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-rateLimiter.C:
}
// get transaction details from the RPC/API service
subContext, cancelFunc := context.WithTimeout(ctx, requestTimeout)
defer cancelFunc()
txDetail, err := fetchFunc(subContext, cfg, txHash)
if err != nil {
return nil, fmt.Errorf("failed to retrieve tx information: %w", err)
}
return txDetail, nil
}