Skip to content

Commit c07a429

Browse files
committed
watcher: add sui support
1 parent f86936d commit c07a429

File tree

10 files changed

+626
-68
lines changed

10 files changed

+626
-68
lines changed

cloud_functions/src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ export function makeCache() {
195195
lastUpdated: 0,
196196
lastRowKey: '',
197197
},
198+
32: {
199+
messages: [],
200+
lastUpdated: 0,
201+
lastRowKey: '',
202+
},
198203
3104: {
199204
messages: [],
200205
lastUpdated: 0,

common/src/consts.ts

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const INITIAL_DEPLOYMENT_BLOCK_BY_CHAIN: {
2828
near: '72767136',
2929
xpla: '777549',
3030
solana: '94401321', // https://explorer.solana.com/tx/KhLy688yDxbP7xbXVXK7TGpZU5DAFHbYiaoX16zZArxvVySz8i8g7N7Ss2noQYoq9XRbg6HDzrQBjUfmNcSWwhe
31+
sui: '1485552', // https://explorer.sui.io/txblock/671SoTvVUvBZQWKXeameDvAwzHQvnr8Nj7dR9MUwm3CV?network=https%3A%2F%2Frpc.mainnet.sui.io
3132
};
3233

3334
export const TOKEN_BRIDGE_EMITTERS: { [key in ChainName]?: string } = {

package-lock.json

+477-45
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"database"
1616
],
1717
"dependencies": {
18-
"@certusone/wormhole-sdk": "^0.9.13"
18+
"@certusone/wormhole-sdk": "^0.9.15-beta.4"
1919
},
2020
"devDependencies": {
2121
"typescript": "^4.9.4"

tsconfig.base.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"forceConsistentCasingInFileNames": true,
1414
"noFallthroughCasesInSwitch": true,
1515
"resolveJsonModule": true,
16-
"lib": ["es2019"]
16+
"lib": ["es2022"]
1717
},
1818
"exclude": ["**/__tests__/*"]
1919
}

watcher/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@celo-tools/celo-ethers-wrapper": "^0.3.0",
2525
"@google-cloud/bigtable": "^4.1.0",
2626
"@google-cloud/pubsub": "^3.4.1",
27+
"@mysten/sui.js": "^0.33.0",
2728
"@solana/web3.js": "^1.73.0",
2829
"@wormhole-foundation/wormhole-monitor-common": "^0.0.1",
2930
"algosdk": "^1.24.1",

watcher/src/consts.ts

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const RPCS_BY_CHAIN: { [key in ChainName]?: string } = {
4646
terra: 'https://columbus-fcd.terra.dev',
4747
injective: 'https://api.injective.network',
4848
solana: process.env.SOLANA_RPC ?? 'https://api.mainnet-beta.solana.com',
49+
sui: 'https://rpc.mainnet.sui.io',
4950
};
5051

5152
// Separating for now so if we max out infura we can keep Polygon going

watcher/src/index.ts

+22-21
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,28 @@ import { makeFinalizedWatcher } from './watchers/utils';
88
initDb();
99

1010
const supportedChains: ChainName[] = [
11-
'solana',
12-
'ethereum',
13-
'bsc',
14-
'polygon',
15-
'avalanche',
16-
'oasis',
17-
'algorand',
18-
'fantom',
19-
'karura',
20-
'acala',
21-
'klaytn',
22-
'celo',
23-
'moonbeam',
24-
'arbitrum',
25-
'optimism',
26-
'aptos',
27-
'near',
28-
'terra2',
29-
'terra',
30-
'xpla',
31-
'injective',
11+
// 'solana',
12+
// 'ethereum',
13+
// 'bsc',
14+
// 'polygon',
15+
// 'avalanche',
16+
// 'oasis',
17+
// 'algorand',
18+
// 'fantom',
19+
// 'karura',
20+
// 'acala',
21+
// 'klaytn',
22+
// 'celo',
23+
// 'moonbeam',
24+
// 'arbitrum',
25+
// 'optimism',
26+
// 'aptos',
27+
// 'near',
28+
// 'terra2',
29+
// 'terra',
30+
// 'xpla',
31+
// 'injective',
32+
'sui',
3233
];
3334

