|
| 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 | +} |
0 commit comments