Skip to content

Commit b2f4b82

Browse files
committed
cloud_functions: add alarmFastTransfer
1 parent 2679258 commit b2f4b82

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

cloud_functions/scripts/deploy.sh

+11
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@ if [ -z "$PG_HOST" ] || [ "$PG_HOST" == "localhost" ] || [ "$PG_HOST" == "127.0.
176176
exit 1
177177
fi
178178

179+
if [ -z "$PG_FT_DATABASE" ]; then
180+
echo "PG_FT_DATABASE must be specified"
181+
exit 1
182+
fi
183+
184+
if [ -z "$FT_MISSING_VAA_SLACK_CHANNEL_ID" ]; then
185+
echo "FT_MISSING_VAA_SLACK_CHANNEL_ID must be specified"
186+
exit 1
187+
fi
188+
179189
if [ -z "$PG_TOKEN_TRANSFER_TABLE" ]; then
180190
echo "PG_TOKEN_TRANSFER_TABLE must be specified"
181191
exit 1
@@ -231,6 +241,7 @@ if [ -z "$SOLANA_RPC" ]; then
231241
exit 1
232242
fi
233243

244+
gcloud functions --project "$GCP_PROJECT" deploy alarm-fast-transfer --entry-point alarmFastTransfer --gen2 --runtime nodejs22 --trigger-http --allow-unauthenticated --timeout 300 --memory 512MB --region europe-west3 --set-env-vars FT_MISSING_VAA_SLACK_CHANNEL_ID=$FT_MISSING_VAA_SLACK_CHANNEL_ID,MISSING_VAA_SLACK_POST_URL=$MISSING_VAA_SLACK_POST_URL,MISSING_VAA_SLACK_BOT_TOKEN=$MISSING_VAA_SLACK_BOT_TOKEN,PG_USER=$PG_USER,PG_PASSWORD=$PG_PASSWORD,PG_FT_DATABASE=$PG_FT_DATABASE,PG_HOST=$PG_HOST,NETWORK=$NETWORK,FUNCTION=alarmFastTransfer
234245
gcloud functions --project "$GCP_PROJECT" deploy compute-guardian-set-info --entry-point computeGuardianSetInfo --gen2 --runtime nodejs22 --trigger-http --allow-unauthenticated --timeout 300 --memory 512MB --region europe-west3 --set-env-vars NETWORK=$NETWORK,FIRESTORE_GUARDIAN_SET_INFO_COLLECTION=$FIRESTORE_GUARDIAN_SET_INFO_COLLECTION,FUNCTION=computeGuardianSetInfo
235246
gcloud functions --project "$GCP_PROJECT" deploy compute-tvl --entry-point computeTVL --gen2 --runtime nodejs22 --trigger-http --no-allow-unauthenticated --timeout 300 --memory 1GB --region europe-west3 --set-env-vars PG_USER=$PG_USER,PG_PASSWORD=$PG_PASSWORD,PG_DATABASE=$PG_DATABASE,PG_HOST=$PG_HOST,PG_ATTEST_MESSAGE_TABLE=$PG_ATTEST_MESSAGE_TABLE,PG_TOKEN_METADATA_TABLE=$PG_TOKEN_METADATA_TABLE,PG_TOKEN_TRANSFER_TABLE=$PG_TOKEN_TRANSFER_TABLE,FIRESTORE_TVL_COLLECTION=$FIRESTORE_TVL_COLLECTION,NETWORK=$NETWORK,FUNCTION=computeTVL
236247
gcloud functions --project "$GCP_PROJECT" deploy compute-tvl-history --entry-point computeTVLHistory --gen2 --runtime nodejs22 --trigger-http --no-allow-unauthenticated --timeout 540 --memory 1GB --region europe-west3 --set-env-vars PG_USER=$PG_USER,PG_PASSWORD=$PG_PASSWORD,PG_DATABASE=$PG_DATABASE,PG_HOST=$PG_HOST,PG_ATTEST_MESSAGE_TABLE=$PG_ATTEST_MESSAGE_TABLE,PG_TOKEN_METADATA_TABLE=$PG_TOKEN_METADATA_TABLE,PG_TOKEN_TRANSFER_TABLE=$PG_TOKEN_TRANSFER_TABLE,FIRESTORE_TVL_HISTORY_COLLECTION=$FIRESTORE_TVL_HISTORY_COLLECTION,PG_TOKEN_PRICE_HISTORY_TABLE=$PG_TOKEN_PRICE_HISTORY_TABLE,NETWORK=$NETWORK,FUNCTION=computeTVLHistory
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {
2+
assertEnvironmentVariable,
3+
formatAndSendToSlack,
4+
SlackInfo,
5+
} from '@wormhole-foundation/wormhole-monitor-common';
6+
import knex, { Knex } from 'knex';
7+
8+
let alarmSlackInfo: SlackInfo;
9+
let initialized = false;
10+
let pg: Knex;
11+
12+
function initialize() {
13+
pg = knex({
14+
client: 'pg',
15+
connection: {
16+
user: assertEnvironmentVariable('PG_USER'),
17+
password: assertEnvironmentVariable('PG_PASSWORD'),
18+
database: assertEnvironmentVariable('PG_FT_DATABASE'),
19+
host: assertEnvironmentVariable('PG_HOST'),
20+
},
21+
});
22+
console.log(`database = ${assertEnvironmentVariable('PG_FT_DATABASE')}`);
23+
24+
alarmSlackInfo = {
25+
channelId: assertEnvironmentVariable('FT_MISSING_VAA_SLACK_CHANNEL_ID'),
26+
postUrl: assertEnvironmentVariable('MISSING_VAA_SLACK_POST_URL'),
27+
botToken: assertEnvironmentVariable('MISSING_VAA_SLACK_BOT_TOKEN'),
28+
bannerTxt: 'Wormhole Fast Transfer Alarm',
29+
msg: '',
30+
};
31+
console.log(`channelId = ${assertEnvironmentVariable('FT_MISSING_VAA_SLACK_CHANNEL_ID')}`);
32+
console.log('initialized global variables');
33+
initialized = true;
34+
}
35+
36+
export async function alarmFastTransfer(req: any, res: any) {
37+
res.set('Access-Control-Allow-Origin', '*');
38+
if (req.method === 'OPTIONS') {
39+
// Send response to OPTIONS requests
40+
res.set('Access-Control-Allow-Methods', 'GET');
41+
res.set('Access-Control-Allow-Headers', 'Content-Type');
42+
res.set('Access-Control-Max-Age', '3600');
43+
res.status(204).send('');
44+
return;
45+
}
46+
47+
try {
48+
if (!initialized) {
49+
initialize();
50+
}
51+
52+
// Get the last 30 minutes of delayed orders.
53+
// In this case, delayed means the execution time is greater than 20 seconds.
54+
// This cloud function is scheduled to run every 30 minutes.
55+
const alertOrders: DisplayRow[] = await getDelayedOrders();
56+
for (const order of alertOrders) {
57+
const formattedAmountIn = (Number(order.amountIn) / 1_000_000).toFixed(2);
58+
const formattedAmountOut = (Number(order.amountOut) / 1_000_000).toFixed(2);
59+
alarmSlackInfo.msg =
60+
`🚨 Delayed Order Alert!\n` +
61+
`Source Chain: ${order.sourceChain}\n` +
62+
`Sequence: ${order.sequence}\n` +
63+
`Status: ${order.status}\n` +
64+
`Order Timestamp: ${order.market_order_timestamp.toISOString()}\n` +
65+
`Destination Chain: ${order.destinationChain}\n` +
66+
`Execution Time: ${order.executionTime} mSec\n` +
67+
`Amount In: ${formattedAmountIn}\n` +
68+
`Amount Out: ${formattedAmountOut}`;
69+
console.log(alarmSlackInfo.msg);
70+
await formatAndSendToSlack(alarmSlackInfo);
71+
}
72+
} catch (e) {
73+
console.error(e);
74+
res.sendStatus(500);
75+
}
76+
res.status(200).send('successfully alarmed delayed fast transfers');
77+
return;
78+
}
79+
80+
async function getDelayedOrders(): Promise<DisplayRow[]> {
81+
console.log('getDelayedOrders');
82+
const result = await pg
83+
.select(
84+
'mo.fast_vaa_id',
85+
'mo.status',
86+
'mo.market_order_timestamp',
87+
'mo.dst_chain AS destinationChain',
88+
pg.raw(
89+
'EXTRACT(EPOCH FROM (fte.execution_time - mo.market_order_timestamp)) AS "executionTime"'
90+
),
91+
'mo.amount_in AS amountIn',
92+
'fte.user_amount'
93+
)
94+
.from('market_orders AS mo')
95+
.join('fast_transfer_executions AS fte', 'mo.fast_vaa_hash', '=', 'fte.fast_vaa_hash')
96+
.where('mo.market_order_timestamp', '>=', pg.raw("NOW() - INTERVAL '30 MINUTES'")) // Get orders from last 30 minutes
97+
.andWhereRaw('EXTRACT(EPOCH FROM (fte.execution_time - mo.market_order_timestamp)) > 20000')
98+
.orderBy('mo.market_order_timestamp', 'desc');
99+
100+
console.log('result', result);
101+
return result.map((row) => ({
102+
sourceChain: row.fast_vaa_id.split('/')[0],
103+
sequence: row.fast_vaa_id.split('/')[2],
104+
status: row.status,
105+
market_order_timestamp: row.market_order_timestamp,
106+
destinationChain: row.destinationChain,
107+
executionTime: row.executionTime,
108+
amountIn: BigInt(row.amountIn),
109+
amountOut: BigInt(row.user_amount),
110+
}));
111+
}
112+
113+
type DisplayRow = {
114+
sourceChain: number; // from fast_vaa_id
115+
sequence: BigInt; // from fast_vaa_id
116+
status: string; // from MarketOrder
117+
market_order_timestamp: Date; // from MarketOrder
118+
destinationChain: number; // from MarketOrder
119+
executionTime: number; // execution_time - market_order_timestamp
120+
amountIn: BigInt; // from MarketOrder
121+
amountOut: BigInt; // from FastTransferExecutions
122+
};

0 commit comments

Comments
 (0)