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

208 lines
8.0 KiB
Go

package chains
import (
"context"
"errors"
"fmt"
"math"
"time"
"github.com/wormhole-foundation/wormhole-explorer/txtracker/config"
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
)
var (
ErrChainNotSupported = errors.New("chain id not supported")
ErrTransactionNotFound = errors.New("transaction not found")
)
var (
// rateLimitersByChain maps a chain ID to the request rate limiter for that chain.
rateLimitersByChain map[sdk.ChainID]*time.Ticker
// baseUrlsByChain maps a chain ID to the base URL of the RPC/API service for that chain.
baseUrlsByChain map[sdk.ChainID]string
)
// WARNING: The following chain IDs are not supported by the wormhole-sdk:
const ChainIDOsmosis sdk.ChainID = 20
const ChainIDCosmoshub sdk.ChainID = 4000
const ChainIDEvmos sdk.ChainID = 4001
const ChainIDKujira sdk.ChainID = 4002
type WormchainTxDetail struct {
}
type TxDetail struct {
// From is the address that signed the transaction, encoded in the chain's native format.
From string
// NativeTxHash contains the transaction hash, encoded in the chain's native format.
NativeTxHash string
// Attribute contains the specific information of the transaction.
Attribute *AttributeTxDetail
}
type AttributeTxDetail struct {
Type string
Value any
}
func Initialize(cfg *config.RpcProviderSettings) {
// convertToRateLimiter converts "requests per minute" into the associated *time.Ticker
convertToRateLimiter := func(requestsPerMinute uint16) *time.Ticker {
division := float64(time.Minute) / float64(time.Duration(requestsPerMinute))
roundedUp := math.Ceil(division)
duration := time.Duration(roundedUp)
return time.NewTicker(duration)
}
// Initialize rate limiters for each chain
rateLimitersByChain = make(map[sdk.ChainID]*time.Ticker)
rateLimitersByChain[sdk.ChainIDAcala] = convertToRateLimiter(cfg.AcalaRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDArbitrum] = convertToRateLimiter(cfg.ArbitrumRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDAlgorand] = convertToRateLimiter(cfg.AlgorandRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDAptos] = convertToRateLimiter(cfg.AptosRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDAvalanche] = convertToRateLimiter(cfg.AvalancheRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDBase] = convertToRateLimiter(cfg.BaseRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDBSC] = convertToRateLimiter(cfg.BscRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDCelo] = convertToRateLimiter(cfg.CeloRequestsPerMinute)
rateLimitersByChain[ChainIDCosmoshub] = convertToRateLimiter(cfg.CosmoshubRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDEthereum] = convertToRateLimiter(cfg.EthereumRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDFantom] = convertToRateLimiter(cfg.FantomRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDInjective] = convertToRateLimiter(cfg.InjectiveRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDKarura] = convertToRateLimiter(cfg.KaruraRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDKlaytn] = convertToRateLimiter(cfg.KlaytnRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDMoonbeam] = convertToRateLimiter(cfg.MoonbeamRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDOasis] = convertToRateLimiter(cfg.OasisRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDOptimism] = convertToRateLimiter(cfg.OptimismRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDPolygon] = convertToRateLimiter(cfg.PolygonRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDSolana] = convertToRateLimiter(cfg.SolanaRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDTerra] = convertToRateLimiter(cfg.TerraRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDTerra2] = convertToRateLimiter(cfg.Terra2RequestsPerMinute)
rateLimitersByChain[sdk.ChainIDSui] = convertToRateLimiter(cfg.SuiRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDXpla] = convertToRateLimiter(cfg.XplaRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDWormchain] = convertToRateLimiter(cfg.WormchainRequestsPerMinute)
rateLimitersByChain[ChainIDOsmosis] = convertToRateLimiter(cfg.OsmosisRequestsPerMinute)
rateLimitersByChain[sdk.ChainIDSei] = convertToRateLimiter(cfg.SeiRequestsPerMinute)
// Initialize the RPC base URLs for each chain
baseUrlsByChain = make(map[sdk.ChainID]string)
baseUrlsByChain[sdk.ChainIDAcala] = cfg.AcalaBaseUrl
baseUrlsByChain[sdk.ChainIDArbitrum] = cfg.ArbitrumBaseUrl
baseUrlsByChain[sdk.ChainIDAlgorand] = cfg.AlgorandBaseUrl
baseUrlsByChain[sdk.ChainIDAptos] = cfg.AptosBaseUrl
baseUrlsByChain[sdk.ChainIDAvalanche] = cfg.AvalancheBaseUrl
baseUrlsByChain[sdk.ChainIDBase] = cfg.BaseBaseUrl
baseUrlsByChain[sdk.ChainIDBSC] = cfg.BscBaseUrl
baseUrlsByChain[sdk.ChainIDCelo] = cfg.CeloBaseUrl
baseUrlsByChain[sdk.ChainIDEthereum] = cfg.EthereumBaseUrl
baseUrlsByChain[sdk.ChainIDFantom] = cfg.FantomBaseUrl
baseUrlsByChain[sdk.ChainIDInjective] = cfg.InjectiveBaseUrl
baseUrlsByChain[sdk.ChainIDKarura] = cfg.KaruraBaseUrl
baseUrlsByChain[sdk.ChainIDKlaytn] = cfg.KlaytnBaseUrl
baseUrlsByChain[sdk.ChainIDMoonbeam] = cfg.MoonbeamBaseUrl
baseUrlsByChain[sdk.ChainIDOasis] = cfg.OasisBaseUrl
baseUrlsByChain[sdk.ChainIDOptimism] = cfg.OptimismBaseUrl
baseUrlsByChain[sdk.ChainIDPolygon] = cfg.PolygonBaseUrl
baseUrlsByChain[sdk.ChainIDSolana] = cfg.SolanaBaseUrl
baseUrlsByChain[sdk.ChainIDTerra] = cfg.TerraBaseUrl
baseUrlsByChain[sdk.ChainIDTerra2] = cfg.Terra2BaseUrl
baseUrlsByChain[sdk.ChainIDSui] = cfg.SuiBaseUrl
baseUrlsByChain[sdk.ChainIDXpla] = cfg.XplaBaseUrl
baseUrlsByChain[sdk.ChainIDWormchain] = cfg.WormchainBaseUrl
baseUrlsByChain[sdk.ChainIDSei] = cfg.SeiBaseUrl
}
func FetchTx(
ctx context.Context,
cfg *config.RpcProviderSettings,
chainId sdk.ChainID,
txHash string,
p2pNetwork string,
) (*TxDetail, error) {
// Decide which RPC/API service to use based on chain ID
var fetchFunc func(ctx context.Context, rateLimiter *time.Ticker, baseUrl string, txHash string) (*TxDetail, error)
switch chainId {
case sdk.ChainIDSolana:
fetchFunc = fetchSolanaTx
case sdk.ChainIDAlgorand:
fetchFunc = fetchAlgorandTx
case sdk.ChainIDAptos:
fetchFunc = fetchAptosTx
case sdk.ChainIDSui:
fetchFunc = fetchSuiTx
case sdk.ChainIDInjective,
sdk.ChainIDTerra,
sdk.ChainIDTerra2,
sdk.ChainIDXpla:
fetchFunc = fetchCosmosTx
case sdk.ChainIDAcala,
sdk.ChainIDArbitrum,
sdk.ChainIDAvalanche,
sdk.ChainIDBase,
sdk.ChainIDBSC,
sdk.ChainIDCelo,
sdk.ChainIDEthereum,
sdk.ChainIDFantom,
sdk.ChainIDKarura,
sdk.ChainIDKlaytn,
sdk.ChainIDMoonbeam,
sdk.ChainIDOasis,
sdk.ChainIDOptimism,
sdk.ChainIDPolygon:
fetchFunc = fetchEthTx
case sdk.ChainIDWormchain:
rateLimiter, ok := rateLimitersByChain[ChainIDOsmosis]
if !ok {
return nil, errors.New("found no rate limiter for chain osmosis")
}
apiWormchain := &apiWormchain{
osmosisUrl: cfg.OsmosisBaseUrl,
osmosisRateLimiter: rateLimiter,
evmosUrl: cfg.EvmosBaseUrl,
evmosRateLimiter: rateLimiter,
kujiraUrl: cfg.KujiraBaseUrl,
kujiraRateLimiter: rateLimiter,
cosmoshubUrl: cfg.CosmoshubBaseUrl,
cosmoshubRateLimiter: rateLimiter,
p2pNetwork: p2pNetwork,
}
fetchFunc = apiWormchain.fetchWormchainTx
case sdk.ChainIDSei:
rateLimiter, ok := rateLimitersByChain[sdk.ChainIDWormchain]
if !ok {
return nil, errors.New("found no rate limiter for chain osmosis")
}
apiSei := &apiSei{
wormchainRateLimiter: rateLimiter,
wormchainUrl: cfg.WormchainBaseUrl,
p2pNetwork: p2pNetwork,
}
fetchFunc = apiSei.fetchSeiTx
default:
return nil, ErrChainNotSupported
}
// Get the rate limiter and base URL for the given chain ID
rateLimiter, ok := rateLimitersByChain[chainId]
if !ok {
return nil, fmt.Errorf("found no rate limiter for chain %s", chainId.String())
}
baseUrl, ok := baseUrlsByChain[chainId]
if !ok {
return nil, fmt.Errorf("found no base URL for chain %s", chainId.String())
}
// Get transaction details from the RPC/API service
txDetail, err := fetchFunc(ctx, rateLimiter, baseUrl, txHash)
if err != nil {
return nil, fmt.Errorf("failed to retrieve tx information: %w", err)
}
return txDetail, nil
}