From 4b4b6f61ca76e56c1a1af8ea20955af9c925558e Mon Sep 17 00:00:00 2001 From: agodnic Date: Wed, 21 Jun 2023 15:47:12 -0300 Subject: [PATCH] Add support for cosmos chains in `tx-tracker` (#426) ### Description Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/418 This pull request modifies the `tx-tracker` service to support `ChainIDTerra2` and `ChainIDXpla`. In particular, this will make sender addresses from these blockchains available for the Wormhole Scan UI. Support for `ChainIDTerra` was left out due to lack of working public RPC nodes (`https://lcd.terra.dev` doesn't seem to be functioning correctly at the moment). --- .../tx-tracker-backfiller/env/production.env | 47 +++++++++++- deploy/tx-tracker-backfiller/env/staging.env | 48 +++++++++++- deploy/tx-tracker-backfiller/env/test.env | 47 +++++++++++- .../tx-tracker-backfiller-job.yaml | 8 ++ deploy/tx-tracker/env/production.env | 47 +++++++++++- deploy/tx-tracker/env/staging.env | 48 +++++++++++- deploy/tx-tracker/env/test.env | 49 +++++++++++- deploy/tx-tracker/tx-tracker-service.yaml | 8 ++ tx-tracker/chains/cosmos.go | 76 +++++++++++++++++++ tx-tracker/chains/tx.go | 19 ++++- tx-tracker/config/structs.go | 4 + 11 files changed, 385 insertions(+), 16 deletions(-) create mode 100644 tx-tracker/chains/cosmos.go diff --git a/deploy/tx-tracker-backfiller/env/production.env b/deploy/tx-tracker-backfiller/env/production.env index ce1fb2e0..038f0309 100644 --- a/deploy/tx-tracker-backfiller/env/production.env +++ b/deploy/tx-tracker-backfiller/env/production.env @@ -6,5 +6,48 @@ RESOURCES_LIMITS_MEMORY=256Mi RESOURCES_LIMITS_CPU=500m RESOURCES_REQUESTS_MEMORY=128Mi RESOURCES_REQUESTS_CPU=250m -SOLANA_BASE_URL=https://api.mainnet-beta.solana.com -SOLANA_REQUESTS_PER_MINUTE=6 + +APTOS_BASE_URL= +APTOS_REQUESTS_PER_MINUTE=2 + +ARBITRUM_BASE_URL= +ARBITRUM_REQUESTS_PER_MINUTE=2 + +AVALANCHE_BASE_URL= +AVALANCHE_REQUESTS_PER_MINUTE=2 + +BSC_BASE_URL= +BSC_REQUESTS_PER_MINUTE=2 + +CELO_BASE_URL= +CELO_REQUESTS_PER_MINUTE=2 + +ETHEREUM_BASE_URL= +ETHEREUM_REQUESTS_PER_MINUTE=2 + +FANTOM_BASE_URL= +FANTOM_REQUESTS_PER_MINUTE=2 + +KLAYTN_BASE_URL= +KLAYTN_REQUESTS_PER_MINUTE=2 + +MOONBEAM_BASE_URL= +MOONBEAM_REQUESTS_PER_MINUTE=2 + +OPTIMISM_BASE_URL= +OPTIMISM_REQUESTS_PER_MINUTE=2 + +POLYGON_BASE_URL= +POLYGON_REQUESTS_PER_MINUTE=2 + +SOLANA_BASE_URL= +SOLANA_REQUESTS_PER_MINUTE=2 + +SUI_BASE_URL= +SUI_REQUESTS_PER_MINUTE=2 + +TERRA2_BASE_URL= +TERRA2_REQUESTS_PER_MINUTE=2 + +XPLA_BASE_URL= +XPLA_REQUESTS_PER_MINUTE=2 \ No newline at end of file diff --git a/deploy/tx-tracker-backfiller/env/staging.env b/deploy/tx-tracker-backfiller/env/staging.env index 5d270918..8a17af9d 100644 --- a/deploy/tx-tracker-backfiller/env/staging.env +++ b/deploy/tx-tracker-backfiller/env/staging.env @@ -7,9 +7,51 @@ RESOURCES_LIMITS_CPU=500m RESOURCES_REQUESTS_MEMORY=64Mi RESOURCES_REQUESTS_CPU=250m -SOLANA_BASE_URL=https://api.mainnet-beta.solana.com -SOLANA_REQUESTS_PER_MINUTE=6 - STRATEGY_NAME=time_range STRATEGY_TIMESTAMP_AFTER=2023-01-01T00:00:00.000Z STRATEGY_TIMESTAMP_BEFORE=2023-04-01T00:00:00.000Z + +APTOS_BASE_URL= +APTOS_REQUESTS_PER_MINUTE=1 + +ARBITRUM_BASE_URL= +ARBITRUM_REQUESTS_PER_MINUTE=1 + +AVALANCHE_BASE_URL= +AVALANCHE_REQUESTS_PER_MINUTE=1 + +BSC_BASE_URL= +BSC_REQUESTS_PER_MINUTE=1 + +CELO_BASE_URL= +CELO_REQUESTS_PER_MINUTE=1 + +ETHEREUM_BASE_URL= +ETHEREUM_REQUESTS_PER_MINUTE=1 + +FANTOM_BASE_URL= +FANTOM_REQUESTS_PER_MINUTE=1 + +KLAYTN_BASE_URL= +KLAYTN_REQUESTS_PER_MINUTE=1 + +MOONBEAM_BASE_URL= +MOONBEAM_REQUESTS_PER_MINUTE=1 + +OPTIMISM_BASE_URL= +OPTIMISM_REQUESTS_PER_MINUTE=1 + +POLYGON_BASE_URL= +POLYGON_REQUESTS_PER_MINUTE=1 + +SOLANA_BASE_URL= +SOLANA_REQUESTS_PER_MINUTE=1 + +SUI_BASE_URL= +SUI_REQUESTS_PER_MINUTE=1 + +TERRA2_BASE_URL= +TERRA2_REQUESTS_PER_MINUTE=1 + +XPLA_BASE_URL= +XPLA_REQUESTS_PER_MINUTE=1 \ No newline at end of file diff --git a/deploy/tx-tracker-backfiller/env/test.env b/deploy/tx-tracker-backfiller/env/test.env index c27dd8a7..0cdab705 100644 --- a/deploy/tx-tracker-backfiller/env/test.env +++ b/deploy/tx-tracker-backfiller/env/test.env @@ -6,5 +6,48 @@ RESOURCES_LIMITS_MEMORY=128Mi RESOURCES_LIMITS_CPU=200m RESOURCES_REQUESTS_MEMORY=64Mi RESOURCES_REQUESTS_CPU=100m -SOLANA_BASE_URL=https://api.mainnet-beta.solana.com -SOLANA_REQUESTS_PER_MINUTE=6 + +APTOS_BASE_URL= +APTOS_REQUESTS_PER_MINUTE=1 + +ARBITRUM_BASE_URL= +ARBITRUM_REQUESTS_PER_MINUTE=1 + +AVALANCHE_BASE_URL= +AVALANCHE_REQUESTS_PER_MINUTE=1 + +BSC_BASE_URL= +BSC_REQUESTS_PER_MINUTE=1 + +CELO_BASE_URL= +CELO_REQUESTS_PER_MINUTE=1 + +ETHEREUM_BASE_URL= +ETHEREUM_REQUESTS_PER_MINUTE=1 + +FANTOM_BASE_URL= +FANTOM_REQUESTS_PER_MINUTE=1 + +KLAYTN_BASE_URL= +KLAYTN_REQUESTS_PER_MINUTE=1 + +MOONBEAM_BASE_URL= +MOONBEAM_REQUESTS_PER_MINUTE=1 + +OPTIMISM_BASE_URL= +OPTIMISM_REQUESTS_PER_MINUTE=1 + +POLYGON_BASE_URL= +POLYGON_REQUESTS_PER_MINUTE=1 + +SOLANA_BASE_URL= +SOLANA_REQUESTS_PER_MINUTE=1 + +SUI_BASE_URL= +SUI_REQUESTS_PER_MINUTE=1 + +TERRA2_BASE_URL= +TERRA2_REQUESTS_PER_MINUTE=1 + +XPLA_BASE_URL= +XPLA_REQUESTS_PER_MINUTE=1 diff --git a/deploy/tx-tracker-backfiller/tx-tracker-backfiller-job.yaml b/deploy/tx-tracker-backfiller/tx-tracker-backfiller-job.yaml index fef685fa..58974a5d 100644 --- a/deploy/tx-tracker-backfiller/tx-tracker-backfiller-job.yaml +++ b/deploy/tx-tracker-backfiller/tx-tracker-backfiller-job.yaml @@ -83,6 +83,14 @@ spec: value: {{ .SUI_BASE_URL }} - name: SUI_REQUESTS_PER_MINUTE value: "{{ .SUI_REQUESTS_PER_MINUTE }}" + - name: TERRA2_BASE_URL + value: {{ .TERRA2_BASE_URL }} + - name: TERRA2_REQUESTS_PER_MINUTE + value: "{{ .TERRA2_REQUESTS_PER_MINUTE }}" + - name: XPLA_BASE_URL + value: {{ .XPLA_BASE_URL }} + - name: XPLA_REQUESTS_PER_MINUTE + value: "{{ .XPLA_REQUESTS_PER_MINUTE }}" - name: NUM_WORKERS value: "100" - name: BULK_SIZE diff --git a/deploy/tx-tracker/env/production.env b/deploy/tx-tracker/env/production.env index bd1c176a..473149c5 100644 --- a/deploy/tx-tracker/env/production.env +++ b/deploy/tx-tracker/env/production.env @@ -9,6 +9,49 @@ RESOURCES_REQUESTS_MEMORY=128Mi RESOURCES_REQUESTS_CPU=250m SQS_URL= SQS_AWS_REGION= -SOLANA_BASE_URL=https://api.mainnet-beta.solana.com -SOLANA_REQUESTS_PER_MINUTE=6 AWS_IAM_ROLE= + +APTOS_BASE_URL= +APTOS_REQUESTS_PER_MINUTE=8 + +ARBITRUM_BASE_URL= +ARBITRUM_REQUESTS_PER_MINUTE=8 + +AVALANCHE_BASE_URL= +AVALANCHE_REQUESTS_PER_MINUTE=4 + +BSC_BASE_URL= +BSC_REQUESTS_PER_MINUTE=8 + +CELO_BASE_URL= +CELO_REQUESTS_PER_MINUTE=8 + +ETHEREUM_BASE_URL= +ETHEREUM_REQUESTS_PER_MINUTE=8 + +FANTOM_BASE_URL= +FANTOM_REQUESTS_PER_MINUTE=8 + +KLAYTN_BASE_URL= +KLAYTN_REQUESTS_PER_MINUTE=8 + +MOONBEAM_BASE_URL= +MOONBEAM_REQUESTS_PER_MINUTE=8 + +OPTIMISM_BASE_URL= +OPTIMISM_REQUESTS_PER_MINUTE=8 + +POLYGON_BASE_URL= +POLYGON_REQUESTS_PER_MINUTE=8 + +SOLANA_BASE_URL= +SOLANA_REQUESTS_PER_MINUTE=4 + +SUI_BASE_URL= +SUI_REQUESTS_PER_MINUTE=4 + +TERRA2_BASE_URL= +TERRA2_REQUESTS_PER_MINUTE=4 + +XPLA_BASE_URL= +XPLA_REQUESTS_PER_MINUTE=4 \ No newline at end of file diff --git a/deploy/tx-tracker/env/staging.env b/deploy/tx-tracker/env/staging.env index 0b6ddfc7..ddfddf56 100644 --- a/deploy/tx-tracker/env/staging.env +++ b/deploy/tx-tracker/env/staging.env @@ -9,7 +9,49 @@ RESOURCES_REQUESTS_MEMORY=15Mi RESOURCES_REQUESTS_CPU=40m SQS_URL= SQS_AWS_REGION= +AWS_IAM_ROLE= -SOLANA_BASE_URL=https://api.mainnet-beta.solana.com -SOLANA_REQUESTS_PER_MINUTE=6 -AWS_IAM_ROLE= \ No newline at end of file +APTOS_BASE_URL= +APTOS_REQUESTS_PER_MINUTE=2 + +ARBITRUM_BASE_URL= +ARBITRUM_REQUESTS_PER_MINUTE=2 + +AVALANCHE_BASE_URL= +AVALANCHE_REQUESTS_PER_MINUTE=2 + +BSC_BASE_URL= +BSC_REQUESTS_PER_MINUTE=2 + +CELO_BASE_URL= +CELO_REQUESTS_PER_MINUTE=2 + +ETHEREUM_BASE_URL= +ETHEREUM_REQUESTS_PER_MINUTE=2 + +FANTOM_BASE_URL= +FANTOM_REQUESTS_PER_MINUTE=2 + +KLAYTN_BASE_URL= +KLAYTN_REQUESTS_PER_MINUTE=2 + +MOONBEAM_BASE_URL= +MOONBEAM_REQUESTS_PER_MINUTE=2 + +OPTIMISM_BASE_URL= +OPTIMISM_REQUESTS_PER_MINUTE=2 + +POLYGON_BASE_URL= +POLYGON_REQUESTS_PER_MINUTE=2 + +SOLANA_BASE_URL= +SOLANA_REQUESTS_PER_MINUTE=2 + +SUI_BASE_URL= +SUI_REQUESTS_PER_MINUTE=2 + +TERRA2_BASE_URL= +TERRA2_REQUESTS_PER_MINUTE=2 + +XPLA_BASE_URL= +XPLA_REQUESTS_PER_MINUTE=2 \ No newline at end of file diff --git a/deploy/tx-tracker/env/test.env b/deploy/tx-tracker/env/test.env index 832dd925..700ffb6f 100644 --- a/deploy/tx-tracker/env/test.env +++ b/deploy/tx-tracker/env/test.env @@ -9,6 +9,49 @@ RESOURCES_REQUESTS_MEMORY=15Mi RESOURCES_REQUESTS_CPU=10m SQS_URL= SQS_AWS_REGION= -SOLANA_BASE_URL=https://api.devnet.solana.com -SOLANA_REQUESTS_PER_MINUTE=6 -AWS_IAM_ROLE= \ No newline at end of file +AWS_IAM_ROLE= + +APTOS_BASE_URL= +APTOS_REQUESTS_PER_MINUTE=2 + +ARBITRUM_BASE_URL= +ARBITRUM_REQUESTS_PER_MINUTE=2 + +AVALANCHE_BASE_URL= +AVALANCHE_REQUESTS_PER_MINUTE=2 + +BSC_BASE_URL= +BSC_REQUESTS_PER_MINUTE=2 + +CELO_BASE_URL= +CELO_REQUESTS_PER_MINUTE=2 + +ETHEREUM_BASE_URL= +ETHEREUM_REQUESTS_PER_MINUTE=2 + +FANTOM_BASE_URL= +FANTOM_REQUESTS_PER_MINUTE=2 + +KLAYTN_BASE_URL= +KLAYTN_REQUESTS_PER_MINUTE=2 + +MOONBEAM_BASE_URL= +MOONBEAM_REQUESTS_PER_MINUTE=2 + +OPTIMISM_BASE_URL= +OPTIMISM_REQUESTS_PER_MINUTE=2 + +POLYGON_BASE_URL= +POLYGON_REQUESTS_PER_MINUTE=2 + +SOLANA_BASE_URL= +SOLANA_REQUESTS_PER_MINUTE=2 + +SUI_BASE_URL= +SUI_REQUESTS_PER_MINUTE=2 + +TERRA2_BASE_URL= +TERRA2_REQUESTS_PER_MINUTE=2 + +XPLA_BASE_URL= +XPLA_REQUESTS_PER_MINUTE=2 \ No newline at end of file diff --git a/deploy/tx-tracker/tx-tracker-service.yaml b/deploy/tx-tracker/tx-tracker-service.yaml index 1a45e6cb..b0cfd12d 100644 --- a/deploy/tx-tracker/tx-tracker-service.yaml +++ b/deploy/tx-tracker/tx-tracker-service.yaml @@ -109,6 +109,14 @@ spec: value: {{ .SUI_BASE_URL }} - name: SUI_REQUESTS_PER_MINUTE value: "{{ .SUI_REQUESTS_PER_MINUTE }}" + - name: TERRA2_BASE_URL + value: {{ .TERRA2_BASE_URL }} + - name: TERRA2_REQUESTS_PER_MINUTE + value: "{{ .TERRA2_REQUESTS_PER_MINUTE }}" + - name: XPLA_BASE_URL + value: {{ .XPLA_BASE_URL }} + - name: XPLA_REQUESTS_PER_MINUTE + value: "{{ .XPLA_REQUESTS_PER_MINUTE }}" resources: limits: memory: {{ .RESOURCES_LIMITS_MEMORY }} diff --git a/tx-tracker/chains/cosmos.go b/tx-tracker/chains/cosmos.go new file mode 100644 index 00000000..de283d7f --- /dev/null +++ b/tx-tracker/chains/cosmos.go @@ -0,0 +1,76 @@ +package chains + +import ( + "context" + "encoding/json" + "fmt" + "time" +) + +const ( + cosmosMsgExecuteContract = "/cosmwasm.wasm.v1.MsgExecuteContract" +) + +// cosmosTxsResponse models the response body from `GET /cosmos/tx/v1beta1/txs/{hash}` +type cosmosTxsResponse struct { + TxResponse struct { + Tx struct { + Body struct { + Messages []struct { + Type_ string `json:"@type"` + Sender string `json:"sender"` + } `json:"messages"` + } `json:"body"` + } `json:"tx"` + Timestamp string `json:"timestamp"` + TxHash string `json:"txhash"` + } `json:"tx_response"` +} + +func fetchCosmosTx( + ctx context.Context, + baseUri string, + txHash string, +) (*TxDetail, error) { + + // Query the Cosmos transaction endpoint + uri := fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", baseUri, txHash) + body, err := httpGet(ctx, uri) + if err != nil { + return nil, fmt.Errorf("failed to query cosmos tx endpoint: %w", err) + } + + // Deserialize response body + var response cosmosTxsResponse + if err := json.Unmarshal(body, &response); err != nil { + return nil, fmt.Errorf("failed to deserialize cosmos tx response: %w", err) + } + + // Find the sender address + var sender string + for i := range response.TxResponse.Tx.Body.Messages { + + msg := &response.TxResponse.Tx.Body.Messages[i] + if msg.Type_ == cosmosMsgExecuteContract { + sender = msg.Sender + break + } + } + if sender == "" { + return nil, fmt.Errorf("failed to find sender address in cosmos tx response") + } + + // Parse the timestamp + timestamp, err := time.Parse("2006-01-02T15:04:05Z", response.TxResponse.Timestamp) + if err != nil { + return nil, fmt.Errorf("failed to parse tx timestamp from cosmos tx response: %w", err) + } + + // Build the result object and return + TxDetail := &TxDetail{ + From: sender, + Timestamp: timestamp, + NativeTxHash: response.TxResponse.TxHash, + } + return TxDetail, nil +} diff --git a/tx-tracker/chains/tx.go b/tx-tracker/chains/tx.go index 07cfe352..e813d766 100644 --- a/tx-tracker/chains/tx.go +++ b/tx-tracker/chains/tx.go @@ -45,6 +45,8 @@ var tickers = struct { polygon *time.Ticker solana *time.Ticker sui *time.Ticker + terra2 *time.Ticker + xpla *time.Ticker }{} func Initialize(cfg *config.RpcProviderSettings) { @@ -58,8 +60,10 @@ func Initialize(cfg *config.RpcProviderSettings) { return time.Duration(roundedUp) } - // this adapter sends 1 request per txHash + // these adapters send 1 request per txHash tickers.sui = time.NewTicker(f(cfg.SuiRequestsPerMinute)) + tickers.terra2 = time.NewTicker(f(cfg.Terra2RequestsPerMinute)) + tickers.xpla = time.NewTicker(f(cfg.XplaRequestsPerMinute)) // these adapters send 2 requests per txHash tickers.aptos = time.NewTicker(f(cfg.AptosRequestsPerMinute / 2)) @@ -147,6 +151,16 @@ func FetchTx( case vaa.ChainIDSui: fetchFunc = fetchSuiTx rateLimiter = *tickers.sui + case vaa.ChainIDTerra2: + fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) { + return fetchCosmosTx(ctx, cfg.Terra2BaseUrl, txHash) + } + rateLimiter = *tickers.terra2 + case vaa.ChainIDXpla: + fetchFunc = func(ctx context.Context, cfg *config.RpcProviderSettings, txHash string) (*TxDetail, error) { + return fetchCosmosTx(ctx, cfg.XplaBaseUrl, txHash) + } + rateLimiter = *tickers.xpla default: return nil, ErrChainNotSupported } @@ -203,6 +217,9 @@ func httpGet(ctx context.Context, url string) ([]byte, error) { return nil, fmt.Errorf("failed to query url: %w", err) } defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected HTTP status code: %d", response.StatusCode) + } // Read the response body and return body, err := ioutil.ReadAll(response.Body) diff --git a/tx-tracker/config/structs.go b/tx-tracker/config/structs.go index 3cdbda2a..22fc044a 100644 --- a/tx-tracker/config/structs.go +++ b/tx-tracker/config/structs.go @@ -85,6 +85,10 @@ type RpcProviderSettings struct { SolanaRequestsPerMinute uint16 `split_words:"true" required:"true"` SuiBaseUrl string `split_words:"true" required:"true"` SuiRequestsPerMinute uint16 `split_words:"true" required:"true"` + Terra2BaseUrl string `split_words:"true" required:"true"` + Terra2RequestsPerMinute uint16 `split_words:"true" required:"true"` + XplaBaseUrl string `split_words:"true" required:"true"` + XplaRequestsPerMinute uint16 `split_words:"true" required:"true"` } func LoadFromEnv[T any]() (*T, error) {