Retrieve sender address for each transaction (#408)
### Summary Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/404 The WormholeScan UI needs to display the sender address for each token bridge VAA. This pull request modifies the `tx-tracker` service to obtain that information for Solana and eight EVM chains. The transaction sender will become accessible through the following endpoints: * `GET /api/v1/global-tx/{chain}/{emitter}/{seq}`: field `originTx.from`. * `GET /api/v1/transactions`: field `originAddress`. In both cases, the field is nullable (i.e.: sometimes it may not be available due to eventual consistency or internal errors)
This commit is contained in:
parent
a0475ab17e
commit
19743322e4
|
@ -79,6 +79,7 @@ type OriginTx struct {
|
||||||
ChainID sdk.ChainID `bson:"chainId" json:"chainId"`
|
ChainID sdk.ChainID `bson:"chainId" json:"chainId"`
|
||||||
TxHash string `bson:"nativeTxHash" json:"txHash"`
|
TxHash string `bson:"nativeTxHash" json:"txHash"`
|
||||||
Timestamp *time.Time `bson:"timestamp" json:"timestamp"`
|
Timestamp *time.Time `bson:"timestamp" json:"timestamp"`
|
||||||
|
From string `bson:"from" json:"from"`
|
||||||
Status string `bson:"status" json:"status"`
|
Status string `bson:"status" json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -421,6 +421,13 @@ func (c *Controller) ListTransactions(ctx *fiber.Ctx) error {
|
||||||
tx.Status = TxStatusOngoing
|
tx.Status = TxStatusOngoing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the origin address, if available
|
||||||
|
if len(queryResult.Transactions[i].GlobalTransations) == 1 &&
|
||||||
|
queryResult.Transactions[i].GlobalTransations[0].OriginTx != nil {
|
||||||
|
|
||||||
|
tx.OriginAddress = queryResult.Transactions[i].GlobalTransations[0].OriginTx.From
|
||||||
|
}
|
||||||
|
|
||||||
response.Transactions = append(response.Transactions, tx)
|
response.Transactions = append(response.Transactions, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ type TransactionOverview struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
TxHash string `json:"txHash,omitempty"`
|
TxHash string `json:"txHash,omitempty"`
|
||||||
|
OriginAddress string `json:"originAddress,omitempty"`
|
||||||
OriginChain sdk.ChainID `json:"originChain"`
|
OriginChain sdk.ChainID `json:"originChain"`
|
||||||
DestinationAddress string `json:"destinationAddress,omitempty"`
|
DestinationAddress string `json:"destinationAddress,omitempty"`
|
||||||
DestinationChain sdk.ChainID `json:"destinationChain,omitempty"`
|
DestinationChain sdk.ChainID `json:"destinationChain,omitempty"`
|
||||||
|
|
|
@ -6,7 +6,5 @@ RESOURCES_LIMITS_MEMORY=256Mi
|
||||||
RESOURCES_LIMITS_CPU=500m
|
RESOURCES_LIMITS_CPU=500m
|
||||||
RESOURCES_REQUESTS_MEMORY=128Mi
|
RESOURCES_REQUESTS_MEMORY=128Mi
|
||||||
RESOURCES_REQUESTS_CPU=250m
|
RESOURCES_REQUESTS_CPU=250m
|
||||||
VAA_PAYLOAD_PARSER_URL=http://wormscan-vaa-payload-parser.wormscan
|
|
||||||
VAA_PAYLOAD_PARSER_TIMEOUT=10
|
|
||||||
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
||||||
SOLANA_REQUESTS_PER_MINUTE=6
|
SOLANA_REQUESTS_PER_MINUTE=6
|
||||||
|
|
|
@ -6,8 +6,6 @@ RESOURCES_LIMITS_MEMORY=128Mi
|
||||||
RESOURCES_LIMITS_CPU=500m
|
RESOURCES_LIMITS_CPU=500m
|
||||||
RESOURCES_REQUESTS_MEMORY=64Mi
|
RESOURCES_REQUESTS_MEMORY=64Mi
|
||||||
RESOURCES_REQUESTS_CPU=250m
|
RESOURCES_REQUESTS_CPU=250m
|
||||||
VAA_PAYLOAD_PARSER_URL=http://wormscan-vaa-payload-parser.wormscan
|
|
||||||
VAA_PAYLOAD_PARSER_TIMEOUT=10
|
|
||||||
|
|
||||||
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
||||||
SOLANA_REQUESTS_PER_MINUTE=6
|
SOLANA_REQUESTS_PER_MINUTE=6
|
||||||
|
|
|
@ -6,7 +6,5 @@ RESOURCES_LIMITS_MEMORY=128Mi
|
||||||
RESOURCES_LIMITS_CPU=200m
|
RESOURCES_LIMITS_CPU=200m
|
||||||
RESOURCES_REQUESTS_MEMORY=64Mi
|
RESOURCES_REQUESTS_MEMORY=64Mi
|
||||||
RESOURCES_REQUESTS_CPU=100m
|
RESOURCES_REQUESTS_CPU=100m
|
||||||
VAA_PAYLOAD_PARSER_URL=http://wormscan-vaa-payload-parser.wormscan-testnet
|
|
||||||
VAA_PAYLOAD_PARSER_TIMEOUT=10
|
|
||||||
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
||||||
SOLANA_REQUESTS_PER_MINUTE=6
|
SOLANA_REQUESTS_PER_MINUTE=6
|
||||||
|
|
|
@ -31,10 +31,38 @@ spec:
|
||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: config
|
name: config
|
||||||
key: mongo-database
|
key: mongo-database
|
||||||
- name: VAA_PAYLOAD_PARSER_URL
|
- name: ARBITRUM_BASE_URL
|
||||||
value: {{ .VAA_PAYLOAD_PARSER_URL }}
|
value: {{ .ARBITRUM_BASE_URL }}
|
||||||
- name: VAA_PAYLOAD_PARSER_TIMEOUT
|
- name: ARBITRUM_REQUESTS_PER_MINUTE
|
||||||
value: "{{ .VAA_PAYLOAD_PARSER_TIMEOUT }}"
|
value: "{{ .ARBITRUM_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: AVALANCHE_BASE_URL
|
||||||
|
value: {{ .AVALANCHE_BASE_URL }}
|
||||||
|
- name: AVALANCHE_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .AVALANCHE_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: BSC_BASE_URL
|
||||||
|
value: {{ .BSC_BASE_URL }}
|
||||||
|
- name: BSC_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .BSC_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: CELO_BASE_URL
|
||||||
|
value: {{ .CELO_BASE_URL }}
|
||||||
|
- name: CELO_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .CELO_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: ETHEREUM_BASE_URL
|
||||||
|
value: {{ .ETHEREUM_BASE_URL }}
|
||||||
|
- name: ETHEREUM_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .ETHEREUM_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: FANTOM_BASE_URL
|
||||||
|
value: {{ .FANTOM_BASE_URL }}
|
||||||
|
- name: FANTOM_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .FANTOM_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: OPTIMISM_BASE_URL
|
||||||
|
value: {{ .OPTIMISM_BASE_URL }}
|
||||||
|
- name: OPTIMISM_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .OPTIMISM_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: POLYGON_BASE_URL
|
||||||
|
value: {{ .POLYGON_BASE_URL }}
|
||||||
|
- name: POLYGON_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .POLYGON_REQUESTS_PER_MINUTE }}"
|
||||||
- name: SOLANA_BASE_URL
|
- name: SOLANA_BASE_URL
|
||||||
value: {{ .SOLANA_BASE_URL }}
|
value: {{ .SOLANA_BASE_URL }}
|
||||||
- name: SOLANA_REQUESTS_PER_MINUTE
|
- name: SOLANA_REQUESTS_PER_MINUTE
|
||||||
|
|
|
@ -9,8 +9,6 @@ RESOURCES_REQUESTS_MEMORY=128Mi
|
||||||
RESOURCES_REQUESTS_CPU=250m
|
RESOURCES_REQUESTS_CPU=250m
|
||||||
SQS_URL=
|
SQS_URL=
|
||||||
SQS_AWS_REGION=
|
SQS_AWS_REGION=
|
||||||
VAA_PAYLOAD_PARSER_URL=http://wormscan-vaa-payload-parser.wormscan
|
|
||||||
VAA_PAYLOAD_PARSER_TIMEOUT=10
|
|
||||||
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
||||||
SOLANA_REQUESTS_PER_MINUTE=6
|
SOLANA_REQUESTS_PER_MINUTE=6
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
||||||
|
|
|
@ -10,9 +10,6 @@ RESOURCES_REQUESTS_CPU=40m
|
||||||
SQS_URL=
|
SQS_URL=
|
||||||
SQS_AWS_REGION=
|
SQS_AWS_REGION=
|
||||||
|
|
||||||
VAA_PAYLOAD_PARSER_URL=http://wormscan-vaa-payload-parser.wormscan
|
|
||||||
VAA_PAYLOAD_PARSER_TIMEOUT=10
|
|
||||||
|
|
||||||
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
SOLANA_BASE_URL=https://api.mainnet-beta.solana.com
|
||||||
SOLANA_REQUESTS_PER_MINUTE=6
|
SOLANA_REQUESTS_PER_MINUTE=6
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
|
@ -9,8 +9,6 @@ RESOURCES_REQUESTS_MEMORY=15Mi
|
||||||
RESOURCES_REQUESTS_CPU=10m
|
RESOURCES_REQUESTS_CPU=10m
|
||||||
SQS_URL=
|
SQS_URL=
|
||||||
SQS_AWS_REGION=
|
SQS_AWS_REGION=
|
||||||
VAA_PAYLOAD_PARSER_URL=http://wormscan-vaa-payload-parser.wormscan-testnet
|
|
||||||
VAA_PAYLOAD_PARSER_TIMEOUT=10
|
|
||||||
SOLANA_BASE_URL=https://api.devnet.solana.com
|
SOLANA_BASE_URL=https://api.devnet.solana.com
|
||||||
SOLANA_REQUESTS_PER_MINUTE=6
|
SOLANA_REQUESTS_PER_MINUTE=6
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
|
@ -57,10 +57,38 @@ spec:
|
||||||
value: {{ .SQS_URL }}
|
value: {{ .SQS_URL }}
|
||||||
- name: AWS_REGION
|
- name: AWS_REGION
|
||||||
value: {{ .SQS_AWS_REGION }}
|
value: {{ .SQS_AWS_REGION }}
|
||||||
- name: VAA_PAYLOAD_PARSER_URL
|
- name: ARBITRUM_BASE_URL
|
||||||
value: {{ .VAA_PAYLOAD_PARSER_URL }}
|
value: {{ .ARBITRUM_BASE_URL }}
|
||||||
- name: VAA_PAYLOAD_PARSER_TIMEOUT
|
- name: ARBITRUM_REQUESTS_PER_MINUTE
|
||||||
value: "{{ .VAA_PAYLOAD_PARSER_TIMEOUT }}"
|
value: "{{ .ARBITRUM_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: AVALANCHE_BASE_URL
|
||||||
|
value: {{ .AVALANCHE_BASE_URL }}
|
||||||
|
- name: AVALANCHE_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .AVALANCHE_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: BSC_BASE_URL
|
||||||
|
value: {{ .BSC_BASE_URL }}
|
||||||
|
- name: BSC_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .BSC_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: CELO_BASE_URL
|
||||||
|
value: {{ .CELO_BASE_URL }}
|
||||||
|
- name: CELO_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .CELO_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: ETHEREUM_BASE_URL
|
||||||
|
value: {{ .ETHEREUM_BASE_URL }}
|
||||||
|
- name: ETHEREUM_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .ETHEREUM_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: FANTOM_BASE_URL
|
||||||
|
value: {{ .FANTOM_BASE_URL }}
|
||||||
|
- name: FANTOM_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .FANTOM_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: OPTIMISM_BASE_URL
|
||||||
|
value: {{ .OPTIMISM_BASE_URL }}
|
||||||
|
- name: OPTIMISM_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .OPTIMISM_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: POLYGON_BASE_URL
|
||||||
|
value: {{ .POLYGON_BASE_URL }}
|
||||||
|
- name: POLYGON_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .POLYGON_REQUESTS_PER_MINUTE }}"
|
||||||
- name: SOLANA_BASE_URL
|
- name: SOLANA_BASE_URL
|
||||||
value: {{ .SOLANA_BASE_URL }}
|
value: {{ .SOLANA_BASE_URL }}
|
||||||
- name: SOLANA_REQUESTS_PER_MINUTE
|
- name: SOLANA_REQUESTS_PER_MINUTE
|
||||||
|
|
|
@ -24,17 +24,10 @@ func fetchEthTx(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
txHash string,
|
txHash string,
|
||||||
baseUrl string,
|
baseUrl string,
|
||||||
apiKey string,
|
|
||||||
) (*TxDetail, error) {
|
) (*TxDetail, error) {
|
||||||
|
|
||||||
// build RPC URL
|
|
||||||
url := baseUrl
|
|
||||||
if apiKey != "" {
|
|
||||||
url += "/" + apiKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize RPC client
|
// initialize RPC client
|
||||||
client, err := rpc.DialContext(ctx, url)
|
client, err := rpc.DialContext(ctx, baseUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize RPC client: %w", err)
|
return nil, fmt.Errorf("failed to initialize RPC client: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -69,7 +62,7 @@ func fetchEthTx(
|
||||||
|
|
||||||
// build results and return
|
// build results and return
|
||||||
txDetail := &TxDetail{
|
txDetail := &TxDetail{
|
||||||
Signer: strings.ToLower(txReply.From),
|
From: strings.ToLower(txReply.From),
|
||||||
Timestamp: timestamp,
|
Timestamp: timestamp,
|
||||||
NativeTxHash: fmt.Sprintf("0x%s", strings.ToLower(txHash)),
|
NativeTxHash: fmt.Sprintf("0x%s", strings.ToLower(txHash)),
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,10 +116,10 @@ func fetchSolanaTx(
|
||||||
// set sender/receiver
|
// set sender/receiver
|
||||||
for i := range response.Transaction.Message.AccountKeys {
|
for i := range response.Transaction.Message.AccountKeys {
|
||||||
if response.Transaction.Message.AccountKeys[i].Signer {
|
if response.Transaction.Message.AccountKeys[i].Signer {
|
||||||
txDetail.Signer = response.Transaction.Message.AccountKeys[i].Pubkey
|
txDetail.From = response.Transaction.Message.AccountKeys[i].Pubkey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if txDetail.Signer == "" {
|
if txDetail.From == "" {
|
||||||
return nil, fmt.Errorf("failed to find source account")
|
return nil, fmt.Errorf("failed to find source account")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type TxDetail struct {
|
type TxDetail struct {
|
||||||
// Signer is the address that signed the transaction, encoded in the chain's native format.
|
// From is the address that signed the transaction, encoded in the chain's native format.
|
||||||
Signer string
|
From string
|
||||||
// Timestamp indicates the time at which the transaction was confirmed.
|
// Timestamp indicates the time at which the transaction was confirmed.
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
// NativeTxHash contains the transaction hash, encoded in the chain's native format.
|
// NativeTxHash contains the transaction hash, encoded in the chain's native format.
|
||||||
|
@ -30,6 +30,14 @@ type TxDetail struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var tickers = struct {
|
var tickers = struct {
|
||||||
|
arbitrum *time.Ticker
|
||||||
|
avalanche *time.Ticker
|
||||||
|
bsc *time.Ticker
|
||||||
|
celo *time.Ticker
|
||||||
|
ethereum *time.Ticker
|
||||||
|
fantom *time.Ticker
|
||||||
|
optimism *time.Ticker
|
||||||
|
polygon *time.Ticker
|
||||||
solana *time.Ticker
|
solana *time.Ticker
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
|
@ -44,7 +52,15 @@ func Initialize(cfg *config.RpcProviderSettings) {
|
||||||
return time.Duration(roundedUp)
|
return time.Duration(roundedUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this adapter sends 2 requests per txHash
|
// these adapters send 2 requests per txHash
|
||||||
|
tickers.arbitrum = time.NewTicker(f(cfg.ArbitrumRequestsPerMinute / 2))
|
||||||
|
tickers.avalanche = time.NewTicker(f(cfg.AvalancheRequestsPerMinute / 2))
|
||||||
|
tickers.bsc = time.NewTicker(f(cfg.BscRequestsPerMinute / 2))
|
||||||
|
tickers.celo = time.NewTicker(f(cfg.CeloRequestsPerMinute / 2))
|
||||||
|
tickers.ethereum = time.NewTicker(f(cfg.EthereumRequestsPerMinute / 2))
|
||||||
|
tickers.fantom = time.NewTicker(f(cfg.FantomRequestsPerMinute / 2))
|
||||||
|
tickers.optimism = time.NewTicker(f(cfg.OptimismRequestsPerMinute / 2))
|
||||||
|
tickers.polygon = time.NewTicker(f(cfg.PolygonRequestsPerMinute / 2))
|
||||||
tickers.solana = time.NewTicker(f(cfg.SolanaRequestsPerMinute / 2))
|
tickers.solana = time.NewTicker(f(cfg.SolanaRequestsPerMinute / 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +79,46 @@ func FetchTx(
|
||||||
case vaa.ChainIDSolana:
|
case vaa.ChainIDSolana:
|
||||||
fetchFunc = fetchSolanaTx
|
fetchFunc = fetchSolanaTx
|
||||||
rateLimiter = *tickers.solana
|
rateLimiter = *tickers.solana
|
||||||
|
case vaa.ChainIDCelo:
|
||||||
|
fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) {
|
||||||
|
return fetchEthTx(ctx, txHash, cfg.CeloBaseUrl)
|
||||||
|
}
|
||||||
|
rateLimiter = *tickers.celo
|
||||||
|
case vaa.ChainIDEthereum:
|
||||||
|
fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) {
|
||||||
|
return fetchEthTx(ctx, txHash, cfg.EthereumBaseUrl)
|
||||||
|
}
|
||||||
|
rateLimiter = *tickers.ethereum
|
||||||
|
case vaa.ChainIDBSC:
|
||||||
|
fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) {
|
||||||
|
return fetchEthTx(ctx, txHash, cfg.BscBaseUrl)
|
||||||
|
}
|
||||||
|
rateLimiter = *tickers.bsc
|
||||||
|
case vaa.ChainIDPolygon:
|
||||||
|
fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) {
|
||||||
|
return fetchEthTx(ctx, txHash, cfg.PolygonBaseUrl)
|
||||||
|
}
|
||||||
|
rateLimiter = *tickers.polygon
|
||||||
|
case vaa.ChainIDFantom:
|
||||||
|
fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) {
|
||||||
|
return fetchEthTx(ctx, txHash, cfg.FantomBaseUrl)
|
||||||
|
}
|
||||||
|
rateLimiter = *tickers.fantom
|
||||||
|
case vaa.ChainIDArbitrum:
|
||||||
|
fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) {
|
||||||
|
return fetchEthTx(ctx, txHash, cfg.ArbitrumBaseUrl)
|
||||||
|
}
|
||||||
|
rateLimiter = *tickers.arbitrum
|
||||||
|
case vaa.ChainIDOptimism:
|
||||||
|
fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) {
|
||||||
|
return fetchEthTx(ctx, txHash, cfg.OptimismBaseUrl)
|
||||||
|
}
|
||||||
|
rateLimiter = *tickers.optimism
|
||||||
|
case vaa.ChainIDAvalanche:
|
||||||
|
fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) {
|
||||||
|
return fetchEthTx(ctx, txHash, cfg.AvalancheBaseUrl)
|
||||||
|
}
|
||||||
|
rateLimiter = *tickers.avalanche
|
||||||
default:
|
default:
|
||||||
return nil, ErrChainNotSupported
|
return nil, ErrChainNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,6 @@ func main() {
|
||||||
name := fmt.Sprintf("worker-%d", i)
|
name := fmt.Sprintf("worker-%d", i)
|
||||||
p := consumerParams{
|
p := consumerParams{
|
||||||
logger: makeLogger(rootLogger, name),
|
logger: makeLogger(rootLogger, name),
|
||||||
vaaPayloadParserSettings: &cfg.VaaPayloadParserSettings,
|
|
||||||
rpcProviderSettings: &cfg.RpcProviderSettings,
|
rpcProviderSettings: &cfg.RpcProviderSettings,
|
||||||
repository: repository,
|
repository: repository,
|
||||||
queueRx: queue,
|
queueRx: queue,
|
||||||
|
@ -234,7 +233,6 @@ func produce(ctx context.Context, params *producerParams) {
|
||||||
// consumerParams contains the parameters for the consumer goroutine.
|
// consumerParams contains the parameters for the consumer goroutine.
|
||||||
type consumerParams struct {
|
type consumerParams struct {
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
vaaPayloadParserSettings *config.VaaPayloadParserSettings
|
|
||||||
rpcProviderSettings *config.RpcProviderSettings
|
rpcProviderSettings *config.RpcProviderSettings
|
||||||
repository *consumer.Repository
|
repository *consumer.Repository
|
||||||
queueRx <-chan consumer.GlobalTransaction
|
queueRx <-chan consumer.GlobalTransaction
|
||||||
|
@ -252,18 +250,12 @@ type consumerParams struct {
|
||||||
func consume(ctx context.Context, params *consumerParams) {
|
func consume(ctx context.Context, params *consumerParams) {
|
||||||
|
|
||||||
// Initialize the client, which processes source Txs.
|
// Initialize the client, which processes source Txs.
|
||||||
client, err := consumer.New(
|
client := consumer.New(
|
||||||
nil,
|
nil,
|
||||||
params.vaaPayloadParserSettings,
|
|
||||||
params.rpcProviderSettings,
|
params.rpcProviderSettings,
|
||||||
params.logger,
|
params.logger,
|
||||||
params.repository,
|
params.repository,
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
params.logger.Error("Failed to initialize consumer", zap.Error(err))
|
|
||||||
params.wg.Done()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main loop: fetch global txs and process them
|
// Main loop: fetch global txs and process them
|
||||||
for {
|
for {
|
||||||
|
@ -314,7 +306,7 @@ func consume(ctx context.Context, params *consumerParams) {
|
||||||
Sequence: v.Sequence,
|
Sequence: v.Sequence,
|
||||||
TxHash: *v.TxHash,
|
TxHash: *v.TxHash,
|
||||||
}
|
}
|
||||||
err = client.ProcessSourceTx(ctx, &p)
|
err := client.ProcessSourceTx(ctx, &p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
params.logger.Error("Failed to track source tx",
|
params.logger.Error("Failed to track source tx",
|
||||||
zap.String("vaaId", globalTx.Id),
|
zap.String("vaaId", globalTx.Id),
|
||||||
|
|
|
@ -38,5 +38,5 @@ func main() {
|
||||||
|
|
||||||
// print tx details
|
// print tx details
|
||||||
log.Printf("tx detail: sender=%s nativeTxHash=%s timestamp=%s",
|
log.Printf("tx detail: sender=%s nativeTxHash=%s timestamp=%s",
|
||||||
txDetail.Signer, txDetail.NativeTxHash, txDetail.Timestamp)
|
txDetail.From, txDetail.NativeTxHash, txDetail.Timestamp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,10 +65,7 @@ func main() {
|
||||||
// create and start a consumer.
|
// create and start a consumer.
|
||||||
vaaConsumeFunc := newVAAConsumeFunc(rootCtx, cfg, logger)
|
vaaConsumeFunc := newVAAConsumeFunc(rootCtx, cfg, logger)
|
||||||
repository := consumer.NewRepository(logger, db)
|
repository := consumer.NewRepository(logger, db)
|
||||||
consumer, err := consumer.New(vaaConsumeFunc, &cfg.VaaPayloadParserSettings, &cfg.RpcProviderSettings, logger, repository)
|
consumer := consumer.New(vaaConsumeFunc, &cfg.RpcProviderSettings, logger, repository)
|
||||||
if err != nil {
|
|
||||||
logger.Fatal("Failed to create VAA consumer", zap.Error(err))
|
|
||||||
}
|
|
||||||
consumer.Start(rootCtx)
|
consumer.Start(rootCtx)
|
||||||
|
|
||||||
logger.Info("Started wormhole-explorer-tx-tracker")
|
logger.Info("Started wormhole-explorer-tx-tracker")
|
||||||
|
|
|
@ -30,7 +30,6 @@ type BackfillerSettings struct {
|
||||||
TimestampBefore string `split_words:"true" required:"false"`
|
TimestampBefore string `split_words:"true" required:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
VaaPayloadParserSettings
|
|
||||||
MongodbSettings
|
MongodbSettings
|
||||||
RpcProviderSettings
|
RpcProviderSettings
|
||||||
}
|
}
|
||||||
|
@ -42,16 +41,10 @@ type ServiceSettings struct {
|
||||||
PprofEnabled bool `split_words:"true" default:"false"`
|
PprofEnabled bool `split_words:"true" default:"false"`
|
||||||
|
|
||||||
AwsSettings
|
AwsSettings
|
||||||
VaaPayloadParserSettings
|
|
||||||
MongodbSettings
|
MongodbSettings
|
||||||
RpcProviderSettings
|
RpcProviderSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
type VaaPayloadParserSettings struct {
|
|
||||||
VaaPayloadParserUrl string `split_words:"true" required:"true"`
|
|
||||||
VaaPayloadParserTimeout int64 `split_words:"true" required:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AwsSettings struct {
|
type AwsSettings struct {
|
||||||
AwsEndpoint string `split_words:"true" required:"false"`
|
AwsEndpoint string `split_words:"true" required:"false"`
|
||||||
AwsAccessKeyID string `split_words:"true" required:"false"`
|
AwsAccessKeyID string `split_words:"true" required:"false"`
|
||||||
|
@ -66,6 +59,22 @@ type MongodbSettings struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RpcProviderSettings struct {
|
type RpcProviderSettings struct {
|
||||||
|
ArbitrumBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
ArbitrumRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
|
AvalancheBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
AvalancheRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
|
BscBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
BscRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
|
CeloBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
CeloRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
|
EthereumBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
EthereumRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
|
FantomBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
FantomRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
|
OptimismBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
OptimismRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
|
PolygonBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
PolygonRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
SolanaBaseUrl string `split_words:"true" required:"true"`
|
SolanaBaseUrl string `split_words:"true" required:"true"`
|
||||||
SolanaRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
SolanaRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,9 @@ package consumer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/parser/parser"
|
|
||||||
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/txtracker/chains"
|
"github.com/wormhole-foundation/wormhole-explorer/txtracker/chains"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/txtracker/config"
|
"github.com/wormhole-foundation/wormhole-explorer/txtracker/config"
|
||||||
|
@ -26,36 +24,24 @@ type Consumer struct {
|
||||||
rpcServiceProviderSettings *config.RpcProviderSettings
|
rpcServiceProviderSettings *config.RpcProviderSettings
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
repository *Repository
|
repository *Repository
|
||||||
vaaPayloadParser parser.ParserVAAAPIClient
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new vaa consumer.
|
// New creates a new vaa consumer.
|
||||||
func New(
|
func New(
|
||||||
consumeFunc queue.VAAConsumeFunc,
|
consumeFunc queue.VAAConsumeFunc,
|
||||||
vaaPayloadParserSettings *config.VaaPayloadParserSettings,
|
|
||||||
rpcServiceProviderSettings *config.RpcProviderSettings,
|
rpcServiceProviderSettings *config.RpcProviderSettings,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
repository *Repository,
|
repository *Repository,
|
||||||
) (*Consumer, error) {
|
) *Consumer {
|
||||||
|
|
||||||
vaaPayloadParser, err := parser.NewParserVAAAPIClient(
|
|
||||||
vaaPayloadParserSettings.VaaPayloadParserTimeout,
|
|
||||||
vaaPayloadParserSettings.VaaPayloadParserUrl,
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create VAA parser client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := Consumer{
|
c := Consumer{
|
||||||
consumeFunc: consumeFunc,
|
consumeFunc: consumeFunc,
|
||||||
rpcServiceProviderSettings: rpcServiceProviderSettings,
|
rpcServiceProviderSettings: rpcServiceProviderSettings,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
repository: repository,
|
repository: repository,
|
||||||
vaaPayloadParser: vaaPayloadParser,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &c, nil
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start consumes messages from VAA queue, parse and store those messages in a repository.
|
// Start consumes messages from VAA queue, parse and store those messages in a repository.
|
||||||
|
@ -77,38 +63,6 @@ func (c *Consumer) Start(ctx context.Context) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the VAA's payload
|
|
||||||
parsedPayload, err := c.vaaPayloadParser.Parse(
|
|
||||||
uint16(event.ChainID),
|
|
||||||
event.EmitterAddress,
|
|
||||||
event.Sequence,
|
|
||||||
event.Vaa,
|
|
||||||
)
|
|
||||||
if err == parser.ErrNotFound {
|
|
||||||
c.logger.Debug("Skipping message - no parsed registered for this (chain, emitter) pair",
|
|
||||||
zap.String("vaaId", event.ID),
|
|
||||||
)
|
|
||||||
msg.Done()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Error("Failed to parse VAA payload",
|
|
||||||
zap.String("vaaId", event.ID),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
msg.Done()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip messages that have not been generated by the portal token bridge
|
|
||||||
if parsedPayload.AppID != domain.AppIdPortalTokenBridge {
|
|
||||||
c.logger.Debug("Skipping VAA because it was not generated by the portal token bridge",
|
|
||||||
zap.String("vaaId", event.ID),
|
|
||||||
)
|
|
||||||
msg.Done()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch tx details from the corresponding RPC/API, then persist them on MongoDB.
|
// Fetch tx details from the corresponding RPC/API, then persist them on MongoDB.
|
||||||
p := ProcessSourceTxParams{
|
p := ProcessSourceTxParams{
|
||||||
VaaId: event.ID,
|
VaaId: event.ID,
|
||||||
|
@ -117,7 +71,7 @@ func (c *Consumer) Start(ctx context.Context) {
|
||||||
Sequence: event.Sequence,
|
Sequence: event.Sequence,
|
||||||
TxHash: event.TxHash,
|
TxHash: event.TxHash,
|
||||||
}
|
}
|
||||||
err = c.ProcessSourceTx(ctx, &p)
|
err := c.ProcessSourceTx(ctx, &p)
|
||||||
if err == chains.ErrChainNotSupported {
|
if err == chains.ErrChainNotSupported {
|
||||||
c.logger.Debug("Skipping VAA - chain not supported",
|
c.logger.Debug("Skipping VAA - chain not supported",
|
||||||
zap.String("vaaId", event.ID),
|
zap.String("vaaId", event.ID),
|
||||||
|
|
|
@ -53,6 +53,7 @@ func (r *Repository) UpsertDocument(ctx context.Context, params *UpsertDocumentP
|
||||||
|
|
||||||
if params.TxDetail != nil {
|
if params.TxDetail != nil {
|
||||||
fields = append(fields, primitive.E{Key: "nativeTxHash", Value: params.TxDetail.NativeTxHash})
|
fields = append(fields, primitive.E{Key: "nativeTxHash", Value: params.TxDetail.NativeTxHash})
|
||||||
|
fields = append(fields, primitive.E{Key: "from", Value: params.TxDetail.From})
|
||||||
}
|
}
|
||||||
|
|
||||||
update := bson.D{
|
update := bson.D{
|
||||||
|
|
Loading…
Reference in New Issue