3435
for (const chain of supportedChains) {

watcher/src/watchers/SuiWatcher.ts

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { CHAIN_ID_SUI } from '@certusone/wormhole-sdk/lib/cjs/utils/consts';
2+
import {
3+
Checkpoint,
4+
JsonRpcClient,
5+
PaginatedEvents,
6+
SuiTransactionBlockResponse,
7+
} from '@mysten/sui.js';
8+
import { RPCS_BY_CHAIN } from '../consts';
9+
import { VaasByBlock } from '../databases/types';
10+
import { Watcher } from './Watcher';
11+
import { makeBlockKey, makeVaaKey } from '../databases/utils';
12+
13+
const SUI_EVENT_HANDLE = `0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a::publish_message::WormholeMessage`;
14+
15+
type PublishMessageEvent = {
16+
consistency_level: number;
17+
nonce: number;
18+
payload: number[];
19+
sender: string;
20+
sequence: string;
21+
timestamp: string;
22+
};
23+
24+
export class SuiWatcher extends Watcher {
25+
client: JsonRpcClient;
26+
maximumBatchSize: number = 100000; // arbitrarily large as this pages back by events
27+
28+
constructor() {
29+
super('sui');
30+
this.client = new JsonRpcClient(RPCS_BY_CHAIN[this.chain]!);
31+
}
32+
33+
// TODO: this might break using numbers, the whole service needs a refactor to use BigInt
34+
async getFinalizedBlockNumber(): Promise<number> {
35+
return Number(
36+
(await this.client.request('sui_getLatestCheckpointSequenceNumber', undefined)).result
37+
);
38+
}
39+
40+
// TODO: this might break using numbers, the whole service needs a refactor to use BigInt
41+
async getMessagesForBlocks(fromCheckpoint: number, toCheckpoint: number): Promise<VaasByBlock> {
42+
this.logger.info(`fetching info for checkpoints ${fromCheckpoint} to ${toCheckpoint}`);
43+
const vaasByBlock: VaasByBlock = {};
44+
45+
{
46+
// reserve empty slot for initial block so query is cataloged
47+
const fromCheckpointTimestamp = new Date(
48+
Number(
49+
(
50+
await this.client.requestWithType(
51+
'sui_getCheckpoint',
52+
{ id: fromCheckpoint.toString() },
53+
Checkpoint
54+
)
55+
).timestampMs
56+
)
57+
).toISOString();
58+
const fromBlockKey = makeBlockKey(fromCheckpoint.toString(), fromCheckpointTimestamp);
59+
vaasByBlock[fromBlockKey] = [];
60+
}
61+
62+
let lastCheckpoint: null | number = null;
63+
let cursor: any = undefined;
64+
let hasNextPage = false;
65+
do {
66+
const response = await this.client.requestWithType(
67+
'suix_queryEvents',
68+
{
69+
query: { MoveEventType: SUI_EVENT_HANDLE },
70+
cursor,
71+
descending_order: true,
72+
},
73+
PaginatedEvents
74+
);
75+
const digest = response.data.length
76+
? response.data[response.data.length - 1].id.txDigest
77+
: null;
78+
lastCheckpoint = digest
79+
? Number(
80+
(
81+
await this.client.requestWithType(
82+
'sui_getTransactionBlock',
83+
{ digest },
84+
SuiTransactionBlockResponse
85+
)
86+
).checkpoint!
87+
)
88+
: null;
89+
cursor = response.nextCursor;
90+
hasNextPage = response.hasNextPage;
91+
for (const event of response.data) {
92+
// TODO: https://docs.sui.io/sui-jsonrpc#sui_multiGetTransactionBlocks
93+
const checkpoint = (
94+
await this.client.requestWithType(
95+
'sui_getTransactionBlock',
96+
{ digest: event.id.txDigest },
97+
SuiTransactionBlockResponse
98+
)
99+
).checkpoint!;
100+
const msg = event.parsedJson as PublishMessageEvent;
101+
const timestamp = new Date(Number(msg.timestamp)).toISOString();
102+
const vaaKey = makeVaaKey(
103+
event.id.txDigest,
104+
CHAIN_ID_SUI,
105+
msg.sender.slice(2),
106+
msg.sequence
107+
);
108+
const blockKey = makeBlockKey(checkpoint, timestamp);
109+
vaasByBlock[blockKey] = [...(vaasByBlock[blockKey] || []), vaaKey];
110+
}
111+
} while (hasNextPage && lastCheckpoint && fromCheckpoint < lastCheckpoint);
112+
return vaasByBlock;
113+
}
114+
}

watcher/src/watchers/utils.ts

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { PolygonWatcher } from './PolygonWatcher';
1313
import { SolanaWatcher } from './SolanaWatcher';
1414
import { TerraExplorerWatcher } from './TerraExplorerWatcher';
1515
import { Watcher } from './Watcher';
16+
import { SuiWatcher } from './SuiWatcher';
1617

1718
export function makeFinalizedWatcher(chainName: ChainName): Watcher {
1819
if (chainName === 'solana') {
@@ -49,6 +50,8 @@ export function makeFinalizedWatcher(chainName: ChainName): Watcher {
4950
return new TerraExplorerWatcher('terra');
5051
} else if (chainName === 'terra2' || chainName === 'xpla') {
5152
return new CosmwasmWatcher(chainName);
53+
} else if (chainName === 'sui') {
54+
return new SuiWatcher();
5255
} else {
5356
throw new Error(`Attempted to create finalized watcher for unsupported chain ${chainName}`);
5457
}

0 commit comments

Comments
 (0)