-
Notifications
You must be signed in to change notification settings - Fork 3
fix: tx prices linking, dashboard load, initial sync errors #1132
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
base: master
Are you sure you want to change the base?
Changes from 8 commits
3ebc4a0
03e107c
c275f8e
054ccda
a7c0814
18879a6
25b5016
8ce9149
6fdb0f6
b72ce29
72333f3
4a93f89
5bdf53f
59373c9
a4b6f5c
5392e6c
68ad97b
3b198d0
678f9f4
f83885d
ff1a38f
e5eafb3
3d9c4bd
c77cd75
2e78112
89d64e0
1231945
c616877
4f38145
f076ec9
9799f62
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 |
|---|---|---|
| @@ -1,2 +1,4 @@ | ||
| IGNORE_SEEDING=true | ||
| DONT_EXECUTE_TRIGGERS=true | ||
| FROM_DUMP=true | ||
| SKIP_CACHE_REBUILD=true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,3 +39,4 @@ prisma-local/seeds/productionTxs.csv | |
| paybutton-config.json | ||
|
|
||
| dump.sql* | ||
| .forever | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -137,6 +137,7 @@ export class ChronikBlockchainClient { | |
| mempoolTxsBeingProcessed!: number | ||
|
|
||
| private latencyTestFinished: boolean | ||
| private wsReconnecting = false | ||
|
|
||
| constructor (networkSlug: string) { | ||
| this.latencyTestFinished = false | ||
|
|
@@ -156,8 +157,8 @@ export class ChronikBlockchainClient { | |
| this.latencyTestFinished = true | ||
| this.chronikWSEndpoint = this.chronik.ws(this.getWsConfig()) | ||
| this.confirmedTxsHashesFromLastBlock = [] | ||
| void this.chronikWSEndpoint.waitForOpen() | ||
| this.chronikWSEndpoint.subscribeToBlocks() | ||
| void this.connectWsWithRetry() | ||
| this.lastProcessedMessages = { confirmed: {}, unconfirmed: {} } | ||
| this.CHRONIK_MSG_PREFIX = `[CHRONIK — ${networkSlug}]` | ||
| this.wsEndpoint = io(`${config.wsBaseURL}/broadcast`, { | ||
|
|
@@ -181,6 +182,42 @@ export class ChronikBlockchainClient { | |
| } | ||
| } | ||
|
|
||
| private async connectWsWithRetry (maxRetries = 10, baseDelay = 5000): Promise<void> { | ||
| for (let attempt = 1; attempt <= maxRetries; attempt++) { | ||
|
Collaborator
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. This is off by one
Collaborator
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. I renamed |
||
| try { | ||
| await this.chronikWSEndpoint.waitForOpen() | ||
| console.log(`${this.CHRONIK_MSG_PREFIX}: WebSocket connected.`) | ||
| return | ||
| } catch (err: any) { | ||
| console.error(`${this.CHRONIK_MSG_PREFIX}: WebSocket connection attempt ${attempt}/${maxRetries} failed: ${err.message as string}`) | ||
| if (attempt < maxRetries) { | ||
| const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), 60000) | ||
| console.log(`${this.CHRONIK_MSG_PREFIX}: Retrying WebSocket in ${delay / 1000}s...`) | ||
| await new Promise(resolve => setTimeout(resolve, delay)) | ||
| } | ||
| } | ||
| } | ||
| console.error(`${this.CHRONIK_MSG_PREFIX}: WebSocket failed after ${maxRetries} attempts. Continuing without real-time updates.`) | ||
| } | ||
|
|
||
| private async reconnectWs (): Promise<void> { | ||
| if (this.wsReconnecting) return | ||
| this.wsReconnecting = true | ||
| try { | ||
| const addresses = this.getSubscribedAddresses() | ||
| this.chronikWSEndpoint = this.chronik.ws(this.getWsConfig()) | ||
| this.chronikWSEndpoint.subscribeToBlocks() | ||
| for (const addr of addresses) { | ||
| this.chronikWSEndpoint.subscribeToAddress(addr) | ||
| } | ||
| await this.connectWsWithRetry() | ||
| } catch (err: any) { | ||
| console.error(`${this.CHRONIK_MSG_PREFIX}: WebSocket reconnection error: ${err.message as string}`) | ||
| } finally { | ||
| this.wsReconnecting = false | ||
| } | ||
| } | ||
|
Comment on lines
+204
to
+220
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. Prevent reconnects during intentional shutdown.
Proposed fix export class ChronikBlockchainClient {
chronik!: ChronikClient
@@
private latencyTestFinished: boolean
private wsReconnecting = false
private confirmedTxsBeingProcessed = 0
+ private wsShuttingDown = false
@@
private async reconnectWs (): Promise<void> {
- if (this.wsReconnecting) return
+ if (this.wsReconnecting || this.wsShuttingDown) return
this.wsReconnecting = true
@@
onReconnect: (_: ws.Event) => {
+ if (this.wsShuttingDown) return
console.log(`${this.CHRONIK_MSG_PREFIX}: Chronik webSocket unexpectedly closed. Attempting reconnection...`)
void this.reconnectWs()
},
@@
onEnd: (e: ws.Event) => {
+ if (this.wsShuttingDown) return
console.log(`${this.CHRONIK_MSG_PREFIX}: Chronik WebSocket ended, type: ${e.type}. Attempting reconnection...`)
void this.reconnectWs()
},Add a small Also applies to: 705-714 🤖 Prompt for AI Agents |
||
|
|
||
| public getUrls (): string[] { | ||
| return this.chronik.proxyInterface().getEndpointArray().map(e => e.url) | ||
| } | ||
|
|
@@ -664,10 +701,16 @@ export class ChronikBlockchainClient { | |
| return { | ||
| onMessage: (msg: WsMsgClient) => { void this.processWsMessage(msg) }, | ||
| onError: (e: ws.ErrorEvent) => { console.log(`${this.CHRONIK_MSG_PREFIX}: Chronik webSocket error, type: ${e.type} | message: ${e.message} | error: ${e.error as string}`) }, | ||
| onReconnect: (_: ws.Event) => { console.log(`${this.CHRONIK_MSG_PREFIX}: Chronik webSocket unexpectedly closed.`) }, | ||
| onReconnect: (_: ws.Event) => { | ||
| console.log(`${this.CHRONIK_MSG_PREFIX}: Chronik webSocket unexpectedly closed. Attempting reconnection...`) | ||
| void this.reconnectWs() | ||
| }, | ||
| onConnect: (_: ws.Event) => { console.log(`${this.CHRONIK_MSG_PREFIX}: Chronik webSocket connection (re)established.`) }, | ||
| onEnd: (e: ws.Event) => { console.log(`${this.CHRONIK_MSG_PREFIX}: Chronik WebSocket ended, type: ${e.type}.`) }, | ||
| autoReconnect: true | ||
| onEnd: (e: ws.Event) => { | ||
| console.log(`${this.CHRONIK_MSG_PREFIX}: Chronik WebSocket ended, type: ${e.type}. Attempting reconnection...`) | ||
| void this.reconnectWs() | ||
|
Collaborator
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. I'm not sure that's what you want, this could prevent you from stopping the app as it will keep trying to reconnect ?
Collaborator
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. Indeed, I think the reconnection logic only at |
||
| }, | ||
| autoReconnect: false | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -519,21 +519,22 @@ export async function connectTransactionsListToPrices ( | |
| grouped.set(p.timestamp, g) | ||
| } | ||
|
|
||
| // Throw on missing price pairs | ||
| for (const ts of tsArray) { | ||
| const allPrices = grouped.get(ts) | ||
| const formattedDate = moment.unix(ts).format(HUMAN_READABLE_DATE_FORMAT) | ||
|
|
||
| if (allPrices == null) { | ||
| throw new Error( | ||
| `[PRICES] No price record found for networkId=${networkId} at ${formattedDate}.` | ||
| console.warn( | ||
| `[PRICES] No price record found for networkId=${networkId} at ${formattedDate} — skipping affected txs.` | ||
| ) | ||
| continue | ||
| } | ||
|
|
||
| if ((allPrices.cad == null) || (allPrices.usd == null)) { | ||
| throw new Error( | ||
| `[PRICES] Incomplete price data for networkId=${networkId} at ${formattedDate}. Partial data: ${JSON.stringify(allPrices, null, 2)}` | ||
| console.warn( | ||
| `[PRICES] Incomplete price data for networkId=${networkId} at ${formattedDate} — skipping affected txs.` | ||
| ) | ||
| continue | ||
| } | ||
|
|
||
| priceByNetworkTs.set(`${networkId}:${ts}`, allPrices as AllPrices) | ||
|
|
@@ -544,19 +545,32 @@ export async function connectTransactionsListToPrices ( | |
|
|
||
| // Build all join rows (2 per tx: USD + CAD) | ||
| const rows: Prisma.PricesOnTransactionsCreateManyInput[] = [] | ||
| const skippedTxIds: string[] = [] | ||
| for (const t of txList) { | ||
| const ts = flattenTimestamp(t.timestamp) | ||
| const allPrices = priceByNetworkTs.get(`${t.address.networkId}:${ts}`) | ||
| if (allPrices == null) { | ||
| throw new Error(`[PRICES] Missing price pair for networkId ${t.address.networkId} at ${moment.unix(ts).format(HUMAN_READABLE_DATE_FORMAT)}.`) | ||
| skippedTxIds.push(t.id) | ||
| continue | ||
| } | ||
| rows.push(...buildPriceTxConnectionInput(t, allPrices)) | ||
| } | ||
|
|
||
| if (skippedTxIds.length > 0) { | ||
| console.warn(`[PRICES] Skipped ${skippedTxIds.length} txs due to missing price data.`) | ||
| } | ||
|
|
||
| const txIdsToConnect = txList.filter(t => !skippedTxIds.includes(t.id)).map(t => t.id) | ||
|
Collaborator
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. This is very inefficient, you can build txIdsToConnect on the fly (instead of the skipped ones) and check if txList.length > txIdsToConnect.length to determine if there is any skipped tx. This avoids the O(n²) search and map building + saves some memory from the temporary filter vector.
Collaborator
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. True, fixed. |
||
| if (txIdsToConnect.length === 0) { | ||
| console.warn('[PRICES] No txs to connect after filtering — all had missing prices.') | ||
| return | ||
| } | ||
|
|
||
| console.log(`[PRICES] Built ${rows.length} price links (2 per tx).`) | ||
|
|
||
| await prisma.$transaction( | ||
| async (tx) => { | ||
| await deletePriceTxConnectionsInChunks(tx, txList.map((t) => t.id)) | ||
| await deletePriceTxConnectionsInChunks(tx, txIdsToConnect) | ||
| await createPriceTxConnectionInChunks(tx, rows) | ||
| }, | ||
| { timeout: PRICES_CONNECTION_TIMEOUT } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.