Skip to content

Commit a17686d

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

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-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
+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 = formatBigInt(order.amountIn);
58+
const formattedAmountOut = formatBigInt(order.amountOut);
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+
const formatBigInt = (amt: bigint) => {
81+
const str = (amt / 10_000n).toString().padStart(3, '0');
82+
return str.substring(0, str.length - 2) + '.' + str.substring(str.length - 2);
83+
};
84+
85+
async function getDelayedOrders(): Promise<DisplayRow[]> {
86+
console.log('getDelayedOrders');
87+
const result = await pg
88+
.select(
89+
'mo.fast_vaa_id',
90+
'mo.status',
91+
'mo.market_order_timestamp',
92+
'mo.dst_chain AS destinationChain',
93+
pg.raw(
94+
'EXTRACT(EPOCH FROM (fte.execution_time - mo.market_order_timestamp)) AS "executionTime"'
95+
),
96+
'mo.amount_in AS amountIn',
97+
'fte.user_amount'
98+
)
99+
.from('market_orders AS mo')
100+
.join('fast_transfer_executions AS fte', 'mo.fast_vaa_hash', '=', 'fte.fast_vaa_hash')
101+
.where('mo.market_order_timestamp', '>=', pg.raw("NOW() - INTERVAL '30 MINUTES'")) // Get orders from last 30 minutes
102+
.andWhereRaw('EXTRACT(EPOCH FROM (fte.execution_time - mo.market_order_timestamp)) > 20000')
103+
.orderBy('mo.market_order_timestamp', 'desc');
104+
105+
console.log('result', result);
106+
return result.map((row) => ({
107+
sourceChain: row.fast_vaa_id.split('/')[0],
108+
sequence: row.fast_vaa_id.split('/')[2],
109+
status: row.status,
110+
market_order_timestamp: row.market_order_timestamp,
111+
destinationChain: row.destinationChain,
112+
executionTime: row.executionTime,
113+
amountIn: BigInt(row.amountIn),
114+
amountOut: BigInt(row.user_amount),
115+
}));
116+
}
117+
118+
type DisplayRow = {
119+
sourceChain: number; // from fast_vaa_id
120+
sequence: bigint; // from fast_vaa_id
121+
status: string; // from MarketOrder
122+
market_order_timestamp: Date; // from MarketOrder
123+
destinationChain: number; // from MarketOrder
124+
executionTime: number; // execution_time - market_order_timestamp
125+
amountIn: bigint; // from MarketOrder
126+
amountOut: bigint; // from FastTransferExecutions
127+
};

0 commit comments

Comments
 (0)