Spy relayer fixes (#1095)
* try catch pullAllEVMTokens * remove some logs from walletMonitor * redundant check fix, init s/f metrics * fix unwrap for realz * confirmed and rollback metrics * fix terra balance fetching * relayer: split out wallet monitor * relayer: update tilt for wallet-monitor * relayer: evm print tx hash before wait * relayer: split out redis queue by source/target * Update spy relayer example mainnet config files * Includes Aurora bits in the emitter addresses, supported tokens, and supported chains. Co-authored-by: Jeff Schroeder <jeffschroeder@computer.org>
This commit is contained in:
parent
26f9b9d752
commit
50bb184522
13
Tiltfile
13
Tiltfile
|
@ -349,13 +349,24 @@ if spy_relayer:
|
||||||
"spy-relayer",
|
"spy-relayer",
|
||||||
resource_deps = ["proto-gen", "guardian", "redis"],
|
resource_deps = ["proto-gen", "guardian", "redis"],
|
||||||
port_forwards = [
|
port_forwards = [
|
||||||
port_forward(6063, container_port = 6060, name = "Debug/Status Server [:6063]", host = webHost),
|
|
||||||
port_forward(8083, name = "Prometheus [:8083]", host = webHost),
|
port_forward(8083, name = "Prometheus [:8083]", host = webHost),
|
||||||
],
|
],
|
||||||
labels = ["spy-relayer"],
|
labels = ["spy-relayer"],
|
||||||
trigger_mode = trigger_mode,
|
trigger_mode = trigger_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
k8s_yaml_with_ns("devnet/spy-wallet-monitor.yaml")
|
||||||
|
|
||||||
|
k8s_resource(
|
||||||
|
"spy-wallet-monitor",
|
||||||
|
resource_deps = ["proto-gen", "guardian", "redis"],
|
||||||
|
port_forwards = [
|
||||||
|
port_forward(8084, name = "Prometheus [:8084]", host = webHost),
|
||||||
|
],
|
||||||
|
labels = ["spy-relayer"],
|
||||||
|
trigger_mode = trigger_mode,
|
||||||
|
)
|
||||||
|
|
||||||
k8s_yaml_with_ns("devnet/eth-devnet.yaml")
|
k8s_yaml_with_ns("devnet/eth-devnet.yaml")
|
||||||
|
|
||||||
k8s_resource(
|
k8s_resource(
|
||||||
|
|
|
@ -9,6 +9,10 @@ spec:
|
||||||
clusterIP: None
|
clusterIP: None
|
||||||
selector:
|
selector:
|
||||||
app: spy-listener
|
app: spy-listener
|
||||||
|
ports:
|
||||||
|
- port: 8082
|
||||||
|
name: prometheus
|
||||||
|
protocol: TCP
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
|
|
|
@ -9,6 +9,10 @@ spec:
|
||||||
clusterIP: None
|
clusterIP: None
|
||||||
selector:
|
selector:
|
||||||
app: spy-relayer
|
app: spy-relayer
|
||||||
|
ports:
|
||||||
|
- port: 8083
|
||||||
|
name: prometheus
|
||||||
|
protocol: TCP
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
|
@ -36,6 +40,10 @@ spec:
|
||||||
- --prefix
|
- --prefix
|
||||||
- /app/relayer/spy_relayer/
|
- /app/relayer/spy_relayer/
|
||||||
- tilt_relayer
|
- tilt_relayer
|
||||||
|
ports:
|
||||||
|
- containerPort: 8083
|
||||||
|
name: prometheus
|
||||||
|
protocol: TCP
|
||||||
tty: true
|
tty: true
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
tcpSocket:
|
tcpSocket:
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: spy-wallet-monitor
|
||||||
|
labels:
|
||||||
|
app: spy-wallet-monitor
|
||||||
|
spec:
|
||||||
|
clusterIP: None
|
||||||
|
selector:
|
||||||
|
app: spy-wallet-monitor
|
||||||
|
ports:
|
||||||
|
- port: 8084
|
||||||
|
name: prometheus
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: spy-wallet-monitor
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: spy-wallet-monitor
|
||||||
|
serviceName: spy-wallet-monitor
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: spy-wallet-monitor
|
||||||
|
spec:
|
||||||
|
restartPolicy: Always
|
||||||
|
terminationGracePeriodSeconds: 0
|
||||||
|
containers:
|
||||||
|
- name: spy-wallet-monitor
|
||||||
|
image: spy-relay-image
|
||||||
|
command:
|
||||||
|
- npm
|
||||||
|
- run
|
||||||
|
- --prefix
|
||||||
|
- /app/relayer/spy_relayer/
|
||||||
|
- tilt_wallet_monitor
|
||||||
|
ports:
|
||||||
|
- containerPort: 8084
|
||||||
|
name: prometheus
|
||||||
|
protocol: TCP
|
||||||
|
tty: true
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 2000
|
||||||
|
periodSeconds: 1
|
||||||
|
failureThreshold: 300
|
|
@ -6,7 +6,6 @@ READINESS_PORT=2000
|
||||||
CLEAR_REDIS_ON_INIT=false
|
CLEAR_REDIS_ON_INIT=false
|
||||||
DEMOTE_WORKING_ON_INIT=true
|
DEMOTE_WORKING_ON_INIT=true
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
SIMULATED_TERRA_WALLET_ADDRESS=terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v
|
|
||||||
SUPPORTED_TOKENS=[{"chainId":1,"address":"So11111111111111111111111111111111111111112"}, {"chainId":2,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}, {"chainId":3,"address":"uluna"}, {"chainId":4,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}]
|
SUPPORTED_TOKENS=[{"chainId":1,"address":"So11111111111111111111111111111111111111112"}, {"chainId":2,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}, {"chainId":3,"address":"uluna"}, {"chainId":4,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}]
|
||||||
PRIVATE_KEYS=[ { "chainId": 1, "privateKeys": [ [ 14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161, 89, 84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70, 87, 65, 8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47, 44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141 ] ] }, { "chainId": 2, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }, { "chainId": 3, "privateKeys": [ "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" ] }, { "chainId": 4, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }]
|
PRIVATE_KEYS=[ { "chainId": 1, "privateKeys": [ [ 14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161, 89, 84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70, 87, 65, 8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47, 44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141 ] ] }, { "chainId": 2, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }, { "chainId": 3, "privateKeys": [ "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" ] }, { "chainId": 4, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }]
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,5 @@ READINESS_PORT=2000
|
||||||
CLEAR_REDIS_ON_INIT=false
|
CLEAR_REDIS_ON_INIT=false
|
||||||
DEMOTE_WORKING_ON_INIT=true
|
DEMOTE_WORKING_ON_INIT=true
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
SIMULATED_TERRA_WALLET_ADDRESS=terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v
|
|
||||||
SUPPORTED_TOKENS=[{"chainId":1,"address":"So11111111111111111111111111111111111111112"}, {"chainId":2,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}, {"chainId":3,"address":"uluna"}, {"chainId":4,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}]
|
SUPPORTED_TOKENS=[{"chainId":1,"address":"So11111111111111111111111111111111111111112"}, {"chainId":2,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}, {"chainId":3,"address":"uluna"}, {"chainId":4,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}]
|
||||||
PRIVATE_KEYS=[ { "chainId": 1, "privateKeys": [ [ 14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161, 89, 84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70, 87, 65, 8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47, 44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141 ] ] }, { "chainId": 2, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }, { "chainId": 3, "privateKeys": [ "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" ] }, { "chainId": 4, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }]
|
PRIVATE_KEYS=[ { "chainId": 1, "privateKeys": [ [ 14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161, 89, 84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70, 87, 65, 8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47, 44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141 ] ] }, { "chainId": 2, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }, { "chainId": 3, "privateKeys": [ "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" ] }, { "chainId": 4, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }]
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
SUPPORTED_CHAINS=[ { "chainId": 1, "chainName": "Solana", "nativeCurrencySymbol": "SOL", "nodeUrl": "http://solana-devnet:8899", "tokenBridgeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16", "bridgeAddress": "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o", "walletPrivateKey": [ [ 14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161, 89, 84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70, 87, 65, 8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47, 44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141 ] ], "wrappedAsset": "So11111111111111111111111111111111111111112" }, { "chainId": 2, "chainName": "Ethereum", "nativeCurrencySymbol": "ETH", "nodeUrl": "http://eth-devnet:8545", "tokenBridgeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16", "walletPrivateKey": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ], "wrappedAsset": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" }, { "chainId": 3, "chainName": "Terra", "nativeCurrencySymbol": "LUNA", "nodeUrl": "http://terra-terrad:1317", "tokenBridgeAddress": "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4", "walletPrivateKey": [ "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" ], "terraName": "localterra", "terraChainId": "columbus-5", "terraCoin": "uluna", "terraGasPriceUrl": "http://terra-fcd:3060/v1/txs/gas_prices" }, { "chainId": 4, "chainName": "Binance Smart Chain", "nativeCurrencySymbol": "BNB", "nodeUrl": "http://eth-devnet2:8545", "tokenBridgeAddress": "0x0290FB167208Af455bB137780163b7B7a9a10C16", "walletPrivateKey": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ], "wrappedAsset": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" }]
|
||||||
|
REDIS_HOST= redis
|
||||||
|
REDIS_PORT=6379
|
||||||
|
PROM_PORT=8084
|
||||||
|
READINESS_PORT=2000
|
||||||
|
CLEAR_REDIS_ON_INIT=false
|
||||||
|
DEMOTE_WORKING_ON_INIT=true
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
SUPPORTED_TOKENS=[{"chainId":1,"address":"So11111111111111111111111111111111111111112"}, {"chainId":2,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}, {"chainId":3,"address":"uluna"}, {"chainId":4,"address":"0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"}]
|
||||||
|
PRIVATE_KEYS=[ { "chainId": 1, "privateKeys": [ [ 14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161, 89, 84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70, 87, 65, 8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47, 44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141 ] ] }, { "chainId": 2, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }, { "chainId": 3, "privateKeys": [ "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" ] }, { "chainId": 4, "privateKeys": [ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" ] }]
|
|
@ -6,6 +6,5 @@ READINESS_PORT=2000
|
||||||
CLEAR_REDIS_ON_INIT=false
|
CLEAR_REDIS_ON_INIT=false
|
||||||
DEMOTE_WORKING_ON_INIT=true
|
DEMOTE_WORKING_ON_INIT=true
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
SIMULATED_TERRA_WALLET_ADDRESS= Requires a terra public address which will always have enough funds to pay for transactions. one of the hot wallets will do.
|
|
||||||
SUPPORTED_TOKENS= paste from supportedTokens.json. This must be the same as in the .env.listener file.
|
SUPPORTED_TOKENS= paste from supportedTokens.json. This must be the same as in the .env.listener file.
|
||||||
PRIVATE_KEYS= paste from privateKeys.json
|
PRIVATE_KEYS= paste from privateKeys.json
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
SUPPORTED_CHAINS= paste from supportedChains.json
|
||||||
|
REDIS_HOST= change me
|
||||||
|
REDIS_PORT= change me
|
||||||
|
PROM_PORT=8084
|
||||||
|
READINESS_PORT=2000
|
||||||
|
CLEAR_REDIS_ON_INIT=false
|
||||||
|
DEMOTE_WORKING_ON_INIT=true
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
SUPPORTED_TOKENS= paste from supportedTokens.json. This must be the same as in the .env.listener file.
|
||||||
|
PRIVATE_KEYS= paste from privateKeys.json
|
|
@ -1,10 +1,11 @@
|
||||||
[
|
[
|
||||||
{"chainId":1,"emitterAddress":"wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"},
|
{"chainId":1,"emitterAddress":"wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"},
|
||||||
{"chainId":2,"emitterAddress":"0x3ee18B2214AFF97000D974cf647E7C347E8fa585"},
|
{"chainId":2,"emitterAddress":"0x3ee18B2214AFF97000D974cf647E7C347E8fa585"},
|
||||||
{"chainId":3,"emitterAddress":"terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf"},
|
{"chainId":3,"emitterAddress":"terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf"},
|
||||||
{"chainId":4,"emitterAddress":"0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7"},
|
{"chainId":4,"emitterAddress":"0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7"},
|
||||||
{"chainId":5,"emitterAddress":"0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE"},
|
{"chainId":5,"emitterAddress":"0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE"},
|
||||||
{"chainId":6,"emitterAddress":"0x0e082F06FF657D94310cB8cE8B0D9a04541d8052"},
|
{"chainId":6,"emitterAddress":"0x0e082F06FF657D94310cB8cE8B0D9a04541d8052"},
|
||||||
{"chainId":7,"emitterAddress":"0x5848C791e09901b40A9Ef749f2a6735b418d7564"},
|
{"chainId":7,"emitterAddress":"0x5848C791e09901b40A9Ef749f2a6735b418d7564"},
|
||||||
|
{"chainId":9,"emitterAddress":"0x51b5123a7b0f9b2ba265f9c4c8de7d78d52f510f"},
|
||||||
{"chainId":10,"emitterAddress":"0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2"}
|
{"chainId":10,"emitterAddress":"0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2"}
|
||||||
]
|
]
|
||||||
|
|
|
@ -59,6 +59,14 @@
|
||||||
"tokenBridgeAddress": "0x5848C791e09901b40A9Ef749f2a6735b418d7564",
|
"tokenBridgeAddress": "0x5848C791e09901b40A9Ef749f2a6735b418d7564",
|
||||||
"wrappedAsset": "0x21C718C22D52d0F3a789b752D4c2fD5908a8A733"
|
"wrappedAsset": "0x21C718C22D52d0F3a789b752D4c2fD5908a8A733"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"chainId": 9,
|
||||||
|
"chainName": "Aurora",
|
||||||
|
"nativeCurrencySymbol": "ETH",
|
||||||
|
"nodeUrl": "https://mainnet.aurora.dev",
|
||||||
|
"tokenBridgeAddress": "0x51b5123a7b0f9b2ba265f9c4c8de7d78d52f510f",
|
||||||
|
"wrappedAsset": "0xC9BdeEd33CD01541e1eeD10f90519d2C06Fe3feB"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"chainId": 10,
|
"chainId": 10,
|
||||||
"chainName": "Fantom",
|
"chainName": "Fantom",
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
[
|
[
|
||||||
{"chainId":1,"address":"So11111111111111111111111111111111111111112"},
|
{"chainId":1,"address":"So11111111111111111111111111111111111111112"},
|
||||||
{"chainId":2,"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},
|
{"chainId":2,"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},
|
||||||
{"chainId":3,"address":"uluna"},
|
{"chainId":3,"address":"uluna"},
|
||||||
{"chainId":3,"address":"uusd"},
|
{"chainId":3,"address":"uusd"},
|
||||||
{"chainId":4,"address":"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"},
|
{"chainId":4,"address":"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"},
|
||||||
{"chainId":5,"address":"0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"},
|
{"chainId":5,"address":"0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"},
|
||||||
{"chainId":6,"address":"0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7"},
|
{"chainId":6,"address":"0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7"},
|
||||||
{"chainId":7,"address":"0x21C718C22D52d0F3a789b752D4c2fD5908a8A733"},
|
{"chainId":7,"address":"0x21C718C22D52d0F3a789b752D4c2fD5908a8A733"},
|
||||||
|
{"chainId":9,"address":"0xC9BdeEd33CD01541e1eeD10f90519d2C06Fe3feB"},
|
||||||
{"chainId":10,"address":"0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83"},
|
{"chainId":10,"address":"0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83"},
|
||||||
|
|
||||||
|
|
||||||
{"chainId":2,"address":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"},
|
{"chainId":2,"address":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"},
|
||||||
{"chainId":2,"address":"0xdac17f958d2ee523a2206206994597c13d831ec7"}
|
{"chainId":2,"address":"0xdac17f958d2ee523a2206206994597c13d831ec7"}
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
"spy_relay": "node lib/main.js",
|
"spy_relay": "node lib/main.js",
|
||||||
"tilt_listener": "SPY_RELAY_CONFIG=.env.tilt.listener node lib/main.js --listen_only",
|
"tilt_listener": "SPY_RELAY_CONFIG=.env.tilt.listener node lib/main.js --listen_only",
|
||||||
"tilt_relayer": "SPY_RELAY_CONFIG=.env.tilt.relayer node lib/main.js --relay_only",
|
"tilt_relayer": "SPY_RELAY_CONFIG=.env.tilt.relayer node lib/main.js --relay_only",
|
||||||
|
"tilt_wallet_monitor": "SPY_RELAY_CONFIG=.env.tilt.wallet-monitor node lib/main.js --wallet_monitor_only",
|
||||||
"listen_only": "node lib/main.js --listen_only",
|
"listen_only": "node lib/main.js --listen_only",
|
||||||
"relay_only": "node lib/main.js --relay_only",
|
"relay_only": "node lib/main.js --relay_only",
|
||||||
|
"wallet_monitor_only": "node lib/main.js --wallet_monitor_only",
|
||||||
"test": "jest --config jestconfig.json --verbose"
|
"test": "jest --config jestconfig.json --verbose"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ChainId } from "@certusone/wormhole-sdk";
|
import { ChainId } from "@certusone/wormhole-sdk";
|
||||||
import http = require("http");
|
import http = require("http");
|
||||||
import client = require("prom-client");
|
import client = require("prom-client");
|
||||||
import { WalletBalance } from "../relayer/walletMonitor";
|
import { WalletBalance } from "../monitor/walletMonitor";
|
||||||
import { chainIDStrings } from "../utils/wormhole";
|
import { chainIDStrings } from "../utils/wormhole";
|
||||||
import { getScopedLogger } from "./logHelper";
|
import { getScopedLogger } from "./logHelper";
|
||||||
import { RedisTables } from "./redisHelper";
|
import { RedisTables } from "./redisHelper";
|
||||||
|
@ -15,7 +15,8 @@ const logger = getScopedLogger(["prometheusHelpers"]);
|
||||||
export enum PromMode {
|
export enum PromMode {
|
||||||
Listen,
|
Listen,
|
||||||
Relay,
|
Relay,
|
||||||
Both,
|
WalletMonitor,
|
||||||
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PromHelper {
|
export class PromHelper {
|
||||||
|
@ -29,11 +30,21 @@ export class PromHelper {
|
||||||
help: "number of successful relays",
|
help: "number of successful relays",
|
||||||
labelNames: ["chain_name"],
|
labelNames: ["chain_name"],
|
||||||
});
|
});
|
||||||
|
private confirmedCounter = new client.Counter({
|
||||||
|
name: "spy_relay_confirmed_successes",
|
||||||
|
help: "number of confirmed successful relays",
|
||||||
|
labelNames: ["chain_name"],
|
||||||
|
});
|
||||||
private failureCounter = new client.Counter({
|
private failureCounter = new client.Counter({
|
||||||
name: "spy_relay_failures",
|
name: "spy_relay_failures",
|
||||||
help: "number of failed relays",
|
help: "number of failed relays",
|
||||||
labelNames: ["chain_name"],
|
labelNames: ["chain_name"],
|
||||||
});
|
});
|
||||||
|
private rollbackCounter = new client.Counter({
|
||||||
|
name: "spy_relay_rollback",
|
||||||
|
help: "number of rolled back relays",
|
||||||
|
labelNames: ["chain_name"],
|
||||||
|
});
|
||||||
private completeTime = new client.Histogram({
|
private completeTime = new client.Histogram({
|
||||||
name: "spy_relay_complete_time",
|
name: "spy_relay_complete_time",
|
||||||
help: "Time is took to complete transfer",
|
help: "Time is took to complete transfer",
|
||||||
|
@ -54,7 +65,7 @@ export class PromHelper {
|
||||||
private redisQueue = new client.Gauge({
|
private redisQueue = new client.Gauge({
|
||||||
name: "spy_relay_redis_queue_length",
|
name: "spy_relay_redis_queue_length",
|
||||||
help: "number of items in the pending queue.",
|
help: "number of items in the pending queue.",
|
||||||
labelNames: ["queue"],
|
labelNames: ["queue", "source_chain_name", "target_chain_name"],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wallet metrics
|
// Wallet metrics
|
||||||
|
@ -83,17 +94,12 @@ export class PromHelper {
|
||||||
} else if (
|
} else if (
|
||||||
req.url === "/metrics" ||
|
req.url === "/metrics" ||
|
||||||
req.url === "/relayer" ||
|
req.url === "/relayer" ||
|
||||||
req.url === "/listener"
|
req.url === "/listener" ||
|
||||||
|
req.url === "/wallet-monitor"
|
||||||
) {
|
) {
|
||||||
// Return all metrics in the Prometheus exposition format
|
// Return all metrics in the Prometheus exposition format
|
||||||
if (this._mode === PromMode.Listen || this._mode == PromMode.Both) {
|
res.setHeader("Content-Type", this._register.contentType);
|
||||||
res.setHeader("Content-Type", this._register.contentType);
|
res.end(await this._register.metrics());
|
||||||
res.end(await this._register.metrics());
|
|
||||||
}
|
|
||||||
if (this._mode === PromMode.Relay || this._mode == PromMode.Both) {
|
|
||||||
res.setHeader("Content-Type", this._register.contentType);
|
|
||||||
res.end(await this._register.metrics());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
res.writeHead(404, { "Content-Type": "text/plain" });
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
||||||
res.write("404 Not Found - " + req.url + "\n");
|
res.write("404 Not Found - " + req.url + "\n");
|
||||||
|
@ -108,8 +114,10 @@ export class PromHelper {
|
||||||
mode_name = "listener";
|
mode_name = "listener";
|
||||||
} else if (mode === PromMode.Relay) {
|
} else if (mode === PromMode.Relay) {
|
||||||
mode_name = "relayer";
|
mode_name = "relayer";
|
||||||
} else if (mode === PromMode.Both) {
|
} else if (mode === PromMode.WalletMonitor) {
|
||||||
mode_name = "both";
|
mode_name = "wallet-monitor";
|
||||||
|
} else if (mode === PromMode.All) {
|
||||||
|
mode_name = "all";
|
||||||
}
|
}
|
||||||
|
|
||||||
this._register.setDefaultLabels({
|
this._register.setDefaultLabels({
|
||||||
|
@ -121,14 +129,18 @@ export class PromHelper {
|
||||||
|
|
||||||
this._mode = mode;
|
this._mode = mode;
|
||||||
// Register each metric
|
// Register each metric
|
||||||
if (this._mode === PromMode.Listen || this._mode == PromMode.Both) {
|
if (this._mode === PromMode.Listen || this._mode === PromMode.All) {
|
||||||
this._register.registerMetric(this.listenCounter);
|
this._register.registerMetric(this.listenCounter);
|
||||||
}
|
}
|
||||||
if (this._mode === PromMode.Relay || this._mode == PromMode.Both) {
|
if (this._mode === PromMode.Relay || this._mode === PromMode.All) {
|
||||||
this._register.registerMetric(this.successCounter);
|
this._register.registerMetric(this.successCounter);
|
||||||
|
this._register.registerMetric(this.confirmedCounter);
|
||||||
this._register.registerMetric(this.failureCounter);
|
this._register.registerMetric(this.failureCounter);
|
||||||
|
this._register.registerMetric(this.rollbackCounter);
|
||||||
this._register.registerMetric(this.alreadyExecutedCounter);
|
this._register.registerMetric(this.alreadyExecutedCounter);
|
||||||
this._register.registerMetric(this.redisQueue);
|
this._register.registerMetric(this.redisQueue);
|
||||||
|
}
|
||||||
|
if (this._mode === PromMode.WalletMonitor || this._mode === PromMode.All) {
|
||||||
this._register.registerMetric(this.walletBalance);
|
this._register.registerMetric(this.walletBalance);
|
||||||
}
|
}
|
||||||
// End registering metric
|
// End registering metric
|
||||||
|
@ -137,15 +149,25 @@ export class PromHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are the accessor methods for the metrics
|
// These are the accessor methods for the metrics
|
||||||
incSuccesses(chainId: ChainId) {
|
incSuccesses(chainId: ChainId, value?: number) {
|
||||||
this.successCounter
|
this.successCounter
|
||||||
.labels({ chain_name: chainIDStrings[chainId] || "Unknown" })
|
.labels({ chain_name: chainIDStrings[chainId] || "Unknown" })
|
||||||
.inc();
|
.inc(value);
|
||||||
}
|
}
|
||||||
incFailures(chainId: ChainId) {
|
incConfirmed(chainId: ChainId, value?: number) {
|
||||||
|
this.confirmedCounter
|
||||||
|
.labels({ chain_name: chainIDStrings[chainId] || "Unknown" })
|
||||||
|
.inc(value);
|
||||||
|
}
|
||||||
|
incFailures(chainId: ChainId, value?: number) {
|
||||||
this.failureCounter
|
this.failureCounter
|
||||||
.labels({ chain_name: chainIDStrings[chainId] || "Unknown" })
|
.labels({ chain_name: chainIDStrings[chainId] || "Unknown" })
|
||||||
.inc();
|
.inc(value);
|
||||||
|
}
|
||||||
|
incRollback(chainId: ChainId, value?: number) {
|
||||||
|
this.rollbackCounter
|
||||||
|
.labels({ chain_name: chainIDStrings[chainId] || "Unknown" })
|
||||||
|
.inc(value);
|
||||||
}
|
}
|
||||||
addCompleteTime(val: number) {
|
addCompleteTime(val: number) {
|
||||||
this.completeTime.observe(val);
|
this.completeTime.observe(val);
|
||||||
|
@ -160,9 +182,18 @@ export class PromHelper {
|
||||||
handleListenerMemqueue(size: number) {
|
handleListenerMemqueue(size: number) {
|
||||||
this.listenerMemqueue.set(size);
|
this.listenerMemqueue.set(size);
|
||||||
}
|
}
|
||||||
setRedisQueue(queue: RedisTables, size: number) {
|
setRedisQueue(
|
||||||
|
queue: RedisTables,
|
||||||
|
sourceChainId: ChainId,
|
||||||
|
targetChainId: ChainId,
|
||||||
|
size: number
|
||||||
|
) {
|
||||||
this.redisQueue
|
this.redisQueue
|
||||||
.labels({ queue: RedisTables[queue].toLowerCase() })
|
.labels({
|
||||||
|
queue: RedisTables[queue].toLowerCase(),
|
||||||
|
source_chain_name: chainIDStrings[sourceChainId],
|
||||||
|
target_chain_name: chainIDStrings[targetChainId],
|
||||||
|
})
|
||||||
.set(size);
|
.set(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
process.env.LOG_LEVEL = "debug";
|
||||||
|
process.env.PROM_PORT = "0";
|
||||||
|
process.env.REDIS_HOST = "localhost";
|
||||||
|
process.env.REDIS_PORT = "0";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
importCoreWasm,
|
||||||
|
setDefaultWasm,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
import { chainIDStrings } from "../utils/wormhole";
|
||||||
|
import {
|
||||||
|
createSourceToTargetMap,
|
||||||
|
incrementSourceToTargetMap,
|
||||||
|
} from "./redisHelper";
|
||||||
|
const TEST_KEY = `{"chain_id":3,"emitter_address":"0000000000000000000000007cf7b764e38a0a5e967972c1df77d432510564e2","sequence":77391}`;
|
||||||
|
const TEST_VAA_BYTES =
|
||||||
|
"01000000010d00d37d5af819b2230d7c2b0ad059d03f0410ee01fa05fba3ede9c180004d6e4cb36b8e4383318422a63705451632b3adc3ca85839e23e2c15408eb21e32e5dbbd20002a31149de339b417fbd9e06fdfb9644f48c3b3981811b170556785517f44316c4171367221ba4f3a0a756115c27fef6a636bfb6447862485884500664652bca920103c814d18dddb5816a8310b496d56cacaa9dac294aa25a6c9b4d194df20c5c7ffd22ca2fdbe389e4e05daac51159b2dd73d302eaf9cc9ddc9aa04de2ef4e07dbe3000455f4a08e1a96493129910237dc66db46d20e0baab9a54ee51587651724ddbe1423b4007802505796cace80b992444704af1a3b5f7813055d0beaeba2d93c25b301070b6732602bf0629dfd7ffc71b70900f4ea21ae4a3a03067df685de4f71965b157d6c5e9fcc3f275b64035e307fa71a9d64a1abd213ee00283e8e8c1ed7507d9e000860e4c5539dda95b5a5c3ad82c4fd9023456b095ca9ff1d51e3d3e673ff60805238b70089fdb2e9c00747f9f6d86a5b56bc9a81f3e53fbcb0d0256a1c2be4827e010a532463674858c045328bbcc632df851b0274709eb2bd139401df54fe6d049afe69ca807590c29fe2753b66a84ae1f99209e9e9d273d3a54865691168a9c79f31010b356897c0e0e23c9b3d99cda837fe09b1ef519ed479981473a832791db29b09a31be1cd2d2d64ddb16972d201dc694adea852544df180711d8baa6606f250a27b000c25ec035c97bb0cffcc61cd8b1280c5b03b8080e77a603198bcbffa3fcb946e4924fc201cbd24af179f89779107421edbdb8247bd85984c6b099f0d42611a9695010df05a74cb0924a0952ccf54e1539d6823c828a597176f284697e73ebcb082964f79585affe1f269873ec7eb8b7cf6f605a21fc3db0a22df409c2a30d41a866d00011095bdf09fb178e1ddd950e66e82fcfff99dc220b76f66b51a83513ce4b826ff4b053be0e290c424e4535f5b915fd1992102405c7cc3cffe086b87fccb942c084200118a15aae39395d490b2f5c6fb41e9f7d1ba8594905d62e8250ecc3bc46e71638f4fd35c7e711e30273fd49b8cb349517918eeb8e14885fabf449d10013449497d0012ea54ad52291bac7c031dfb103adf094fc9461758a32a15c93ae02a011126602b09af0a6a389924bd83bb57146962c083040ca6b467dabababb8e8819c277e6e60162586e2900013a2700030000000000000000000000007cf7b764e38a0a5e967972c1df77d432510564e20000000000012e4f000100000000000000000000000000000000000000000000000000000000026271da010000000000000000000000000000000000000000000000000000756c756e610003000000000000000000000000d2499424e5822dc6dadebec9518c1afc1b970be2000a00000000000000000000000000000000000000000000000000000000000017da";
|
||||||
|
test("should correctly increment sourceToTargetMap", async () => {
|
||||||
|
setDefaultWasm("node");
|
||||||
|
const { parse_vaa } = await importCoreWasm();
|
||||||
|
const knownChainIds = Object.keys(chainIDStrings).map(
|
||||||
|
(c) => Number(c) as ChainId
|
||||||
|
);
|
||||||
|
const sourceToTargetMap = createSourceToTargetMap(knownChainIds);
|
||||||
|
const redisClientMock: any = {
|
||||||
|
get: async () => `{"vaa_bytes":"${TEST_VAA_BYTES}"}`,
|
||||||
|
};
|
||||||
|
await incrementSourceToTargetMap(
|
||||||
|
TEST_KEY,
|
||||||
|
redisClientMock,
|
||||||
|
parse_vaa,
|
||||||
|
sourceToTargetMap
|
||||||
|
);
|
||||||
|
expect(sourceToTargetMap[3][1]).toBe(0);
|
||||||
|
expect(sourceToTargetMap[3][10]).toBe(1);
|
||||||
|
});
|
|
@ -1,8 +1,15 @@
|
||||||
import { ChainId, uint8ArrayToHex } from "@certusone/wormhole-sdk";
|
import {
|
||||||
|
ChainId,
|
||||||
|
hexToUint8Array,
|
||||||
|
importCoreWasm,
|
||||||
|
parseTransferPayload,
|
||||||
|
uint8ArrayToHex,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
import { Mutex } from "async-mutex";
|
import { Mutex } from "async-mutex";
|
||||||
import { createClient } from "redis";
|
import { createClient, RedisClientType } from "redis";
|
||||||
import { getCommonEnvironment } from "../configureEnv";
|
import { getCommonEnvironment } from "../configureEnv";
|
||||||
import { ParsedTransferPayload, ParsedVaa } from "../listener/validation";
|
import { ParsedTransferPayload, ParsedVaa } from "../listener/validation";
|
||||||
|
import { chainIDStrings } from "../utils/wormhole";
|
||||||
import { getScopedLogger } from "./logHelper";
|
import { getScopedLogger } from "./logHelper";
|
||||||
import { PromHelper } from "./promHelpers";
|
import { PromHelper } from "./promHelpers";
|
||||||
import { sleep } from "./utils";
|
import { sleep } from "./utils";
|
||||||
|
@ -224,6 +231,10 @@ export function storePayloadFromJson(json: string): StorePayload {
|
||||||
return JSON.parse(json);
|
return JSON.parse(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resetPayload(storePayload: StorePayload): StorePayload {
|
||||||
|
return initPayloadWithVAA(storePayload.vaa_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
export async function pushVaaToRedis(
|
export async function pushVaaToRedis(
|
||||||
parsedVAA: ParsedVaa<ParsedTransferPayload>,
|
parsedVAA: ParsedVaa<ParsedTransferPayload>,
|
||||||
hexVaa: string
|
hexVaa: string
|
||||||
|
@ -299,18 +310,67 @@ export async function demoteWorkingRedis() {
|
||||||
await redisClient.select(RedisTables.INCOMING);
|
await redisClient.select(RedisTables.INCOMING);
|
||||||
await redisClient.set(
|
await redisClient.set(
|
||||||
si_key,
|
si_key,
|
||||||
storePayloadToJson(
|
storePayloadToJson(resetPayload(storePayloadFromJson(si_value)))
|
||||||
initPayloadWithVAA(storePayloadFromJson(si_value).vaa_bytes)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
await redisClient.select(RedisTables.WORKING);
|
await redisClient.select(RedisTables.WORKING);
|
||||||
}
|
}
|
||||||
redisClient.quit();
|
redisClient.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SourceToTargetMap = {
|
||||||
|
[key in ChainId]: {
|
||||||
|
[key in ChainId]: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createSourceToTargetMap(
|
||||||
|
knownChainIds: ChainId[]
|
||||||
|
): SourceToTargetMap {
|
||||||
|
const sourceToTargetMap: SourceToTargetMap = {} as SourceToTargetMap;
|
||||||
|
for (const sourceKey of knownChainIds) {
|
||||||
|
sourceToTargetMap[sourceKey] = {} as { [key in ChainId]: number };
|
||||||
|
for (const targetKey of knownChainIds) {
|
||||||
|
sourceToTargetMap[sourceKey][targetKey] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sourceToTargetMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function incrementSourceToTargetMap(
|
||||||
|
key: string,
|
||||||
|
redisClient: RedisClientType<any>,
|
||||||
|
parse_vaa: Function,
|
||||||
|
sourceToTargetMap: SourceToTargetMap
|
||||||
|
): Promise<void> {
|
||||||
|
const parsedKey = storeKeyFromJson(key);
|
||||||
|
const si_value = await redisClient.get(key);
|
||||||
|
if (!si_value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parsedPayload = parseTransferPayload(
|
||||||
|
Buffer.from(
|
||||||
|
parse_vaa(hexToUint8Array(storePayloadFromJson(si_value).vaa_bytes))
|
||||||
|
.payload
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
sourceToTargetMap[parsedKey.chain_id as ChainId]?.[
|
||||||
|
parsedPayload.targetChain
|
||||||
|
] !== undefined
|
||||||
|
) {
|
||||||
|
sourceToTargetMap[parsedKey.chain_id as ChainId][
|
||||||
|
parsedPayload.targetChain
|
||||||
|
]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function monitorRedis(metrics: PromHelper) {
|
export async function monitorRedis(metrics: PromHelper) {
|
||||||
const scopedLogger = getScopedLogger(["monitorRedis"], logger);
|
const scopedLogger = getScopedLogger(["monitorRedis"], logger);
|
||||||
const TEN_SECONDS: number = 10000;
|
const TEN_SECONDS: number = 10000;
|
||||||
|
const { parse_vaa } = await importCoreWasm();
|
||||||
|
const knownChainIds = Object.keys(chainIDStrings).map(
|
||||||
|
(c) => Number(c) as ChainId
|
||||||
|
);
|
||||||
while (true) {
|
while (true) {
|
||||||
const redisClient = await connectToRedis();
|
const redisClient = await connectToRedis();
|
||||||
if (!redisClient) {
|
if (!redisClient) {
|
||||||
|
@ -318,9 +378,46 @@ export async function monitorRedis(metrics: PromHelper) {
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await redisClient.select(RedisTables.INCOMING);
|
await redisClient.select(RedisTables.INCOMING);
|
||||||
metrics.setRedisQueue(RedisTables.INCOMING, await redisClient.dbSize());
|
const incomingSourceToTargetMap =
|
||||||
|
createSourceToTargetMap(knownChainIds);
|
||||||
|
for await (const si_key of redisClient.scanIterator()) {
|
||||||
|
incrementSourceToTargetMap(
|
||||||
|
si_key,
|
||||||
|
redisClient,
|
||||||
|
parse_vaa,
|
||||||
|
incomingSourceToTargetMap
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const sourceKey of knownChainIds) {
|
||||||
|
for (const targetKey of knownChainIds) {
|
||||||
|
metrics.setRedisQueue(
|
||||||
|
RedisTables.INCOMING,
|
||||||
|
sourceKey,
|
||||||
|
targetKey,
|
||||||
|
incomingSourceToTargetMap[sourceKey][targetKey]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
await redisClient.select(RedisTables.WORKING);
|
await redisClient.select(RedisTables.WORKING);
|
||||||
metrics.setRedisQueue(RedisTables.WORKING, await redisClient.dbSize());
|
const workingSourceToTargetMap = createSourceToTargetMap(knownChainIds);
|
||||||
|
for await (const si_key of redisClient.scanIterator()) {
|
||||||
|
incrementSourceToTargetMap(
|
||||||
|
si_key,
|
||||||
|
redisClient,
|
||||||
|
parse_vaa,
|
||||||
|
workingSourceToTargetMap
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const sourceKey of knownChainIds) {
|
||||||
|
for (const targetKey of knownChainIds) {
|
||||||
|
metrics.setRedisQueue(
|
||||||
|
RedisTables.WORKING,
|
||||||
|
sourceKey,
|
||||||
|
targetKey,
|
||||||
|
workingSourceToTargetMap[sourceKey][targetKey]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
scopedLogger.error("Failed to get dbSize and set metrics!");
|
scopedLogger.error("Failed to get dbSize and set metrics!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,13 @@ import * as redisHelper from "./helpers/redisHelper";
|
||||||
import * as restListener from "./listener/rest_listen";
|
import * as restListener from "./listener/rest_listen";
|
||||||
import * as spyListener from "./listener/spy_listen";
|
import * as spyListener from "./listener/spy_listen";
|
||||||
import * as relayWorker from "./relayer/relay_worker";
|
import * as relayWorker from "./relayer/relay_worker";
|
||||||
|
import * as walletMonitor from "./monitor";
|
||||||
|
|
||||||
export enum ProcessType {
|
const ARG_LISTEN_ONLY = "--listen_only";
|
||||||
LISTEN_ONLY = "--listen_only",
|
const ARG_RELAY_ONLY = "--relay_only";
|
||||||
RELAY_ONLY = "--relay_only",
|
const ARG_WALLET_MONITOR_ONLY = "--wallet_monitor_only";
|
||||||
SPY_AND_RELAY = "spy and relay",
|
const ONLY_ONE_ARG_ERROR_MSG = `May only specify one of ${ARG_LISTEN_ONLY}, ${ARG_RELAY_ONLY}, or ${ARG_WALLET_MONITOR_ONLY}`;
|
||||||
}
|
const ONLY_ONE_ARG_ERROR_RESULT = `Multiple args found of ${ARG_LISTEN_ONLY}, ${ARG_RELAY_ONLY}, ${ARG_WALLET_MONITOR_ONLY}`;
|
||||||
|
|
||||||
setDefaultWasm("node");
|
setDefaultWasm("node");
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
|
@ -23,34 +24,49 @@ const logger = getLogger();
|
||||||
let runListen: boolean = true;
|
let runListen: boolean = true;
|
||||||
let runWorker: boolean = true;
|
let runWorker: boolean = true;
|
||||||
let runRest: boolean = true;
|
let runRest: boolean = true;
|
||||||
|
let runWalletMonitor: boolean = true;
|
||||||
let foundOne: boolean = false;
|
let foundOne: boolean = false;
|
||||||
let error: string = "";
|
let error: string = "";
|
||||||
|
|
||||||
for (let idx = 0; idx < process.argv.length; ++idx) {
|
for (let idx = 0; idx < process.argv.length; ++idx) {
|
||||||
if (process.argv[idx] === "--listen_only") {
|
if (process.argv[idx] === ARG_LISTEN_ONLY) {
|
||||||
if (foundOne) {
|
if (foundOne) {
|
||||||
logger.error('May only specify one of "--listen_only" or "--relay_only"');
|
logger.error(ONLY_ONE_ARG_ERROR_MSG);
|
||||||
error = "Multiple args found of --listen_only and --relay_only";
|
error = ONLY_ONE_ARG_ERROR_RESULT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("spy_relay is running in listen only mode");
|
logger.info("spy_relay is running in listen only mode");
|
||||||
runWorker = false;
|
runWorker = false;
|
||||||
|
runWalletMonitor = false;
|
||||||
foundOne = true;
|
foundOne = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.argv[idx] === "--relay_only") {
|
if (process.argv[idx] === ARG_RELAY_ONLY) {
|
||||||
if (foundOne) {
|
if (foundOne) {
|
||||||
logger.error(
|
logger.error(ONLY_ONE_ARG_ERROR_MSG);
|
||||||
'May only specify one of "--listen_only", "--relay_only" or "--rest_only"'
|
error = ONLY_ONE_ARG_ERROR_RESULT;
|
||||||
);
|
|
||||||
error = "Multiple args found of --listen_only and --relay_only";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("spy_relay is running in relay only mode");
|
logger.info("spy_relay is running in relay only mode");
|
||||||
runListen = false;
|
runListen = false;
|
||||||
runRest = false;
|
runRest = false;
|
||||||
|
runWalletMonitor = false;
|
||||||
|
foundOne = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[idx] === ARG_WALLET_MONITOR_ONLY) {
|
||||||
|
if (foundOne) {
|
||||||
|
logger.error(ONLY_ONE_ARG_ERROR_MSG);
|
||||||
|
error = ONLY_ONE_ARG_ERROR_RESULT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("spy_relay is running in wallet monitor only mode");
|
||||||
|
runListen = false;
|
||||||
|
runRest = false;
|
||||||
|
runWorker = false;
|
||||||
foundOne = true;
|
foundOne = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,22 +79,25 @@ if (
|
||||||
!error &&
|
!error &&
|
||||||
spyListener.init(runListen) &&
|
spyListener.init(runListen) &&
|
||||||
relayWorker.init(runWorker) &&
|
relayWorker.init(runWorker) &&
|
||||||
restListener.init(runRest)
|
restListener.init(runRest) &&
|
||||||
|
walletMonitor.init(runWalletMonitor)
|
||||||
) {
|
) {
|
||||||
const commonEnv = getCommonEnvironment();
|
const commonEnv = getCommonEnvironment();
|
||||||
const { promPort, readinessPort } = commonEnv;
|
const { promPort, readinessPort } = commonEnv;
|
||||||
logger.info("prometheus client listening on port " + promPort);
|
logger.info("prometheus client listening on port " + promPort);
|
||||||
let promClient: PromHelper;
|
let promClient: PromHelper;
|
||||||
const runBoth: boolean = runListen && runWorker;
|
const runAll: boolean = runListen && runWorker && runWalletMonitor;
|
||||||
if (runBoth) {
|
if (runAll) {
|
||||||
promClient = new PromHelper("spy_relay", promPort, PromMode.Both);
|
promClient = new PromHelper("spy_relay", promPort, PromMode.All);
|
||||||
} else if (runListen) {
|
} else if (runListen) {
|
||||||
promClient = new PromHelper("spy_relay", promPort, PromMode.Listen);
|
promClient = new PromHelper("spy_relay", promPort, PromMode.Listen);
|
||||||
} else if (runWorker) {
|
} else if (runWorker) {
|
||||||
promClient = new PromHelper("spy_relay", promPort, PromMode.Relay);
|
promClient = new PromHelper("spy_relay", promPort, PromMode.Relay);
|
||||||
|
} else if (runWalletMonitor) {
|
||||||
|
promClient = new PromHelper("spy_relay", promPort, PromMode.WalletMonitor);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Invalid run mode for Prometheus");
|
logger.error("Invalid run mode for Prometheus");
|
||||||
promClient = new PromHelper("spy_relay", promPort, PromMode.Both);
|
promClient = new PromHelper("spy_relay", promPort, PromMode.All);
|
||||||
}
|
}
|
||||||
|
|
||||||
redisHelper.init(promClient);
|
redisHelper.init(promClient);
|
||||||
|
@ -86,6 +105,7 @@ if (
|
||||||
if (runListen) spyListener.run(promClient);
|
if (runListen) spyListener.run(promClient);
|
||||||
if (runWorker) relayWorker.run(promClient);
|
if (runWorker) relayWorker.run(promClient);
|
||||||
if (runRest) restListener.run();
|
if (runRest) restListener.run();
|
||||||
|
if (runWalletMonitor) walletMonitor.run(promClient);
|
||||||
|
|
||||||
if (readinessPort) {
|
if (readinessPort) {
|
||||||
const Net = require("net");
|
const Net = require("net");
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { getRelayerEnvironment, RelayerEnvironment } from "../configureEnv";
|
||||||
|
import { getLogger } from "../helpers/logHelper";
|
||||||
|
import { PromHelper } from "../helpers/promHelpers";
|
||||||
|
import { collectWallets } from "./walletMonitor";
|
||||||
|
|
||||||
|
let metrics: PromHelper;
|
||||||
|
|
||||||
|
const logger = getLogger();
|
||||||
|
let relayerEnv: RelayerEnvironment;
|
||||||
|
|
||||||
|
export function init(runWorker: boolean): boolean {
|
||||||
|
if (!runWorker) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
relayerEnv = getRelayerEnvironment();
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
"Encountered error while initiating the monitor environment: " + e
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function run(ph: PromHelper) {
|
||||||
|
metrics = ph;
|
||||||
|
|
||||||
|
try {
|
||||||
|
collectWallets(metrics);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Failed to kick off collectWallets: " + e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
require("../helpers/loadConfig");
|
||||||
|
process.env.LOG_DIR = ".";
|
||||||
|
|
||||||
|
import { CHAIN_ID_TERRA } from "@certusone/wormhole-sdk";
|
||||||
|
import { jest, test } from "@jest/globals";
|
||||||
|
import { LCDClient } from "@terra-money/terra.js";
|
||||||
|
import { ChainConfigInfo } from "../configureEnv";
|
||||||
|
import { calcLocalAddressesTerra, pullTerraBalance } from "./walletMonitor";
|
||||||
|
// import { pullEVMBalance } from "./walletMonitor";
|
||||||
|
|
||||||
|
jest.setTimeout(300000);
|
||||||
|
|
||||||
|
// const bscChainConfig: ChainConfigInfo = {
|
||||||
|
// chainId: CHAIN_ID_BSC,
|
||||||
|
// chainName: "BSC",
|
||||||
|
// nativeCurrencySymbol: "BNB",
|
||||||
|
// nodeUrl: "https://bsc-dataseed.binance.org",
|
||||||
|
// tokenBridgeAddress: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7",
|
||||||
|
// wrappedAsset: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
|
||||||
|
// };
|
||||||
|
// const bscPublicKey = "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7"; // Token Bridge
|
||||||
|
// const bscTokens = [
|
||||||
|
// "0xfA54fF1a158B5189Ebba6ae130CEd6bbd3aEA76e", // SOL
|
||||||
|
// "0x4DB5a66E937A9F4473fA95b1cAF1d1E1D62E29EA", // WETH
|
||||||
|
// "0x156ab3346823B651294766e23e6Cf87254d68962", // LUNA
|
||||||
|
// "0x3d4350cD54aeF9f9b2C29435e0fa809957B3F30a", // UST
|
||||||
|
// "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", // WBNB
|
||||||
|
// "0xc836d8dC361E44DbE64c4862D55BA041F88Ddd39", // WMATIC
|
||||||
|
// "0x96412902aa9aFf61E13f085e70D3152C6ef2a817", // WAVAX
|
||||||
|
// "0x6c6D604D3f07aBE287C1A3dF0281e999A83495C0", // wROSE
|
||||||
|
// "0xbF8413EE8612E0E4f66Aa63B5ebE27f3C5883d47", // WFTM
|
||||||
|
// "0xB04906e95AB5D797aDA81508115611fee694c2b3", // USDC
|
||||||
|
// "0x524bC91Dc82d6b90EF29F76A3ECAaBAffFD490Bc", // USDT
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// test("should pull EVM token balances", async () => {
|
||||||
|
// for (let address of bscTokens) {
|
||||||
|
// const balance = await pullEVMBalance(bscChainConfig, bscPublicKey, address);
|
||||||
|
// console.log(balance);
|
||||||
|
// expect(balance).toBeTruthy();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
const terraChainConfig: ChainConfigInfo = {
|
||||||
|
chainId: CHAIN_ID_TERRA,
|
||||||
|
chainName: "Terra",
|
||||||
|
nativeCurrencySymbol: "UST",
|
||||||
|
nodeUrl: "https://fcd.terra.dev",
|
||||||
|
tokenBridgeAddress: "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf",
|
||||||
|
terraName: "mainnet",
|
||||||
|
terraChainId: "columbus-5",
|
||||||
|
terraCoin: "uluna",
|
||||||
|
terraGasPriceUrl: "https://fcd.terra.dev/v1/txs/gas_prices",
|
||||||
|
};
|
||||||
|
|
||||||
|
const supportedTokens = require("../../config/mainnet/supportedTokens.json");
|
||||||
|
|
||||||
|
test("should pull Terra token balances", async () => {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
terraChainConfig.terraChainId &&
|
||||||
|
terraChainConfig.terraCoin &&
|
||||||
|
terraChainConfig.terraGasPriceUrl &&
|
||||||
|
terraChainConfig.terraName
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error("Terra relay was called without proper instantiation.");
|
||||||
|
}
|
||||||
|
const lcdConfig = {
|
||||||
|
URL: terraChainConfig.nodeUrl,
|
||||||
|
chainID: terraChainConfig.terraChainId,
|
||||||
|
name: terraChainConfig.terraName,
|
||||||
|
};
|
||||||
|
const lcd = new LCDClient(lcdConfig);
|
||||||
|
const localAddresses = await calcLocalAddressesTerra(
|
||||||
|
lcd,
|
||||||
|
supportedTokens,
|
||||||
|
terraChainConfig
|
||||||
|
);
|
||||||
|
expect(localAddresses.length).toBeGreaterThan(0);
|
||||||
|
for (const tokenAddress of localAddresses) {
|
||||||
|
const balance = await pullTerraBalance(
|
||||||
|
lcd,
|
||||||
|
terraChainConfig.tokenBridgeAddress,
|
||||||
|
tokenAddress
|
||||||
|
);
|
||||||
|
console.log(balance);
|
||||||
|
expect(balance).toBeDefined();
|
||||||
|
}
|
||||||
|
});
|
|
@ -26,7 +26,7 @@ import { getMetaplexData, sleep } from "../helpers/utils";
|
||||||
import { getEthereumToken } from "../utils/ethereum";
|
import { getEthereumToken } from "../utils/ethereum";
|
||||||
import { getMultipleAccountsRPC } from "../utils/solana";
|
import { getMultipleAccountsRPC } from "../utils/solana";
|
||||||
import { formatNativeDenom } from "../utils/terra";
|
import { formatNativeDenom } from "../utils/terra";
|
||||||
import { newProvider } from "./evm";
|
import { newProvider } from "../relayer/evm";
|
||||||
|
|
||||||
let env: RelayerEnvironment;
|
let env: RelayerEnvironment;
|
||||||
const logger = getScopedLogger(["walletMonitor"]);
|
const logger = getScopedLogger(["walletMonitor"]);
|
||||||
|
@ -55,8 +55,6 @@ function init() {
|
||||||
|
|
||||||
async function pullBalances(metrics: PromHelper): Promise<WalletBalance[]> {
|
async function pullBalances(metrics: PromHelper): Promise<WalletBalance[]> {
|
||||||
//TODO loop through all the chain configs, calc the public keys, pull their balances, and push to a combo of the loggers and prmometheus
|
//TODO loop through all the chain configs, calc the public keys, pull their balances, and push to a combo of the loggers and prmometheus
|
||||||
|
|
||||||
logger.debug("pulling balances...");
|
|
||||||
if (!env) {
|
if (!env) {
|
||||||
logger.error("pullBalances() - no env");
|
logger.error("pullBalances() - no env");
|
||||||
return [];
|
return [];
|
||||||
|
@ -67,63 +65,50 @@ async function pullBalances(metrics: PromHelper): Promise<WalletBalance[]> {
|
||||||
}
|
}
|
||||||
const balancePromises: Promise<WalletBalance[]>[] = [];
|
const balancePromises: Promise<WalletBalance[]>[] = [];
|
||||||
for (const chainInfo of env.supportedChains) {
|
for (const chainInfo of env.supportedChains) {
|
||||||
if (!chainInfo) break;
|
if (!chainInfo) continue;
|
||||||
for (const privateKey of chainInfo.walletPrivateKey || []) {
|
try {
|
||||||
try {
|
if (chainInfo.chainId === CHAIN_ID_SOLANA) {
|
||||||
if (!privateKey) break;
|
for (const solanaPrivateKey of chainInfo.solanaPrivateKey || []) {
|
||||||
logger.debug(
|
try {
|
||||||
"Attempting to pull native balance for chainId: " + chainInfo.chainId
|
balancePromises.push(
|
||||||
);
|
pullSolanaNativeBalance(chainInfo, solanaPrivateKey)
|
||||||
if (isEVMChain(chainInfo.chainId)) {
|
);
|
||||||
logger.info("Attempting to pull EVM native balance...");
|
balancePromises.push(
|
||||||
|
pullSolanaTokenBalances(chainInfo, solanaPrivateKey)
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error(
|
||||||
|
"pulling balances failed failed for chain: " + chainInfo.chainName
|
||||||
|
);
|
||||||
|
if (e && e.stack) {
|
||||||
|
logger.error(e.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isEVMChain(chainInfo.chainId)) {
|
||||||
|
for (const privateKey of chainInfo.walletPrivateKey || []) {
|
||||||
try {
|
try {
|
||||||
balancePromises.push(pullEVMNativeBalance(chainInfo, privateKey));
|
balancePromises.push(pullEVMNativeBalance(chainInfo, privateKey));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("pullEVMNativeBalance() failed: " + e);
|
logger.error("pullEVMNativeBalance() failed: " + e);
|
||||||
}
|
}
|
||||||
logger.info("Attempting to pull EVM non-native balance...");
|
|
||||||
pullAllEVMTokens(env.supportedTokens, chainInfo, metrics);
|
|
||||||
} else if (chainInfo.chainId === CHAIN_ID_TERRA) {
|
|
||||||
logger.info("Attempting to pull TERRA native balance...");
|
|
||||||
balancePromises.push(pullTerraNativeBalance(chainInfo, privateKey));
|
|
||||||
logger.info("Attempting to pull TERRA non-native balance...");
|
|
||||||
balancePromises.push(
|
|
||||||
pullAllTerraTokens(env.supportedTokens, chainInfo)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
logger.error(
|
|
||||||
"Invalid chain ID in wallet monitor " + chainInfo.chainId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
logger.error(
|
|
||||||
"pulling balances failed failed for chain: " + chainInfo.chainName
|
|
||||||
);
|
|
||||||
if (e && e.stack) {
|
|
||||||
logger.error(e.stack);
|
|
||||||
}
|
}
|
||||||
|
// TODO one day this will spin up independent watchers that time themselves
|
||||||
|
// purposefully not awaited
|
||||||
|
pullAllEVMTokens(env.supportedTokens, chainInfo, metrics);
|
||||||
|
} else if (chainInfo.chainId === CHAIN_ID_TERRA) {
|
||||||
|
// TODO one day this will spin up independent watchers that time themselves
|
||||||
|
// purposefully not awaited
|
||||||
|
pullAllTerraBalances(env.supportedTokens, chainInfo, metrics);
|
||||||
|
} else {
|
||||||
|
logger.error("Invalid chain ID in wallet monitor " + chainInfo.chainId);
|
||||||
}
|
}
|
||||||
}
|
} catch (e: any) {
|
||||||
|
logger.error(
|
||||||
for (const solanaPrivateKey of chainInfo.solanaPrivateKey || []) {
|
"pulling balances failed failed for chain: " + chainInfo.chainName
|
||||||
try {
|
);
|
||||||
if (chainInfo.chainId === CHAIN_ID_SOLANA) {
|
if (e && e.stack) {
|
||||||
logger.info("pullBalances() - calling pullSolanaNativeBalance...");
|
logger.error(e.stack);
|
||||||
balancePromises.push(
|
|
||||||
pullSolanaNativeBalance(chainInfo, solanaPrivateKey)
|
|
||||||
);
|
|
||||||
logger.info("pullBalances() - calling pullSolanaTokenBalances...");
|
|
||||||
balancePromises.push(
|
|
||||||
pullSolanaTokenBalances(chainInfo, solanaPrivateKey)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
logger.error(
|
|
||||||
"pulling balances failed failed for chain: " + chainInfo.chainName
|
|
||||||
);
|
|
||||||
if (e && e.stack) {
|
|
||||||
logger.error(e.stack);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,83 +122,40 @@ async function pullBalances(metrics: PromHelper): Promise<WalletBalance[]> {
|
||||||
return balances;
|
return balances;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pullEVMBalance(
|
export async function pullTerraBalance(
|
||||||
chainInfo: ChainConfigInfo,
|
lcd: LCDClient,
|
||||||
publicAddress: string,
|
walletAddress: string,
|
||||||
tokenAddress: string
|
|
||||||
): Promise<WalletBalance> {
|
|
||||||
let provider = newProvider(chainInfo.nodeUrl);
|
|
||||||
|
|
||||||
const token = await getEthereumToken(tokenAddress, provider);
|
|
||||||
const decimals = await token.decimals();
|
|
||||||
const balance = await token.balanceOf(publicAddress);
|
|
||||||
const symbol = await token.symbol();
|
|
||||||
const balanceFormatted = formatUnits(balance, decimals);
|
|
||||||
|
|
||||||
return {
|
|
||||||
chainId: chainInfo.chainId,
|
|
||||||
balanceAbs: balance.toString(),
|
|
||||||
balanceFormatted: balanceFormatted,
|
|
||||||
currencyName: symbol,
|
|
||||||
currencyAddressNative: tokenAddress,
|
|
||||||
isNative: false,
|
|
||||||
walletAddress: publicAddress,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pullTerraBalance(
|
|
||||||
chainInfo: ChainConfigInfo,
|
|
||||||
walletPrivateKey: string,
|
|
||||||
tokenAddress: string
|
tokenAddress: string
|
||||||
): Promise<WalletBalance | undefined> {
|
): Promise<WalletBalance | undefined> {
|
||||||
if (
|
try {
|
||||||
!(
|
const tokenInfo: any = await lcd.wasm.contractQuery(tokenAddress, {
|
||||||
chainInfo.terraChainId &&
|
token_info: {},
|
||||||
chainInfo.terraCoin &&
|
});
|
||||||
chainInfo.terraGasPriceUrl &&
|
const balanceInfo: any = await lcd.wasm.contractQuery(tokenAddress, {
|
||||||
chainInfo.terraName
|
balance: {
|
||||||
)
|
address: walletAddress,
|
||||||
) {
|
},
|
||||||
logger.error("Terra relay was called without proper instantiation.");
|
});
|
||||||
throw new Error("Terra relay was called without proper instantiation.");
|
|
||||||
|
if (!tokenInfo || !balanceInfo) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
chainId: CHAIN_ID_TERRA,
|
||||||
|
balanceAbs: balanceInfo?.balance?.toString() || "0",
|
||||||
|
balanceFormatted: formatUnits(
|
||||||
|
balanceInfo?.balance?.toString() || "0",
|
||||||
|
tokenInfo.decimals
|
||||||
|
),
|
||||||
|
currencyName: tokenInfo.symbol,
|
||||||
|
currencyAddressNative: tokenAddress,
|
||||||
|
isNative: false,
|
||||||
|
walletAddress: walletAddress,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Failed to fetch terra balance for %s", tokenAddress);
|
||||||
}
|
}
|
||||||
const lcdConfig = {
|
|
||||||
URL: chainInfo.nodeUrl,
|
|
||||||
chainID: chainInfo.terraChainId,
|
|
||||||
name: chainInfo.terraName,
|
|
||||||
};
|
|
||||||
const lcd = new LCDClient(lcdConfig);
|
|
||||||
const mk = new MnemonicKey({
|
|
||||||
mnemonic: walletPrivateKey,
|
|
||||||
});
|
|
||||||
const wallet = lcd.wallet(mk);
|
|
||||||
const walletAddress = wallet.key.accAddress;
|
|
||||||
|
|
||||||
const tokenInfo: any = await lcd.wasm.contractQuery(tokenAddress, {
|
|
||||||
token_info: {},
|
|
||||||
});
|
|
||||||
const balanceInfo: any = lcd.wasm.contractQuery(tokenAddress, {
|
|
||||||
balance: {
|
|
||||||
address: walletAddress,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tokenInfo || !balanceInfo) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
chainId: CHAIN_ID_TERRA,
|
|
||||||
balanceAbs: balanceInfo?.balance?.toString() || "0",
|
|
||||||
balanceFormatted: formatUnits(
|
|
||||||
balanceInfo?.balance?.toString() || "0",
|
|
||||||
tokenInfo.decimals
|
|
||||||
),
|
|
||||||
currencyName: tokenInfo.symbol,
|
|
||||||
currencyAddressNative: tokenAddress,
|
|
||||||
isNative: false,
|
|
||||||
walletAddress: walletAddress,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pullSolanaTokenBalances(
|
async function pullSolanaTokenBalances(
|
||||||
|
@ -294,56 +236,38 @@ async function pullEVMNativeBalance(
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pullTerraNativeBalance(
|
async function pullTerraNativeBalance(
|
||||||
|
lcd: LCDClient,
|
||||||
chainInfo: ChainConfigInfo,
|
chainInfo: ChainConfigInfo,
|
||||||
privateKey: string
|
walletAddress: string
|
||||||
): Promise<WalletBalance[]> {
|
): Promise<WalletBalance[]> {
|
||||||
const output: WalletBalance[] = [];
|
try {
|
||||||
if (
|
const output: WalletBalance[] = [];
|
||||||
!(
|
const [coins] = await lcd.bank.balance(walletAddress);
|
||||||
chainInfo.terraChainId &&
|
// coins doesn't support reduce
|
||||||
chainInfo.terraCoin &&
|
const balancePairs = coins.map(({ amount, denom }) => [denom, amount]);
|
||||||
chainInfo.terraGasPriceUrl &&
|
const balance = balancePairs.reduce((obj, current) => {
|
||||||
chainInfo.terraName
|
obj[current[0].toString()] = current[1].toString();
|
||||||
)
|
return obj;
|
||||||
) {
|
}, {} as TerraNativeBalances);
|
||||||
logger.error(
|
Object.keys(balance).forEach((key) => {
|
||||||
"Terra wallet balance was called without proper instantiation."
|
output.push({
|
||||||
);
|
chainId: chainInfo.chainId,
|
||||||
throw new Error(
|
balanceAbs: balance[key],
|
||||||
"Terra wallet balance was called without proper instantiation."
|
balanceFormatted: formatUnits(balance[key], 6).toString(),
|
||||||
);
|
currencyName: formatNativeDenom(key),
|
||||||
}
|
currencyAddressNative: key,
|
||||||
const lcdConfig = {
|
isNative: true,
|
||||||
URL: chainInfo.nodeUrl,
|
walletAddress: walletAddress,
|
||||||
chainID: chainInfo.terraChainId,
|
});
|
||||||
name: chainInfo.terraName,
|
|
||||||
};
|
|
||||||
const lcd = new LCDClient(lcdConfig);
|
|
||||||
const mk = new MnemonicKey({
|
|
||||||
mnemonic: privateKey,
|
|
||||||
});
|
|
||||||
const wallet = lcd.wallet(mk);
|
|
||||||
const walletAddress = wallet.key.accAddress;
|
|
||||||
|
|
||||||
const [coins] = await lcd.bank.balance(walletAddress);
|
|
||||||
// coins doesn't support reduce
|
|
||||||
const balancePairs = coins.map(({ amount, denom }) => [denom, amount]);
|
|
||||||
const balance = balancePairs.reduce((obj, current) => {
|
|
||||||
obj[current[0].toString()] = current[1].toString();
|
|
||||||
return obj;
|
|
||||||
}, {} as TerraNativeBalances);
|
|
||||||
Object.keys(balance).forEach((key) => {
|
|
||||||
output.push({
|
|
||||||
chainId: chainInfo.chainId,
|
|
||||||
balanceAbs: balance[key],
|
|
||||||
balanceFormatted: formatUnits(balance[key], 6).toString(),
|
|
||||||
currencyName: formatNativeDenom(key),
|
|
||||||
currencyAddressNative: key,
|
|
||||||
isNative: true,
|
|
||||||
walletAddress: walletAddress,
|
|
||||||
});
|
});
|
||||||
});
|
return output;
|
||||||
return output;
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
"Failed to fetch terra native balances for wallet %s",
|
||||||
|
walletAddress
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pullSolanaNativeBalance(
|
async function pullSolanaNativeBalance(
|
||||||
|
@ -450,32 +374,11 @@ async function calcLocalAddressesEVM(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function calcLocalAddressesTerra(
|
export async function calcLocalAddressesTerra(
|
||||||
|
lcd: LCDClient,
|
||||||
supportedTokens: SupportedToken[],
|
supportedTokens: SupportedToken[],
|
||||||
chainConfigInfo: ChainConfigInfo
|
chainConfigInfo: ChainConfigInfo
|
||||||
) {
|
) {
|
||||||
if (
|
|
||||||
!(
|
|
||||||
chainConfigInfo.terraChainId &&
|
|
||||||
chainConfigInfo.terraCoin &&
|
|
||||||
chainConfigInfo.terraGasPriceUrl &&
|
|
||||||
chainConfigInfo.terraName
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
logger.error(
|
|
||||||
"Terra wallet balance was called without proper instantiation."
|
|
||||||
);
|
|
||||||
throw new Error(
|
|
||||||
"Terra wallet balance was called without proper instantiation."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const lcdConfig = {
|
|
||||||
URL: chainConfigInfo.nodeUrl,
|
|
||||||
chainID: chainConfigInfo.terraChainId,
|
|
||||||
name: chainConfigInfo.terraName,
|
|
||||||
};
|
|
||||||
const lcd = new LCDClient(lcdConfig);
|
|
||||||
|
|
||||||
const output: string[] = [];
|
const output: string[] = [];
|
||||||
for (const supportedToken of supportedTokens) {
|
for (const supportedToken of supportedTokens) {
|
||||||
if (supportedToken.chainId === chainConfigInfo.chainId) {
|
if (supportedToken.chainId === chainConfigInfo.chainId) {
|
||||||
|
@ -519,79 +422,115 @@ async function pullAllEVMTokens(
|
||||||
chainConfig: ChainConfigInfo,
|
chainConfig: ChainConfigInfo,
|
||||||
metrics: PromHelper
|
metrics: PromHelper
|
||||||
) {
|
) {
|
||||||
let provider = newProvider(
|
try {
|
||||||
chainConfig.nodeUrl,
|
let provider = newProvider(
|
||||||
true
|
chainConfig.nodeUrl,
|
||||||
) as ethers.providers.JsonRpcBatchProvider;
|
true
|
||||||
const localAddresses = await calcLocalAddressesEVM(
|
) as ethers.providers.JsonRpcBatchProvider;
|
||||||
provider,
|
const localAddresses = await calcLocalAddressesEVM(
|
||||||
supportedTokens,
|
provider,
|
||||||
chainConfig
|
supportedTokens,
|
||||||
);
|
chainConfig
|
||||||
if (!chainConfig.walletPrivateKey) {
|
);
|
||||||
return;
|
if (!chainConfig.walletPrivateKey) {
|
||||||
}
|
return;
|
||||||
for (const privateKey of chainConfig.walletPrivateKey) {
|
|
||||||
try {
|
|
||||||
const publicAddress = await new ethers.Wallet(privateKey).getAddress();
|
|
||||||
const tokens = await Promise.all(
|
|
||||||
localAddresses.map((tokenAddress) =>
|
|
||||||
getEthereumToken(tokenAddress, provider)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const tokenInfos = await Promise.all(
|
|
||||||
tokens.map((token) =>
|
|
||||||
Promise.all([
|
|
||||||
token.decimals(),
|
|
||||||
token.balanceOf(publicAddress),
|
|
||||||
token.symbol(),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const balances = tokenInfos.map(([decimals, balance, symbol], idx) => ({
|
|
||||||
chainId: chainConfig.chainId,
|
|
||||||
balanceAbs: balance.toString(),
|
|
||||||
balanceFormatted: formatUnits(balance, decimals),
|
|
||||||
currencyName: symbol,
|
|
||||||
currencyAddressNative: localAddresses[idx],
|
|
||||||
isNative: false,
|
|
||||||
walletAddress: publicAddress,
|
|
||||||
}));
|
|
||||||
metrics.handleWalletBalances(balances);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(
|
|
||||||
"pollEVMBalance failed: for tokens " +
|
|
||||||
JSON.stringify(localAddresses) +
|
|
||||||
" on chain " +
|
|
||||||
chainConfig.chainId +
|
|
||||||
", error: " +
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
for (const privateKey of chainConfig.walletPrivateKey) {
|
||||||
|
try {
|
||||||
|
const publicAddress = await new ethers.Wallet(privateKey).getAddress();
|
||||||
|
const tokens = await Promise.all(
|
||||||
|
localAddresses.map((tokenAddress) =>
|
||||||
|
getEthereumToken(tokenAddress, provider)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const tokenInfos = await Promise.all(
|
||||||
|
tokens.map((token) =>
|
||||||
|
Promise.all([
|
||||||
|
token.decimals(),
|
||||||
|
token.balanceOf(publicAddress),
|
||||||
|
token.symbol(),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const balances = tokenInfos.map(([decimals, balance, symbol], idx) => ({
|
||||||
|
chainId: chainConfig.chainId,
|
||||||
|
balanceAbs: balance.toString(),
|
||||||
|
balanceFormatted: formatUnits(balance, decimals),
|
||||||
|
currencyName: symbol,
|
||||||
|
currencyAddressNative: localAddresses[idx],
|
||||||
|
isNative: false,
|
||||||
|
walletAddress: publicAddress,
|
||||||
|
}));
|
||||||
|
metrics.handleWalletBalances(balances);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
"pullAllEVMTokens failed: for tokens " +
|
||||||
|
JSON.stringify(localAddresses) +
|
||||||
|
" on chain " +
|
||||||
|
chainConfig.chainId +
|
||||||
|
", error: " +
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
"pullAllEVMTokens failed: for chain " +
|
||||||
|
chainConfig.chainId +
|
||||||
|
", error: " +
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pullAllTerraTokens(
|
async function pullAllTerraBalances(
|
||||||
supportedTokens: SupportedToken[],
|
supportedTokens: SupportedToken[],
|
||||||
chainConfig: ChainConfigInfo
|
chainConfig: ChainConfigInfo,
|
||||||
|
metrics: PromHelper
|
||||||
) {
|
) {
|
||||||
|
let balances: WalletBalance[] = [];
|
||||||
|
if (!chainConfig.walletPrivateKey) {
|
||||||
|
return balances;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
chainConfig.terraChainId &&
|
||||||
|
chainConfig.terraCoin &&
|
||||||
|
chainConfig.terraGasPriceUrl &&
|
||||||
|
chainConfig.terraName
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
logger.error("Terra relay was called without proper instantiation.");
|
||||||
|
throw new Error("Terra relay was called without proper instantiation.");
|
||||||
|
}
|
||||||
|
const lcdConfig = {
|
||||||
|
URL: chainConfig.nodeUrl,
|
||||||
|
chainID: chainConfig.terraChainId,
|
||||||
|
name: chainConfig.terraName,
|
||||||
|
};
|
||||||
|
const lcd = new LCDClient(lcdConfig);
|
||||||
const localAddresses = await calcLocalAddressesTerra(
|
const localAddresses = await calcLocalAddressesTerra(
|
||||||
|
lcd,
|
||||||
supportedTokens,
|
supportedTokens,
|
||||||
chainConfig
|
chainConfig
|
||||||
);
|
);
|
||||||
const output: WalletBalance[] = [];
|
|
||||||
if (!chainConfig.walletPrivateKey) {
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
for (const privateKey of chainConfig.walletPrivateKey) {
|
for (const privateKey of chainConfig.walletPrivateKey) {
|
||||||
|
const mk = new MnemonicKey({
|
||||||
|
mnemonic: privateKey,
|
||||||
|
});
|
||||||
|
const wallet = lcd.wallet(mk);
|
||||||
|
const walletAddress = wallet.key.accAddress;
|
||||||
|
balances = [
|
||||||
|
...balances,
|
||||||
|
...(await pullTerraNativeBalance(lcd, chainConfig, walletAddress)),
|
||||||
|
];
|
||||||
for (const address of localAddresses) {
|
for (const address of localAddresses) {
|
||||||
const balance = await pullTerraBalance(chainConfig, privateKey, address);
|
const balance = await pullTerraBalance(lcd, walletAddress, address);
|
||||||
if (balance) {
|
if (balance) {
|
||||||
output.push(balance);
|
balances.push(balance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// logger.debug("pullAllTerraTokens() - returning %o", output);
|
|
||||||
|
|
||||||
return output;
|
metrics.handleWalletBalances(balances);
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
Bridge__factory,
|
||||||
CHAIN_ID_POLYGON,
|
CHAIN_ID_POLYGON,
|
||||||
getIsTransferCompletedEth,
|
getIsTransferCompletedEth,
|
||||||
hexToUint8Array,
|
hexToUint8Array,
|
||||||
|
@ -9,6 +10,7 @@ import { Signer } from "@ethersproject/abstract-signer";
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import { ChainConfigInfo } from "../configureEnv";
|
import { ChainConfigInfo } from "../configureEnv";
|
||||||
import { getScopedLogger, ScopedLogger } from "../helpers/logHelper";
|
import { getScopedLogger, ScopedLogger } from "../helpers/logHelper";
|
||||||
|
import { PromHelper } from "../helpers/promHelpers";
|
||||||
|
|
||||||
export function newProvider(
|
export function newProvider(
|
||||||
url: string,
|
url: string,
|
||||||
|
@ -31,7 +33,8 @@ export async function relayEVM(
|
||||||
unwrapNative: boolean,
|
unwrapNative: boolean,
|
||||||
checkOnly: boolean,
|
checkOnly: boolean,
|
||||||
walletPrivateKey: string,
|
walletPrivateKey: string,
|
||||||
relayLogger: ScopedLogger
|
relayLogger: ScopedLogger,
|
||||||
|
metrics: PromHelper
|
||||||
) {
|
) {
|
||||||
const logger = getScopedLogger(
|
const logger = getScopedLogger(
|
||||||
["evm", chainConfigInfo.chainName],
|
["evm", chainConfigInfo.chainName],
|
||||||
|
@ -41,15 +44,6 @@ export async function relayEVM(
|
||||||
let provider = newProvider(chainConfigInfo.nodeUrl);
|
let provider = newProvider(chainConfigInfo.nodeUrl);
|
||||||
const signer: Signer = new ethers.Wallet(walletPrivateKey, provider);
|
const signer: Signer = new ethers.Wallet(walletPrivateKey, provider);
|
||||||
|
|
||||||
if (unwrapNative) {
|
|
||||||
logger.info(
|
|
||||||
"Will redeem and unwrap using pubkey: %s",
|
|
||||||
await signer.getAddress()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
logger.info("Will redeem using pubkey: %s", await signer.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Checking to see if vaa has already been redeemed.");
|
logger.debug("Checking to see if vaa has already been redeemed.");
|
||||||
const alreadyRedeemed = await getIsTransferCompletedEth(
|
const alreadyRedeemed = await getIsTransferCompletedEth(
|
||||||
chainConfigInfo.tokenBridgeAddress,
|
chainConfigInfo.tokenBridgeAddress,
|
||||||
|
@ -65,9 +59,18 @@ export async function relayEVM(
|
||||||
return { redeemed: false, result: "not redeemed" };
|
return { redeemed: false, result: "not redeemed" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (unwrapNative) {
|
||||||
|
logger.info(
|
||||||
|
"Will redeem and unwrap using pubkey: %s",
|
||||||
|
await signer.getAddress()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.info("Will redeem using pubkey: %s", await signer.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug("Redeeming.");
|
logger.debug("Redeeming.");
|
||||||
// look, there's something janky with Polygon + ethers + EIP-1559
|
// look, there's something janky with Polygon + ethers + EIP-1559
|
||||||
let overrides;
|
let overrides = {};
|
||||||
if (chainConfigInfo.chainId === CHAIN_ID_POLYGON) {
|
if (chainConfigInfo.chainId === CHAIN_ID_POLYGON) {
|
||||||
let feeData = await provider.getFeeData();
|
let feeData = await provider.getFeeData();
|
||||||
overrides = {
|
overrides = {
|
||||||
|
@ -75,32 +78,27 @@ export async function relayEVM(
|
||||||
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas?.mul(50) || undefined,
|
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas?.mul(50) || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const receipt = unwrapNative
|
const bridge = Bridge__factory.connect(
|
||||||
? await redeemOnEthNative(
|
|
||||||
chainConfigInfo.tokenBridgeAddress,
|
|
||||||
signer,
|
|
||||||
signedVaaArray,
|
|
||||||
overrides
|
|
||||||
)
|
|
||||||
: await redeemOnEth(
|
|
||||||
chainConfigInfo.tokenBridgeAddress,
|
|
||||||
signer,
|
|
||||||
signedVaaArray,
|
|
||||||
overrides
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.debug("Checking to see if the transaction is complete.");
|
|
||||||
|
|
||||||
const success = await getIsTransferCompletedEth(
|
|
||||||
chainConfigInfo.tokenBridgeAddress,
|
chainConfigInfo.tokenBridgeAddress,
|
||||||
provider,
|
signer
|
||||||
signedVaaArray
|
|
||||||
);
|
);
|
||||||
|
const contractMethod = unwrapNative
|
||||||
|
? bridge.completeTransferAndUnwrapETH
|
||||||
|
: bridge.completeTransfer;
|
||||||
|
const tx = await contractMethod(signedVaaArray, overrides);
|
||||||
|
logger.info("waiting for tx hash: %s", tx.hash);
|
||||||
|
const receipt = await tx.wait();
|
||||||
|
|
||||||
|
// Checking getIsTransferCompletedEth can be problematic if we get
|
||||||
|
// load balanced to a node that is behind the block of our accepted tx
|
||||||
|
// The auditor worker should confirm that our tx was successful
|
||||||
|
const success = true;
|
||||||
|
|
||||||
if (provider instanceof ethers.providers.WebSocketProvider) {
|
if (provider instanceof ethers.providers.WebSocketProvider) {
|
||||||
await provider.destroy();
|
await provider.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("success: %s tx hash: %s", success, receipt.transactionHash);
|
logger.info("success: %s tx hash: %s", success, receipt.transactionHash);
|
||||||
|
metrics.incSuccesses(chainConfigInfo.chainId);
|
||||||
return { redeemed: success, result: receipt };
|
return { redeemed: success, result: receipt };
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
ChainId,
|
ChainId,
|
||||||
CHAIN_ID_SOLANA,
|
CHAIN_ID_SOLANA,
|
||||||
CHAIN_ID_TERRA,
|
CHAIN_ID_TERRA,
|
||||||
|
hexToNativeString,
|
||||||
hexToUint8Array,
|
hexToUint8Array,
|
||||||
isEVMChain,
|
isEVMChain,
|
||||||
parseTransferPayload,
|
parseTransferPayload,
|
||||||
|
@ -15,6 +16,7 @@ import { relayTerra } from "./terra";
|
||||||
import { getRelayerEnvironment } from "../configureEnv";
|
import { getRelayerEnvironment } from "../configureEnv";
|
||||||
import { RelayResult, Status } from "../helpers/redisHelper";
|
import { RelayResult, Status } from "../helpers/redisHelper";
|
||||||
import { getLogger, getScopedLogger, ScopedLogger } from "../helpers/logHelper";
|
import { getLogger, getScopedLogger, ScopedLogger } from "../helpers/logHelper";
|
||||||
|
import { PromHelper } from "../helpers/promHelpers";
|
||||||
|
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
|
|
||||||
|
@ -27,7 +29,8 @@ export async function relay(
|
||||||
signedVAA: string,
|
signedVAA: string,
|
||||||
checkOnly: boolean,
|
checkOnly: boolean,
|
||||||
walletPrivateKey: any,
|
walletPrivateKey: any,
|
||||||
relayLogger: ScopedLogger
|
relayLogger: ScopedLogger,
|
||||||
|
metrics: PromHelper
|
||||||
): Promise<RelayResult> {
|
): Promise<RelayResult> {
|
||||||
const logger = getScopedLogger(["relay"], relayLogger);
|
const logger = getScopedLogger(["relay"], relayLogger);
|
||||||
const { parse_vaa } = await importCoreWasm();
|
const { parse_vaa } = await importCoreWasm();
|
||||||
|
@ -51,8 +54,11 @@ export async function relay(
|
||||||
|
|
||||||
if (isEVMChain(transferPayload.targetChain)) {
|
if (isEVMChain(transferPayload.targetChain)) {
|
||||||
const unwrapNative =
|
const unwrapNative =
|
||||||
transferPayload.originAddress.toLowerCase() ===
|
transferPayload.originChain === transferPayload.targetChain &&
|
||||||
chainConfigInfo.wrappedAsset?.toLowerCase();
|
hexToNativeString(
|
||||||
|
transferPayload.originAddress,
|
||||||
|
transferPayload.originChain
|
||||||
|
)?.toLowerCase() === chainConfigInfo.wrappedAsset?.toLowerCase();
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"isEVMChain: originAddress: [" +
|
"isEVMChain: originAddress: [" +
|
||||||
transferPayload.originAddress +
|
transferPayload.originAddress +
|
||||||
|
@ -67,7 +73,8 @@ export async function relay(
|
||||||
unwrapNative,
|
unwrapNative,
|
||||||
checkOnly,
|
checkOnly,
|
||||||
walletPrivateKey,
|
walletPrivateKey,
|
||||||
logger
|
logger,
|
||||||
|
metrics
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
status: evmResult.redeemed ? Status.Completed : Status.Error,
|
status: evmResult.redeemed ? Status.Completed : Status.Error,
|
||||||
|
@ -82,7 +89,8 @@ export async function relay(
|
||||||
signedVAA,
|
signedVAA,
|
||||||
checkOnly,
|
checkOnly,
|
||||||
walletPrivateKey,
|
walletPrivateKey,
|
||||||
logger
|
logger,
|
||||||
|
metrics
|
||||||
);
|
);
|
||||||
if (retVal.redeemed) {
|
if (retVal.redeemed) {
|
||||||
rResult.status = Status.Completed;
|
rResult.status = Status.Completed;
|
||||||
|
@ -98,7 +106,8 @@ export async function relay(
|
||||||
signedVAA,
|
signedVAA,
|
||||||
checkOnly,
|
checkOnly,
|
||||||
walletPrivateKey,
|
walletPrivateKey,
|
||||||
logger
|
logger,
|
||||||
|
metrics
|
||||||
);
|
);
|
||||||
if (retVal.redeemed) {
|
if (retVal.redeemed) {
|
||||||
rResult.status = Status.Completed;
|
rResult.status = Status.Completed;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
monitorRedis,
|
monitorRedis,
|
||||||
RedisTables,
|
RedisTables,
|
||||||
RelayResult,
|
RelayResult,
|
||||||
|
resetPayload,
|
||||||
Status,
|
Status,
|
||||||
StorePayload,
|
StorePayload,
|
||||||
storePayloadFromJson,
|
storePayloadFromJson,
|
||||||
|
@ -18,7 +19,6 @@ import {
|
||||||
} from "../helpers/redisHelper";
|
} from "../helpers/redisHelper";
|
||||||
import { sleep } from "../helpers/utils";
|
import { sleep } from "../helpers/utils";
|
||||||
import { relay } from "./relay";
|
import { relay } from "./relay";
|
||||||
import { collectWallets } from "./walletMonitor";
|
|
||||||
|
|
||||||
const WORKER_THREAD_RESTART_MS = 10 * 1000;
|
const WORKER_THREAD_RESTART_MS = 10 * 1000;
|
||||||
const AUDITOR_THREAD_RESTART_MS = 10 * 1000;
|
const AUDITOR_THREAD_RESTART_MS = 10 * 1000;
|
||||||
|
@ -51,10 +51,15 @@ export function init(runWorker: boolean): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createWorkerInfos() {
|
function createWorkerInfos(metrics: PromHelper) {
|
||||||
let workerArray: WorkerInfo[] = new Array();
|
let workerArray: WorkerInfo[] = new Array();
|
||||||
let index = 0;
|
let index = 0;
|
||||||
relayerEnv.supportedChains.forEach((chain) => {
|
relayerEnv.supportedChains.forEach((chain) => {
|
||||||
|
// initialize per chain metrics
|
||||||
|
metrics.incSuccesses(chain.chainId, 0);
|
||||||
|
metrics.incConfirmed(chain.chainId, 0);
|
||||||
|
metrics.incFailures(chain.chainId, 0);
|
||||||
|
metrics.incRollback(chain.chainId, 0);
|
||||||
chain.walletPrivateKey?.forEach((key) => {
|
chain.walletPrivateKey?.forEach((key) => {
|
||||||
workerArray.push({
|
workerArray.push({
|
||||||
walletPrivateKey: key,
|
walletPrivateKey: key,
|
||||||
|
@ -188,15 +193,22 @@ async function doAuditorThread(workerInfo: WorkerInfo) {
|
||||||
storePayload.vaa_bytes,
|
storePayload.vaa_bytes,
|
||||||
true,
|
true,
|
||||||
workerInfo.walletPrivateKey,
|
workerInfo.walletPrivateKey,
|
||||||
auditLogger
|
auditLogger,
|
||||||
|
metrics
|
||||||
);
|
);
|
||||||
|
|
||||||
await redisClient.del(si_key);
|
await redisClient.del(si_key);
|
||||||
if (rr.status !== Status.Completed) {
|
if (rr.status === Status.Completed) {
|
||||||
|
metrics.incConfirmed(workerInfo.targetChainId);
|
||||||
|
} else {
|
||||||
auditLogger.info("Detected a rollback on " + si_key);
|
auditLogger.info("Detected a rollback on " + si_key);
|
||||||
|
metrics.incRollback(workerInfo.targetChainId);
|
||||||
// Remove this item from the WORKING table and move it to INCOMING
|
// Remove this item from the WORKING table and move it to INCOMING
|
||||||
await redisClient.select(RedisTables.INCOMING);
|
await redisClient.select(RedisTables.INCOMING);
|
||||||
await redisClient.set(si_key, si_value);
|
await redisClient.set(
|
||||||
|
si_key,
|
||||||
|
storePayloadToJson(resetPayload(storePayloadFromJson(si_value)))
|
||||||
|
);
|
||||||
await redisClient.select(RedisTables.WORKING);
|
await redisClient.select(RedisTables.WORKING);
|
||||||
}
|
}
|
||||||
} else if (storePayload.status === Status.Error) {
|
} else if (storePayload.status === Status.Error) {
|
||||||
|
@ -233,14 +245,9 @@ export async function run(ph: PromHelper) {
|
||||||
logger.info("NOT clearing REDIS.");
|
logger.info("NOT clearing REDIS.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let workerArray: WorkerInfo[] = createWorkerInfos();
|
let workerArray: WorkerInfo[] = createWorkerInfos(metrics);
|
||||||
|
|
||||||
spawnWorkerThreads(workerArray);
|
spawnWorkerThreads(workerArray);
|
||||||
try {
|
|
||||||
collectWallets(metrics);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("Failed to kick off collectWallets: " + e);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
monitorRedis(metrics);
|
monitorRedis(metrics);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -285,7 +292,13 @@ async function processRequest(
|
||||||
} else {
|
} else {
|
||||||
logger.info("Calling with vaa_bytes %s", payload.vaa_bytes);
|
logger.info("Calling with vaa_bytes %s", payload.vaa_bytes);
|
||||||
}
|
}
|
||||||
relayResult = await relay(payload.vaa_bytes, false, myPrivateKey, logger);
|
relayResult = await relay(
|
||||||
|
payload.vaa_bytes,
|
||||||
|
false,
|
||||||
|
myPrivateKey,
|
||||||
|
logger,
|
||||||
|
metrics
|
||||||
|
);
|
||||||
logger.info("Relay returned: %o", Status[relayResult.status]);
|
logger.info("Relay returned: %o", Status[relayResult.status]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.message) {
|
if (e.message) {
|
||||||
|
@ -314,9 +327,7 @@ async function processRequest(
|
||||||
targetChain = transferPayload.targetChain;
|
targetChain = transferPayload.targetChain;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
let retry: boolean = false;
|
let retry: boolean = false;
|
||||||
if (relayResult.status === Status.Completed) {
|
if (relayResult.status !== Status.Completed) {
|
||||||
metrics.incSuccesses(targetChain);
|
|
||||||
} else {
|
|
||||||
metrics.incFailures(targetChain);
|
metrics.incFailures(targetChain);
|
||||||
if (payload.retries >= MAX_RETRIES) {
|
if (payload.retries >= MAX_RETRIES) {
|
||||||
relayResult.status = Status.FatalError;
|
relayResult.status = Status.FatalError;
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
|
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
|
||||||
import { ChainConfigInfo } from "../configureEnv";
|
import { ChainConfigInfo } from "../configureEnv";
|
||||||
import { getScopedLogger, ScopedLogger } from "../helpers/logHelper";
|
import { getScopedLogger, ScopedLogger } from "../helpers/logHelper";
|
||||||
|
import { PromHelper } from "../helpers/promHelpers";
|
||||||
|
|
||||||
const MAX_VAA_UPLOAD_RETRIES_SOLANA = 5;
|
const MAX_VAA_UPLOAD_RETRIES_SOLANA = 5;
|
||||||
|
|
||||||
|
@ -25,7 +26,8 @@ export async function relaySolana(
|
||||||
signedVAAString: string,
|
signedVAAString: string,
|
||||||
checkOnly: boolean,
|
checkOnly: boolean,
|
||||||
walletPrivateKey: Uint8Array,
|
walletPrivateKey: Uint8Array,
|
||||||
relayLogger: ScopedLogger
|
relayLogger: ScopedLogger,
|
||||||
|
metrics: PromHelper
|
||||||
) {
|
) {
|
||||||
const logger = getScopedLogger(["solana"], relayLogger);
|
const logger = getScopedLogger(["solana"], relayLogger);
|
||||||
//TODO native transfer & create associated token account
|
//TODO native transfer & create associated token account
|
||||||
|
@ -164,5 +166,6 @@ export async function relaySolana(
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info("success: %s, tx hash: %s", success, txid);
|
logger.info("success: %s, tx hash: %s", success, txid);
|
||||||
|
metrics.incSuccesses(chainConfigInfo.chainId);
|
||||||
return { redeemed: success, result: txid };
|
return { redeemed: success, result: txid };
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,15 @@ import { LCDClient, MnemonicKey } from "@terra-money/terra.js";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { ChainConfigInfo } from "../configureEnv";
|
import { ChainConfigInfo } from "../configureEnv";
|
||||||
import { getScopedLogger, ScopedLogger } from "../helpers/logHelper";
|
import { getScopedLogger, ScopedLogger } from "../helpers/logHelper";
|
||||||
|
import { PromHelper } from "../helpers/promHelpers";
|
||||||
|
|
||||||
export async function relayTerra(
|
export async function relayTerra(
|
||||||
chainConfigInfo: ChainConfigInfo,
|
chainConfigInfo: ChainConfigInfo,
|
||||||
signedVAA: string,
|
signedVAA: string,
|
||||||
checkOnly: boolean,
|
checkOnly: boolean,
|
||||||
walletPrivateKey: any,
|
walletPrivateKey: any,
|
||||||
relayLogger: ScopedLogger
|
relayLogger: ScopedLogger,
|
||||||
|
metrics: PromHelper
|
||||||
) {
|
) {
|
||||||
const logger = getScopedLogger(["terra"], relayLogger);
|
const logger = getScopedLogger(["terra"], relayLogger);
|
||||||
if (
|
if (
|
||||||
|
@ -40,7 +42,7 @@ export async function relayTerra(
|
||||||
const wallet = lcd.wallet(mk);
|
const wallet = lcd.wallet(mk);
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"terraChainId: %s, tokenBridgeAddress: %s, accAddress: %s, signedVAA: $s",
|
"terraChainId: %s, tokenBridgeAddress: %s, accAddress: %s, signedVAA: %s",
|
||||||
chainConfigInfo.terraChainId,
|
chainConfigInfo.terraChainId,
|
||||||
chainConfigInfo.tokenBridgeAddress,
|
chainConfigInfo.tokenBridgeAddress,
|
||||||
wallet.key.accAddress,
|
wallet.key.accAddress,
|
||||||
|
@ -113,5 +115,6 @@ export async function relayTerra(
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info("success: %s, tx hash: %s", success, receipt.txhash);
|
logger.info("success: %s, tx hash: %s", success, receipt.txhash);
|
||||||
|
metrics.incSuccesses(chainConfigInfo.chainId);
|
||||||
return { redeemed: success, result: receipt.txhash };
|
return { redeemed: success, result: receipt.txhash };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
require("../helpers/loadConfig");
|
|
||||||
process.env.LOG_DIR = ".";
|
|
||||||
|
|
||||||
import { CHAIN_ID_BSC } from "@certusone/wormhole-sdk";
|
|
||||||
import { jest, test } from "@jest/globals";
|
|
||||||
import { ChainConfigInfo } from "../configureEnv";
|
|
||||||
import { pullEVMBalance } from "./walletMonitor";
|
|
||||||
|
|
||||||
jest.setTimeout(300000);
|
|
||||||
|
|
||||||
const bscChainConfig: ChainConfigInfo = {
|
|
||||||
chainId: CHAIN_ID_BSC,
|
|
||||||
chainName: "BSC",
|
|
||||||
nativeCurrencySymbol: "BNB",
|
|
||||||
nodeUrl: "https://bsc-dataseed.binance.org",
|
|
||||||
tokenBridgeAddress: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7",
|
|
||||||
wrappedAsset: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
|
|
||||||
};
|
|
||||||
const bscPublicKey = "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7"; // Token Bridge
|
|
||||||
const bscTokens = [
|
|
||||||
"0xfA54fF1a158B5189Ebba6ae130CEd6bbd3aEA76e", // SOL
|
|
||||||
"0x4DB5a66E937A9F4473fA95b1cAF1d1E1D62E29EA", // WETH
|
|
||||||
"0x156ab3346823B651294766e23e6Cf87254d68962", // LUNA
|
|
||||||
"0x3d4350cD54aeF9f9b2C29435e0fa809957B3F30a", // UST
|
|
||||||
"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", // WBNB
|
|
||||||
"0xc836d8dC361E44DbE64c4862D55BA041F88Ddd39", // WMATIC
|
|
||||||
"0x96412902aa9aFf61E13f085e70D3152C6ef2a817", // WAVAX
|
|
||||||
"0x6c6D604D3f07aBE287C1A3dF0281e999A83495C0", // wROSE
|
|
||||||
"0xbF8413EE8612E0E4f66Aa63B5ebE27f3C5883d47", // WFTM
|
|
||||||
"0xB04906e95AB5D797aDA81508115611fee694c2b3", // USDC
|
|
||||||
"0x524bC91Dc82d6b90EF29F76A3ECAaBAffFD490Bc", // USDT
|
|
||||||
];
|
|
||||||
|
|
||||||
test("should pull EVM token balances", async () => {
|
|
||||||
for (let address of bscTokens) {
|
|
||||||
const balance = await pullEVMBalance(bscChainConfig, bscPublicKey, address);
|
|
||||||
console.log(balance);
|
|
||||||
expect(balance).toBeTruthy();
|
|
||||||
}
|
|
||||||
});
|
|
Loading…
Reference in New Issue