Retrieve chain and sender of wormchain originated vaas (#678)
* Add additional information for osmosis transaction through wormchain Co-authored-by: walker-16 <agpazos85@gmail.com> * Modify tx-tracker deployment Co-authored-by: walker-16 <agpazos85@gmail.com> --------- Co-authored-by: walker-16 <agpazos85@gmail.com>
This commit is contained in:
parent
a525a1f686
commit
5e3adeb4ad
|
@ -74,14 +74,21 @@ type GlobalTransactionDoc struct {
|
||||||
DestinationTx *DestinationTx `bson:"destinationTx" json:"destinationTx"`
|
DestinationTx *DestinationTx `bson:"destinationTx" json:"destinationTx"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OriginTx representa a origin transaction.
|
// OriginTx represents a origin transaction.
|
||||||
type OriginTx struct {
|
type OriginTx struct {
|
||||||
TxHash string `bson:"nativeTxHash" json:"txHash"`
|
TxHash string `bson:"nativeTxHash" json:"txHash"`
|
||||||
From string `bson:"from" json:"from"`
|
From string `bson:"from" json:"from"`
|
||||||
Status string `bson:"status" json:"status"`
|
Status string `bson:"status" json:"status"`
|
||||||
|
Attribute *AttributeDoc `bson:"attribute" json:"attribute"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DestinationTx representa a destination transaction.
|
// AttributeDoc represents a custom attribute for a origin transaction.
|
||||||
|
type AttributeDoc struct {
|
||||||
|
Type string `bson:"type" json:"type"`
|
||||||
|
Value map[string]any `bson:"value" json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestinationTx represents a destination transaction.
|
||||||
type DestinationTx struct {
|
type DestinationTx struct {
|
||||||
ChainID sdk.ChainID `bson:"chainId" json:"chainId"`
|
ChainID sdk.ChainID `bson:"chainId" json:"chainId"`
|
||||||
Status string `bson:"status" json:"status"`
|
Status string `bson:"status" json:"status"`
|
||||||
|
|
|
@ -9,6 +9,7 @@ RESOURCES_REQUESTS_MEMORY=128Mi
|
||||||
RESOURCES_REQUESTS_CPU=250m
|
RESOURCES_REQUESTS_CPU=250m
|
||||||
SQS_URL=
|
SQS_URL=
|
||||||
SQS_AWS_REGION=
|
SQS_AWS_REGION=
|
||||||
|
P2P_NETWORK=mainnet
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
|
|
||||||
|
@ -62,6 +63,9 @@ OASIS_REQUESTS_PER_MINUTE=12
|
||||||
OPTIMISM_BASE_URL=https://rpc.ankr.com/optimism
|
OPTIMISM_BASE_URL=https://rpc.ankr.com/optimism
|
||||||
OPTIMISM_REQUESTS_PER_MINUTE=12
|
OPTIMISM_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
OSMOSIS_BASE_URL=https://rpc.osmosis.zone
|
||||||
|
OSMOSIS_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
POLYGON_BASE_URL=https://rpc.ankr.com/polygon
|
POLYGON_BASE_URL=https://rpc.ankr.com/polygon
|
||||||
POLYGON_REQUESTS_PER_MINUTE=12
|
POLYGON_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
@ -77,5 +81,8 @@ TERRA_REQUESTS_PER_MINUTE=12
|
||||||
TERRA2_BASE_URL=https://phoenix-lcd.terra.dev
|
TERRA2_BASE_URL=https://phoenix-lcd.terra.dev
|
||||||
TERRA2_REQUESTS_PER_MINUTE=12
|
TERRA2_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
WORMCHAIN_BASE_URL=https://wormchain.jumpisolated.com
|
||||||
|
WORMCHAIN_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
XPLA_BASE_URL=https://dimension-lcd.xpla.dev
|
XPLA_BASE_URL=https://dimension-lcd.xpla.dev
|
||||||
XPLA_REQUESTS_PER_MINUTE=12
|
XPLA_REQUESTS_PER_MINUTE=12
|
|
@ -9,6 +9,7 @@ RESOURCES_REQUESTS_MEMORY=15Mi
|
||||||
RESOURCES_REQUESTS_CPU=10m
|
RESOURCES_REQUESTS_CPU=10m
|
||||||
SQS_URL=
|
SQS_URL=
|
||||||
SQS_AWS_REGION=
|
SQS_AWS_REGION=
|
||||||
|
P2P_NETWORK=testnet
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
|
|
||||||
|
@ -60,6 +61,9 @@ OASIS_REQUESTS_PER_MINUTE=12
|
||||||
OPTIMISM_BASE_URL=https://goerli.optimism.io
|
OPTIMISM_BASE_URL=https://goerli.optimism.io
|
||||||
OPTIMISM_REQUESTS_PER_MINUTE=12
|
OPTIMISM_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
OSMOSIS_BASE_URL=https://rpc.testnet.osmosis.zone
|
||||||
|
OSMOSIS_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
POLYGON_BASE_URL=https://rpc.ankr.com/polygon_mumbai
|
POLYGON_BASE_URL=https://rpc.ankr.com/polygon_mumbai
|
||||||
POLYGON_REQUESTS_PER_MINUTE=12
|
POLYGON_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
@ -75,5 +79,8 @@ TERRA_REQUESTS_PER_MINUTE=12
|
||||||
TERRA2_BASE_URL=https://pisco-lcd.terra.dev
|
TERRA2_BASE_URL=https://pisco-lcd.terra.dev
|
||||||
TERRA2_REQUESTS_PER_MINUTE=12
|
TERRA2_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
WORMCHAIN_BASE_URL=https://wormchain-testnet.jumpisolated.com
|
||||||
|
WORMCHAIN_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
XPLA_BASE_URL=https://cube-lcd.xpla.dev:443
|
XPLA_BASE_URL=https://cube-lcd.xpla.dev:443
|
||||||
XPLA_REQUESTS_PER_MINUTE=12
|
XPLA_REQUESTS_PER_MINUTE=12
|
|
@ -9,6 +9,7 @@ RESOURCES_REQUESTS_MEMORY=15Mi
|
||||||
RESOURCES_REQUESTS_CPU=40m
|
RESOURCES_REQUESTS_CPU=40m
|
||||||
SQS_URL=
|
SQS_URL=
|
||||||
SQS_AWS_REGION=
|
SQS_AWS_REGION=
|
||||||
|
P2P_NETWORK=mainnet
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
|
|
||||||
|
@ -62,6 +63,9 @@ OASIS_REQUESTS_PER_MINUTE=12
|
||||||
OPTIMISM_BASE_URL=https://rpc.ankr.com/optimism
|
OPTIMISM_BASE_URL=https://rpc.ankr.com/optimism
|
||||||
OPTIMISM_REQUESTS_PER_MINUTE=12
|
OPTIMISM_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
OSMOSIS_BASE_URL=https://rpc.osmosis.zone
|
||||||
|
OSMOSIS_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
POLYGON_BASE_URL=https://rpc.ankr.com/polygon
|
POLYGON_BASE_URL=https://rpc.ankr.com/polygon
|
||||||
POLYGON_REQUESTS_PER_MINUTE=12
|
POLYGON_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
@ -77,5 +81,8 @@ TERRA_REQUESTS_PER_MINUTE=12
|
||||||
TERRA2_BASE_URL=https://phoenix-lcd.terra.dev
|
TERRA2_BASE_URL=https://phoenix-lcd.terra.dev
|
||||||
TERRA2_REQUESTS_PER_MINUTE=12
|
TERRA2_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
WORMCHAIN_BASE_URL=https://wormchain.jumpisolated.com
|
||||||
|
WORMCHAIN_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
XPLA_BASE_URL=https://dimension-lcd.xpla.dev
|
XPLA_BASE_URL=https://dimension-lcd.xpla.dev
|
||||||
XPLA_REQUESTS_PER_MINUTE=12
|
XPLA_REQUESTS_PER_MINUTE=12
|
|
@ -9,6 +9,7 @@ RESOURCES_REQUESTS_MEMORY=15Mi
|
||||||
RESOURCES_REQUESTS_CPU=10m
|
RESOURCES_REQUESTS_CPU=10m
|
||||||
SQS_URL=
|
SQS_URL=
|
||||||
SQS_AWS_REGION=
|
SQS_AWS_REGION=
|
||||||
|
P2P_NETWORK=testnet
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
|
|
||||||
|
@ -60,6 +61,9 @@ OASIS_REQUESTS_PER_MINUTE=12
|
||||||
OPTIMISM_BASE_URL=https://goerli.optimism.io
|
OPTIMISM_BASE_URL=https://goerli.optimism.io
|
||||||
OPTIMISM_REQUESTS_PER_MINUTE=12
|
OPTIMISM_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
OSMOSIS_BASE_URL=https://rpc.testnet.osmosis.zone
|
||||||
|
OSMOSIS_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
POLYGON_BASE_URL=https://rpc.ankr.com/polygon_mumbai
|
POLYGON_BASE_URL=https://rpc.ankr.com/polygon_mumbai
|
||||||
POLYGON_REQUESTS_PER_MINUTE=12
|
POLYGON_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
@ -75,5 +79,8 @@ TERRA_REQUESTS_PER_MINUTE=12
|
||||||
TERRA2_BASE_URL=https://pisco-lcd.terra.dev
|
TERRA2_BASE_URL=https://pisco-lcd.terra.dev
|
||||||
TERRA2_REQUESTS_PER_MINUTE=12
|
TERRA2_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
|
WORMCHAIN_BASE_URL=https://wormchain-testnet.jumpisolated.com
|
||||||
|
WORMCHAIN_REQUESTS_PER_MINUTE=12
|
||||||
|
|
||||||
XPLA_BASE_URL=https://cube-lcd.xpla.dev:443
|
XPLA_BASE_URL=https://cube-lcd.xpla.dev:443
|
||||||
XPLA_REQUESTS_PER_MINUTE=12
|
XPLA_REQUESTS_PER_MINUTE=12
|
|
@ -60,6 +60,8 @@ spec:
|
||||||
value: {{ .SQS_URL }}
|
value: {{ .SQS_URL }}
|
||||||
- name: AWS_REGION
|
- name: AWS_REGION
|
||||||
value: {{ .SQS_AWS_REGION }}
|
value: {{ .SQS_AWS_REGION }}
|
||||||
|
- name: P2P_NETWORK
|
||||||
|
value: {{ .P2P_NETWORK }}
|
||||||
- name: METRICS_ENABLED
|
- name: METRICS_ENABLED
|
||||||
value: "{{ .METRICS_ENABLED }}"
|
value: "{{ .METRICS_ENABLED }}"
|
||||||
- name: ACALA_BASE_URL
|
- name: ACALA_BASE_URL
|
||||||
|
@ -126,6 +128,10 @@ spec:
|
||||||
value: {{ .OPTIMISM_BASE_URL }}
|
value: {{ .OPTIMISM_BASE_URL }}
|
||||||
- name: OPTIMISM_REQUESTS_PER_MINUTE
|
- name: OPTIMISM_REQUESTS_PER_MINUTE
|
||||||
value: "{{ .OPTIMISM_REQUESTS_PER_MINUTE }}"
|
value: "{{ .OPTIMISM_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: OSMOSIS_BASE_URL
|
||||||
|
value: {{ .OSMOSIS_BASE_URL }}
|
||||||
|
- name: OSMOSIS_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .OSMOSIS_REQUESTS_PER_MINUTE }}"
|
||||||
- name: POLYGON_BASE_URL
|
- name: POLYGON_BASE_URL
|
||||||
value: {{ .POLYGON_BASE_URL }}
|
value: {{ .POLYGON_BASE_URL }}
|
||||||
- name: POLYGON_REQUESTS_PER_MINUTE
|
- name: POLYGON_REQUESTS_PER_MINUTE
|
||||||
|
@ -146,6 +152,10 @@ spec:
|
||||||
value: {{ .TERRA2_BASE_URL }}
|
value: {{ .TERRA2_BASE_URL }}
|
||||||
- name: TERRA2_REQUESTS_PER_MINUTE
|
- name: TERRA2_REQUESTS_PER_MINUTE
|
||||||
value: "{{ .TERRA2_REQUESTS_PER_MINUTE }}"
|
value: "{{ .TERRA2_REQUESTS_PER_MINUTE }}"
|
||||||
|
- name: WORMCHAIN_BASE_URL
|
||||||
|
value: {{ .WORMCHAIN_BASE_URL }}
|
||||||
|
- name: WORMCHAIN_REQUESTS_PER_MINUTE
|
||||||
|
value: "{{ .WORMCHAIN_REQUESTS_PER_MINUTE }}"
|
||||||
- name: XPLA_BASE_URL
|
- name: XPLA_BASE_URL
|
||||||
value: {{ .XPLA_BASE_URL }}
|
value: {{ .XPLA_BASE_URL }}
|
||||||
- name: XPLA_REQUESTS_PER_MINUTE
|
- name: XPLA_REQUESTS_PER_MINUTE
|
||||||
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
package chains
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
)
|
||||||
|
|
||||||
|
type apiWormchain struct {
|
||||||
|
osmosisUrl string
|
||||||
|
osmosisRateLimiter *time.Ticker
|
||||||
|
p2pNetwork string
|
||||||
|
}
|
||||||
|
|
||||||
|
type wormchainTxDetail struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
Result struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Height string `json:"height"`
|
||||||
|
Index int `json:"index"`
|
||||||
|
TxResult struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
Log string `json:"log"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
GasWanted string `json:"gas_wanted"`
|
||||||
|
GasUsed string `json:"gas_used"`
|
||||||
|
Events []struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Attributes []struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Index bool `json:"index"`
|
||||||
|
} `json:"attributes"`
|
||||||
|
} `json:"events"`
|
||||||
|
Codespace string `json:"codespace"`
|
||||||
|
} `json:"tx_result"`
|
||||||
|
Tx string `json:"tx"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type event struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Attributes []struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
} `json:"attributes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetData struct {
|
||||||
|
Sender string `json:"sender"`
|
||||||
|
Receiver string `json:"receiver"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type logWrapper struct {
|
||||||
|
Events []event `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type worchainTx struct {
|
||||||
|
srcChannel, dstChannel, sender, receiver, timestamp, sequence string
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchWormchainDetail(ctx context.Context, baseUrl string, rateLimiter *time.Ticker, txHash string) (*worchainTx, error) {
|
||||||
|
uri := fmt.Sprintf("%s/tx?hash=%s", baseUrl, txHash)
|
||||||
|
body, err := httpGet(ctx, rateLimiter, uri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var tx wormchainTxDetail
|
||||||
|
err = json.Unmarshal(body, &tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var log []logWrapper
|
||||||
|
err = json.Unmarshal([]byte(tx.Result.TxResult.Log), &log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var srcChannel, dstChannel, sender, receiver, timestamp, sequence string
|
||||||
|
for _, l := range log {
|
||||||
|
for _, e := range l.Events {
|
||||||
|
if e.Type == "recv_packet" {
|
||||||
|
for _, attr := range e.Attributes {
|
||||||
|
if attr.Key == "packet_src_channel" {
|
||||||
|
srcChannel = attr.Value
|
||||||
|
}
|
||||||
|
if attr.Key == "packet_dst_channel" {
|
||||||
|
dstChannel = attr.Value
|
||||||
|
}
|
||||||
|
if attr.Key == "packet_timeout_timestamp" {
|
||||||
|
timestamp = attr.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr.Key == "packet_sequence" {
|
||||||
|
sequence = attr.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr.Key == "packet_data" {
|
||||||
|
var pd packetData
|
||||||
|
err = json.Unmarshal([]byte(attr.Value), &pd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sender = pd.Sender
|
||||||
|
receiver = pd.Receiver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &worchainTx{
|
||||||
|
srcChannel: srcChannel,
|
||||||
|
dstChannel: dstChannel,
|
||||||
|
sender: sender,
|
||||||
|
receiver: receiver,
|
||||||
|
timestamp: timestamp,
|
||||||
|
sequence: sequence,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryTemplate = `send_packet.packet_sequence='%s' AND send_packet.packet_timeout_timestamp='%s' AND send_packet.packet_src_channel='%s' AND send_packet.packet_dst_channel='%s'`
|
||||||
|
|
||||||
|
type osmosisRequest struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params struct {
|
||||||
|
Query string `json:"query"`
|
||||||
|
Page string `json:"page"`
|
||||||
|
} `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type osmosisResponse struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
Result struct {
|
||||||
|
Txs []struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Height string `json:"height"`
|
||||||
|
Index int `json:"index"`
|
||||||
|
TxResult struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
Log string `json:"log"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
GasWanted string `json:"gas_wanted"`
|
||||||
|
GasUsed string `json:"gas_used"`
|
||||||
|
Events []struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Attributes []struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Index bool `json:"index"`
|
||||||
|
} `json:"attributes"`
|
||||||
|
} `json:"events"`
|
||||||
|
Codespace string `json:"codespace"`
|
||||||
|
} `json:"tx_result"`
|
||||||
|
Tx string `json:"tx"`
|
||||||
|
} `json:"txs"`
|
||||||
|
TotalCount string `json:"total_count"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type osmosisTx struct {
|
||||||
|
txHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorchainAttributeTxDetail struct {
|
||||||
|
OriginChainID sdk.ChainID `bson:"originChainId"`
|
||||||
|
OriginTxHash string `bson:"originTxHash"`
|
||||||
|
OriginAddress string `bson:"originAddress"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchOsmosisDetail(ctx context.Context, baseUrl string, rateLimiter *time.Ticker, sequence, timestamp, srcChannel, dstChannel string) (*osmosisTx, error) {
|
||||||
|
|
||||||
|
query := fmt.Sprintf(queryTemplate, sequence, timestamp, srcChannel, dstChannel)
|
||||||
|
q := osmosisRequest{
|
||||||
|
Jsonrpc: "2.0",
|
||||||
|
ID: 1,
|
||||||
|
Method: "tx_search",
|
||||||
|
Params: struct {
|
||||||
|
Query string `json:"query"`
|
||||||
|
Page string `json:"page"`
|
||||||
|
}{
|
||||||
|
Query: query,
|
||||||
|
Page: "1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := httpPost(ctx, rateLimiter, baseUrl, q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var oReponse osmosisResponse
|
||||||
|
err = json.Unmarshal(response, &oReponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(oReponse.Result.Txs) == 0 {
|
||||||
|
return nil, fmt.Errorf("can not found hash for sequence %s, timestamp %s, srcChannel %s, dstChannel %s", sequence, timestamp, srcChannel, dstChannel)
|
||||||
|
}
|
||||||
|
return &osmosisTx{txHash: oReponse.Result.Txs[0].Hash}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *apiWormchain) fetchWormchainTx(
|
||||||
|
ctx context.Context,
|
||||||
|
rateLimiter *time.Ticker,
|
||||||
|
baseUrl string,
|
||||||
|
txHash string,
|
||||||
|
) (*TxDetail, error) {
|
||||||
|
|
||||||
|
txHash = txHashLowerCaseWith0x(txHash)
|
||||||
|
|
||||||
|
wormchainTx, err := fetchWormchainDetail(ctx, baseUrl, rateLimiter, txHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify if this transaction is from osmosis by wormchain
|
||||||
|
if a.isOsmosisTx(wormchainTx) {
|
||||||
|
osmosisTx, err := fetchOsmosisDetail(ctx, a.osmosisUrl, a.osmosisRateLimiter, wormchainTx.sequence, wormchainTx.timestamp, wormchainTx.srcChannel, wormchainTx.dstChannel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &TxDetail{
|
||||||
|
NativeTxHash: txHash,
|
||||||
|
From: wormchainTx.receiver,
|
||||||
|
Attribute: &AttributeTxDetail{
|
||||||
|
Type: "wormchain-gateway",
|
||||||
|
Value: &WorchainAttributeTxDetail{
|
||||||
|
OriginChainID: ChainIDOsmosis,
|
||||||
|
OriginTxHash: osmosisTx.txHash,
|
||||||
|
OriginAddress: wormchainTx.sender,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TxDetail{
|
||||||
|
NativeTxHash: txHash,
|
||||||
|
From: wormchainTx.receiver,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *apiWormchain) isOsmosisTx(tx *worchainTx) bool {
|
||||||
|
if a.p2pNetwork == domain.P2pMainNet {
|
||||||
|
return tx.srcChannel == "channel-2186" && tx.dstChannel == "channel-3"
|
||||||
|
}
|
||||||
|
if a.p2pNetwork == domain.P2pTestNet {
|
||||||
|
return tx.srcChannel == "channel-486" && tx.dstChannel == "channel-4"
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -23,11 +23,23 @@ var (
|
||||||
baseUrlsByChain map[sdk.ChainID]string
|
baseUrlsByChain map[sdk.ChainID]string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WARNING: The following chain IDs are not supported by the wormhole-sdk:
|
||||||
|
const ChainIDOsmosis sdk.ChainID = 20
|
||||||
|
|
||||||
|
type WormchainTxDetail struct {
|
||||||
|
}
|
||||||
type TxDetail struct {
|
type TxDetail struct {
|
||||||
// From 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.
|
||||||
From string
|
From string
|
||||||
// NativeTxHash contains the transaction hash, encoded in the chain's native format.
|
// NativeTxHash contains the transaction hash, encoded in the chain's native format.
|
||||||
NativeTxHash string
|
NativeTxHash string
|
||||||
|
// Attribute contains the specific information of the transaction.
|
||||||
|
Attribute *AttributeTxDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttributeTxDetail struct {
|
||||||
|
Type string
|
||||||
|
Value any
|
||||||
}
|
}
|
||||||
|
|
||||||
func Initialize(cfg *config.RpcProviderSettings) {
|
func Initialize(cfg *config.RpcProviderSettings) {
|
||||||
|
@ -67,6 +79,8 @@ func Initialize(cfg *config.RpcProviderSettings) {
|
||||||
rateLimitersByChain[sdk.ChainIDTerra2] = convertToRateLimiter(cfg.Terra2RequestsPerMinute)
|
rateLimitersByChain[sdk.ChainIDTerra2] = convertToRateLimiter(cfg.Terra2RequestsPerMinute)
|
||||||
rateLimitersByChain[sdk.ChainIDSui] = convertToRateLimiter(cfg.SuiRequestsPerMinute)
|
rateLimitersByChain[sdk.ChainIDSui] = convertToRateLimiter(cfg.SuiRequestsPerMinute)
|
||||||
rateLimitersByChain[sdk.ChainIDXpla] = convertToRateLimiter(cfg.XplaRequestsPerMinute)
|
rateLimitersByChain[sdk.ChainIDXpla] = convertToRateLimiter(cfg.XplaRequestsPerMinute)
|
||||||
|
rateLimitersByChain[sdk.ChainIDWormchain] = convertToRateLimiter(cfg.WormchainRequestsPerMinute)
|
||||||
|
rateLimitersByChain[ChainIDOsmosis] = convertToRateLimiter(cfg.OsmosisRequestsPerMinute)
|
||||||
|
|
||||||
// Initialize the RPC base URLs for each chain
|
// Initialize the RPC base URLs for each chain
|
||||||
baseUrlsByChain = make(map[sdk.ChainID]string)
|
baseUrlsByChain = make(map[sdk.ChainID]string)
|
||||||
|
@ -92,6 +106,7 @@ func Initialize(cfg *config.RpcProviderSettings) {
|
||||||
baseUrlsByChain[sdk.ChainIDTerra2] = cfg.Terra2BaseUrl
|
baseUrlsByChain[sdk.ChainIDTerra2] = cfg.Terra2BaseUrl
|
||||||
baseUrlsByChain[sdk.ChainIDSui] = cfg.SuiBaseUrl
|
baseUrlsByChain[sdk.ChainIDSui] = cfg.SuiBaseUrl
|
||||||
baseUrlsByChain[sdk.ChainIDXpla] = cfg.XplaBaseUrl
|
baseUrlsByChain[sdk.ChainIDXpla] = cfg.XplaBaseUrl
|
||||||
|
baseUrlsByChain[sdk.ChainIDWormchain] = cfg.WormchainBaseUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchTx(
|
func FetchTx(
|
||||||
|
@ -99,6 +114,7 @@ func FetchTx(
|
||||||
cfg *config.RpcProviderSettings,
|
cfg *config.RpcProviderSettings,
|
||||||
chainId sdk.ChainID,
|
chainId sdk.ChainID,
|
||||||
txHash string,
|
txHash string,
|
||||||
|
p2pNetwork string,
|
||||||
) (*TxDetail, error) {
|
) (*TxDetail, error) {
|
||||||
|
|
||||||
// Decide which RPC/API service to use based on chain ID
|
// Decide which RPC/API service to use based on chain ID
|
||||||
|
@ -132,6 +148,17 @@ func FetchTx(
|
||||||
sdk.ChainIDOptimism,
|
sdk.ChainIDOptimism,
|
||||||
sdk.ChainIDPolygon:
|
sdk.ChainIDPolygon:
|
||||||
fetchFunc = fetchEthTx
|
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,
|
||||||
|
p2pNetwork: p2pNetwork,
|
||||||
|
}
|
||||||
|
fetchFunc = apiWormchain.fetchWormchainTx
|
||||||
default:
|
default:
|
||||||
return nil, ErrChainNotSupported
|
return nil, ErrChainNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package chains
|
package chains
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -56,13 +58,52 @@ func httpGet(ctx context.Context, rateLimiter *time.Ticker, url string) ([]byte,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the response body and return
|
// Read the response body and return
|
||||||
body, err := ioutil.ReadAll(response.Body)
|
body, err := io.ReadAll(response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||||
}
|
}
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// httpPost is a helper function that performs an HTTP request.
|
||||||
|
func httpPost(ctx context.Context, rateLimiter *time.Ticker, url string, body any) ([]byte, error) {
|
||||||
|
|
||||||
|
// Wait for the rate limiter
|
||||||
|
if !waitForRateLimiter(ctx, rateLimiter) {
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the HTTP request
|
||||||
|
request, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Send it
|
||||||
|
var client http.Client
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
|
result, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func waitForRateLimiter(ctx context.Context, t *time.Ticker) bool {
|
func waitForRateLimiter(ctx context.Context, t *time.Ticker) bool {
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
|
@ -108,3 +149,10 @@ func (c *rateLimitedRpcClient) CallContext(
|
||||||
func (c *rateLimitedRpcClient) Close() {
|
func (c *rateLimitedRpcClient) Close() {
|
||||||
c.client.Close()
|
c.client.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func txHashLowerCaseWith0x(v string) string {
|
||||||
|
if strings.HasPrefix(v, "0x") {
|
||||||
|
return strings.ToLower(v)
|
||||||
|
}
|
||||||
|
return "0x" + strings.ToLower(v)
|
||||||
|
}
|
||||||
|
|
|
@ -109,6 +109,7 @@ func main() {
|
||||||
wg: &wg,
|
wg: &wg,
|
||||||
totalDocuments: totalDocuments,
|
totalDocuments: totalDocuments,
|
||||||
processedDocuments: &processedDocuments,
|
processedDocuments: &processedDocuments,
|
||||||
|
p2pNetwork: cfg.P2pNetwork,
|
||||||
}
|
}
|
||||||
go consume(rootCtx, &p)
|
go consume(rootCtx, &p)
|
||||||
}
|
}
|
||||||
|
@ -242,6 +243,7 @@ type consumerParams struct {
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
totalDocuments uint64
|
totalDocuments uint64
|
||||||
processedDocuments *atomic.Uint64
|
processedDocuments *atomic.Uint64
|
||||||
|
p2pNetwork string
|
||||||
}
|
}
|
||||||
|
|
||||||
// consume reads VAA IDs from a channel, processes them, and updates the database accordingly.
|
// consume reads VAA IDs from a channel, processes them, and updates the database accordingly.
|
||||||
|
@ -303,7 +305,7 @@ func consume(ctx context.Context, params *consumerParams) {
|
||||||
TxHash: *v.TxHash,
|
TxHash: *v.TxHash,
|
||||||
Overwrite: true, // Overwrite old contents
|
Overwrite: true, // Overwrite old contents
|
||||||
}
|
}
|
||||||
err := consumer.ProcessSourceTx(ctx, params.logger, params.rpcProviderSettings, params.repository, &p)
|
err := consumer.ProcessSourceTx(ctx, params.logger, params.rpcProviderSettings, params.repository, &p, params.p2pNetwork)
|
||||||
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),
|
||||||
|
|
|
@ -13,8 +13,8 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
// validate commandline arguments
|
// validate commandline arguments
|
||||||
if len(os.Args) != 3 {
|
if len(os.Args) != 4 {
|
||||||
log.Fatalf("Usage: ./%s <chain name> <tx hash>\n", os.Args[0])
|
log.Fatalf("Usage: ./%s <chain name> <tx hash> <p2p network>\n", os.Args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// load config settings
|
// load config settings
|
||||||
|
@ -31,7 +31,7 @@ func main() {
|
||||||
|
|
||||||
// fetch tx data
|
// fetch tx data
|
||||||
chains.Initialize(cfg)
|
chains.Initialize(cfg)
|
||||||
txDetail, err := chains.FetchTx(context.Background(), cfg, chainId, os.Args[2])
|
txDetail, err := chains.FetchTx(context.Background(), cfg, chainId, os.Args[2], os.Args[3])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to get transaction data: %v", err)
|
log.Fatalf("Failed to get transaction data: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ func main() {
|
||||||
// create and start a consumer.
|
// create and start a consumer.
|
||||||
vaaConsumeFunc := newVAAConsumeFunc(rootCtx, cfg, metrics, logger)
|
vaaConsumeFunc := newVAAConsumeFunc(rootCtx, cfg, metrics, logger)
|
||||||
repository := consumer.NewRepository(logger, db.Database)
|
repository := consumer.NewRepository(logger, db.Database)
|
||||||
consumer := consumer.New(vaaConsumeFunc, &cfg.RpcProviderSettings, rootCtx, logger, repository, metrics)
|
consumer := consumer.New(vaaConsumeFunc, &cfg.RpcProviderSettings, rootCtx, logger, repository, metrics, cfg.P2pNetwork)
|
||||||
consumer.Start(rootCtx)
|
consumer.Start(rootCtx)
|
||||||
|
|
||||||
logger.Info("Started wormhole-explorer-tx-tracker")
|
logger.Info("Started wormhole-explorer-tx-tracker")
|
||||||
|
|
|
@ -22,6 +22,7 @@ type BackfillerSettings struct {
|
||||||
LogLevel string `split_words:"true" default:"INFO"`
|
LogLevel string `split_words:"true" default:"INFO"`
|
||||||
NumWorkers uint `split_words:"true" required:"true"`
|
NumWorkers uint `split_words:"true" required:"true"`
|
||||||
BulkSize uint `split_words:"true" required:"true"`
|
BulkSize uint `split_words:"true" required:"true"`
|
||||||
|
P2pNetwork string `split_words:"true" required:"true"`
|
||||||
|
|
||||||
// Strategy determines which VAAs will be affected by the backfiller.
|
// Strategy determines which VAAs will be affected by the backfiller.
|
||||||
Strategy struct {
|
Strategy struct {
|
||||||
|
@ -41,6 +42,7 @@ type ServiceSettings struct {
|
||||||
LogLevel string `split_words:"true" default:"INFO"`
|
LogLevel string `split_words:"true" default:"INFO"`
|
||||||
PprofEnabled bool `split_words:"true" default:"false"`
|
PprofEnabled bool `split_words:"true" default:"false"`
|
||||||
MetricsEnabled bool `split_words:"true" default:"false"`
|
MetricsEnabled bool `split_words:"true" default:"false"`
|
||||||
|
P2pNetwork string `split_words:"true" required:"true"`
|
||||||
AwsSettings
|
AwsSettings
|
||||||
MongodbSettings
|
MongodbSettings
|
||||||
RpcProviderSettings
|
RpcProviderSettings
|
||||||
|
@ -92,6 +94,8 @@ type RpcProviderSettings struct {
|
||||||
OasisRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
OasisRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
OptimismBaseUrl string `split_words:"true" required:"true"`
|
OptimismBaseUrl string `split_words:"true" required:"true"`
|
||||||
OptimismRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
OptimismRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
|
OsmosisBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
OsmosisRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
PolygonBaseUrl string `split_words:"true" required:"true"`
|
PolygonBaseUrl string `split_words:"true" required:"true"`
|
||||||
PolygonRequestsPerMinute uint16 `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"`
|
||||||
|
@ -104,6 +108,8 @@ type RpcProviderSettings struct {
|
||||||
Terra2RequestsPerMinute uint16 `split_words:"true" required:"true"`
|
Terra2RequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
XplaBaseUrl string `split_words:"true" required:"true"`
|
XplaBaseUrl string `split_words:"true" required:"true"`
|
||||||
XplaRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
XplaRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
|
WormchainBaseUrl string `split_words:"true" required:"true"`
|
||||||
|
WormchainRequestsPerMinute uint16 `split_words:"true" required:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadFromEnv[T any]() (*T, error) {
|
func LoadFromEnv[T any]() (*T, error) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Consumer struct {
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
repository *Repository
|
repository *Repository
|
||||||
metrics metrics.Metrics
|
metrics metrics.Metrics
|
||||||
|
p2pNetwork string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new vaa consumer.
|
// New creates a new vaa consumer.
|
||||||
|
@ -29,6 +30,7 @@ func New(
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
repository *Repository,
|
repository *Repository,
|
||||||
metrics metrics.Metrics,
|
metrics metrics.Metrics,
|
||||||
|
p2pNetwork string,
|
||||||
) *Consumer {
|
) *Consumer {
|
||||||
|
|
||||||
c := Consumer{
|
c := Consumer{
|
||||||
|
@ -37,6 +39,7 @@ func New(
|
||||||
logger: logger,
|
logger: logger,
|
||||||
repository: repository,
|
repository: repository,
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
|
p2pNetwork: p2pNetwork,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &c
|
return &c
|
||||||
|
@ -81,7 +84,7 @@ func (c *Consumer) process(ctx context.Context, msg queue.ConsumerMessage) {
|
||||||
TxHash: event.TxHash,
|
TxHash: event.TxHash,
|
||||||
Overwrite: false, // avoid processing the same transaction twice
|
Overwrite: false, // avoid processing the same transaction twice
|
||||||
}
|
}
|
||||||
err := ProcessSourceTx(ctx, c.logger, c.rpcProviderSettings, c.repository, &p)
|
err := ProcessSourceTx(ctx, c.logger, c.rpcProviderSettings, c.repository, &p, c.p2pNetwork)
|
||||||
|
|
||||||
// Log a message informing the processing status
|
// Log a message informing the processing status
|
||||||
if errors.Is(err, chains.ErrChainNotSupported) {
|
if errors.Is(err, chains.ErrChainNotSupported) {
|
||||||
|
|
|
@ -44,6 +44,7 @@ func ProcessSourceTx(
|
||||||
rpcServiceProviderSettings *config.RpcProviderSettings,
|
rpcServiceProviderSettings *config.RpcProviderSettings,
|
||||||
repository *Repository,
|
repository *Repository,
|
||||||
params *ProcessSourceTxParams,
|
params *ProcessSourceTxParams,
|
||||||
|
p2pNetwork string,
|
||||||
) error {
|
) error {
|
||||||
|
|
||||||
if !params.Overwrite {
|
if !params.Overwrite {
|
||||||
|
@ -72,7 +73,7 @@ func ProcessSourceTx(
|
||||||
for retries := 0; ; retries++ {
|
for retries := 0; ; retries++ {
|
||||||
|
|
||||||
// Get transaction details from the emitter blockchain
|
// Get transaction details from the emitter blockchain
|
||||||
txDetail, err = chains.FetchTx(ctx, rpcServiceProviderSettings, params.ChainId, params.TxHash)
|
txDetail, err = chains.FetchTx(ctx, rpcServiceProviderSettings, params.ChainId, params.TxHash, p2pNetwork)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,9 @@ 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})
|
fields = append(fields, primitive.E{Key: "from", Value: params.TxDetail.From})
|
||||||
|
if params.TxDetail.Attribute != nil {
|
||||||
|
fields = append(fields, primitive.E{Key: "attribute", Value: params.TxDetail.Attribute})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update := bson.D{
|
update := bson.D{
|
||||||
|
|
Loading…
Reference in New Issue