-
Notifications
You must be signed in to change notification settings - Fork 0
feat(sui-indexer): add Ika coordinator event polling for signature completion tracking #301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
f846e0c
4e58f58
9bdcf49
5cb0abc
76009bb
445a82b
1fde767
83f43e5
7ba4c93
172e290
26c1d19
0a887a4
7729b1c
4d55376
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,11 @@ import { | |
| type SolvedEventRaw, | ||
| type SignatureRecordedEventRaw, | ||
| type SuiEventNode, | ||
| type IkaCompletedSignEventRaw, | ||
| type IkaRejectedSignEventRaw, | ||
| UtxoStatus, | ||
| } from "./models"; | ||
| import { logger } from "@gonative-cc/lib/logger"; | ||
| import { logError, logger } from "@gonative-cc/lib/logger"; | ||
| import { fromBase64 } from "@mysten/sui/utils"; | ||
|
|
||
| export class SuiEventHandler { | ||
|
|
@@ -105,4 +107,61 @@ export class SuiEventHandler { | |
| utxoId: e.utxo_id, | ||
| }); | ||
| } | ||
|
|
||
| public async handleIkaEvents(events: SuiEventNode[]) { | ||
| const results = await Promise.allSettled( | ||
| events.map((e) => { | ||
| if (e.type.includes("::coordinator_inner::CompletedSignEvent")) { | ||
| return this.handleCompletedSign(e); | ||
| } else if (e.type.includes("::coordinator_inner::RejectedSignEvent")) { | ||
| return this.handleRejectedSign(e); | ||
| } | ||
| return Promise.resolve(); | ||
| }), | ||
| ); | ||
|
|
||
| results.forEach((result, i) => { | ||
| if (result.status === "rejected") { | ||
| const e = events[i]; | ||
| logError( | ||
| { | ||
| msg: "Failed to handle Ika event", | ||
| method: "handleIkaEvents", | ||
| eventType: e?.type, | ||
| txDigest: e?.txDigest, | ||
| }, | ||
| result.reason, | ||
| ); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| private async handleCompletedSign(e: SuiEventNode) { | ||
| const data = e.json as IkaCompletedSignEventRaw; | ||
|
|
||
| logger.info({ | ||
| msg: "Ika signature completed", | ||
| sign_id: data.sign_id, | ||
| is_future_sign: data.is_future_sign, | ||
| signature_length: data.signature.length, | ||
| txDigest: e.txDigest, | ||
| setupId: this.setupId, | ||
| }); | ||
|
|
||
| // TODO: Call redeem_solver service binding to record signature | ||
| } | ||
|
|
||
| private async handleRejectedSign(e: SuiEventNode) { | ||
| const data = e.json as IkaRejectedSignEventRaw; | ||
|
|
||
| logger.warn({ | ||
| msg: "Ika signature rejected", | ||
| sign_id: data.sign_id, | ||
| is_future_sign: data.is_future_sign, | ||
| txDigest: e.txDigest, | ||
| setupId: this.setupId, | ||
| }); | ||
|
|
||
| // TODO: Call redeem_solver service binding to mark signature as rejected | ||
Rcc999 marked this conversation as resolved.
Show resolved
Hide resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { D1Storage } from "./storage"; | |
| import { logError, logger } from "@gonative-cc/lib/logger"; | ||
| import { SuiEventHandler } from "./handler"; | ||
| import type { EventFetcher } from "./graphql-client"; | ||
| import { getNetworkConfig } from "@ika.xyz/sdk"; | ||
|
|
||
| export class Processor { | ||
| netCfg: NetworkConfig; | ||
|
|
@@ -15,46 +16,106 @@ export class Processor { | |
| this.eventFetcher = eventFetcher; | ||
| } | ||
|
|
||
| // poll Nbtc events by multiple package ids | ||
| async pollAllNbtcEvents(nbtcPkgs: PkgCfg[]) { | ||
| const setupIds = nbtcPkgs.map((pkg) => pkg.id); | ||
| // Polls events (nBTC + Ika coordinator) from multiple packages | ||
| async pollEvents(nbtcPkgs: PkgCfg[]) { | ||
| try { | ||
| if (nbtcPkgs.length === 0) return; | ||
|
|
||
| const cursors = await this.storage.getMultipleSuiGqlCursors(setupIds); | ||
| // Get coordinator package from Ika SDK | ||
| const network = this.netCfg.name; | ||
| const coordinatorPkg = | ||
| network === "mainnet" || network === "testnet" | ||
|
||
| ? getNetworkConfig(network).packages.ikaSystemPackage | ||
| : null; | ||
|
|
||
| const packages: { | ||
| cursorId: number; | ||
| setupId: number; | ||
| pkg: string; | ||
| module: string; | ||
| isCoordinator: boolean; | ||
| }[] = []; | ||
|
|
||
| for (const pkg of nbtcPkgs) { | ||
| packages.push({ | ||
| cursorId: pkg.id, | ||
| setupId: pkg.id, | ||
| pkg: pkg.nbtc_pkg, | ||
| module: "nbtc", | ||
| isCoordinator: false, | ||
| }); | ||
| } | ||
|
|
||
| if (coordinatorPkg) { | ||
|
||
| packages.push({ | ||
| cursorId: -1, | ||
|
||
| setupId: -1, | ||
| pkg: coordinatorPkg, | ||
| module: "coordinator_inner", | ||
| isCoordinator: true, | ||
| }); | ||
| } | ||
|
|
||
| const cursors = await this.storage.getMultipleSuiGqlCursors( | ||
| packages.map((p) => p.cursorId), | ||
| ); | ||
|
|
||
| let hasAnyNextPage = true; | ||
| while (hasAnyNextPage) { | ||
| const packages = nbtcPkgs.map((pkg) => ({ | ||
| id: pkg.nbtc_pkg, | ||
| cursor: cursors[pkg.id] || null, | ||
| const fetchRequests = packages.map((p) => ({ | ||
| id: p.pkg, | ||
| module: p.module, | ||
| cursor: cursors[p.cursorId] || null, | ||
| })); | ||
|
|
||
| const results = await this.eventFetcher.fetchEvents(packages); | ||
|
|
||
| const results = await this.eventFetcher.fetchEvents(fetchRequests); | ||
| const cursorsToSave: { setupId: number; cursor: string }[] = []; | ||
| hasAnyNextPage = false; | ||
|
|
||
| for (const pkg of nbtcPkgs) { | ||
| const result = results[pkg.nbtc_pkg]; | ||
| for (const p of packages) { | ||
| const key = `${p.pkg}::${p.module}`; | ||
| const result = results[key]; | ||
| if (!result) continue; | ||
|
|
||
| logger.debug({ | ||
| msg: `Fetched events`, | ||
| network: this.netCfg.name, | ||
| setupIds, | ||
| module: p.module, | ||
| setupId: p.setupId, | ||
| eventsLength: result.events.length, | ||
| endCursor: result.endCursor, | ||
| }); | ||
|
|
||
| let processingSucceeded = true; | ||
| if (result.events.length > 0) { | ||
| const handler = new SuiEventHandler(this.storage, pkg.id); | ||
| await handler.handleEvents(result.events); | ||
| try { | ||
| const handler = new SuiEventHandler(this.storage, p.setupId); | ||
| if (p.isCoordinator) { | ||
| await handler.handleIkaEvents(result.events); | ||
| } else { | ||
| await handler.handleEvents(result.events); | ||
| } | ||
| } catch (error) { | ||
| processingSucceeded = false; | ||
| logError( | ||
| { | ||
| msg: "Failed to process events", | ||
| method: "pollEvents", | ||
| setupId: p.setupId, | ||
| module: p.module, | ||
| }, | ||
| error, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| if (result.endCursor && result.endCursor !== cursors[pkg.id]) { | ||
| cursorsToSave.push({ setupId: pkg.id, cursor: result.endCursor }); | ||
| cursors[pkg.id] = result.endCursor; | ||
| // Only advance cursor if processing succeeded or there were no events to process | ||
| if ( | ||
| processingSucceeded && | ||
| result.endCursor && | ||
| result.endCursor !== cursors[p.cursorId] | ||
| ) { | ||
| cursorsToSave.push({ setupId: p.cursorId, cursor: result.endCursor }); | ||
| cursors[p.cursorId] = result.endCursor; | ||
| } | ||
|
|
||
| if (result.hasNextPage) { | ||
|
|
@@ -70,9 +131,8 @@ export class Processor { | |
| logError( | ||
| { | ||
| msg: "Failed to index packages", | ||
| method: "queryNewEvents", | ||
| method: "pollEvents", | ||
| network: this.netCfg, | ||
| setupIds, | ||
| }, | ||
| e, | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets add it here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#313