From c6fd35c5a0d0d085058d7e671b3a38d83a80b11e Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Fri, 19 Dec 2025 16:30:07 +0000 Subject: [PATCH 1/4] fix(orchestration): badly generated public shared keys --- src/boilerplate/common/number-theory.mjs | 62 +++++++++++++----------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/boilerplate/common/number-theory.mjs b/src/boilerplate/common/number-theory.mjs index 1ff1c085..5fcbace9 100644 --- a/src/boilerplate/common/number-theory.mjs +++ b/src/boilerplate/common/number-theory.mjs @@ -348,34 +348,40 @@ function decrypt(encryptedMessages, secretKey, encPublicKey) { @return {string} key - int string */ function sharedSecretKey(secretKey, recipientPublicKey) { - const publickKeyPoint = decompressStarlightKey(recipientPublicKey); - const sharedSecret = scalarMult(secretKey.hex(32), [ - BigInt(generalise(publickKeyPoint[0]).hex(32)), - BigInt(generalise(publickKeyPoint[1]).hex(32)), - ]); - const key = poseidonHash([ - sharedSecret[0], - sharedSecret[1], - BigInt(DOMAIN_KEM), - ]); - - let sharePublicKeyPoint = generalise( - scalarMult(key.hex(32), config.BABYJUBJUB.GENERATOR) - ); - - let yBits = sharePublicKeyPoint[1].binary; - if (yBits.length > 253) - { - yBits = yBits.slice(yBits.length - 253); - } - - const xBits = sharePublicKeyPoint[0].binary; - const sign = xBits[xBits.length - 1]; - - let sharedPublicKey = new GN(sign + yBits.padStart(253, "0"), "binary"); - - - return [key, sharedPublicKey]; + const publickKeyPoint = decompressStarlightKey(recipientPublicKey); + const sharedSecret = scalarMult(secretKey.hex(32), [ + BigInt(generalise(publickKeyPoint[0]).hex(32)), + BigInt(generalise(publickKeyPoint[1]).hex(32)), + ]); + let key = poseidonHash([ + sharedSecret[0], + sharedSecret[1], + BigInt(DOMAIN_KEM), + BigInt(0), + ]); + + let sharePublicKeyPoint = generalise( + scalarMult(key.hex(32), config.BABYJUBJUB.GENERATOR), + ); + + let sharedPublicKey = compressStarlightKey(sharePublicKeyPoint); + + let i = 0; + while (sharedPublicKey === null) { + i++; + key = poseidonHash([ + sharedSecret[0], + sharedSecret[1], + BigInt(DOMAIN_KEM), + BigInt(i), + ]); + + sharePublicKeyPoint = generalise( + scalarMult(key.hex(32), config.BABYJUBJUB.GENERATOR), + ); + sharedPublicKey = compressStarlightKey(sharePublicKeyPoint); + } + return [key, sharedPublicKey]; } // Implements the Poseidon hash, drawing on the ZoKrates implementation From 2b6e6c6a481f80968c9deb66efede74874a8e708 Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Mon, 22 Dec 2025 16:29:13 +0000 Subject: [PATCH 2/4] fix(orchestration): pause deleting db in backup data retriever under event listener is finished --- .../common/backup-encrypted-data-listener.mjs | 435 ++++++++++-------- .../orchestration/files/toOrchestration.ts | 6 + 2 files changed, 259 insertions(+), 182 deletions(-) diff --git a/src/boilerplate/common/backup-encrypted-data-listener.mjs b/src/boilerplate/common/backup-encrypted-data-listener.mjs index 8cd86d76..d0f73310 100644 --- a/src/boilerplate/common/backup-encrypted-data-listener.mjs +++ b/src/boilerplate/common/backup-encrypted-data-listener.mjs @@ -2,14 +2,43 @@ import fs from 'fs'; import utils from 'zkp-utils'; import config from 'config'; import { generalise } from 'general-number'; -import { getContractAddress, getContractInstance, registerKey } from './contract.mjs'; +import { + getContractAddress, + getContractInstance, + registerKey, +} from './contract.mjs'; import { storeCommitment } from './commitment-storage.mjs'; -import { decompressStarlightKey, decrypt, poseidonHash, } from './number-theory.mjs'; -import { getLeafIndex } from "./timber.mjs"; +import { + decompressStarlightKey, + decrypt, + poseidonHash, +} from './number-theory.mjs'; +import { getLeafIndex } from './timber.mjs'; const keyDb = process.env.KEY_DB_PATH || '/app/orchestration/common/db/key.json'; +// Track in-flight backup processing so other modules can wait for idle +let activeBackupProcesses = 0; + +export async function waitForBackupListenerIdle( + timeoutMs = 100000, + pollMs = 100, +) { + const start = Date.now(); + while (activeBackupProcesses > 0) { + if (Date.now() - start > timeoutMs) { + throw new Error( + 'Timed out waiting for backup listener to finish processing', + ); + } + // eslint-disable-next-line no-await-in-loop + await new Promise(res => { + setTimeout(res, pollMs); + }); + } +} + export default class BackupEncryptedDataEventListener { constructor(web3) { this.web3 = web3; @@ -32,7 +61,8 @@ export default class BackupEncryptedDataEventListener { contractAddr, ); - if (!fs.existsSync(keyDb)) await registerKey(utils.randomHex(31), 'CONTRACT_NAME', true); + if (!fs.existsSync(keyDb)) + await registerKey(utils.randomHex(31), 'CONTRACT_NAME', true); const keys = JSON.parse(fs.readFileSync(keyDb, 'utf-8')); this.secretKey = generalise(keys.secretKey); @@ -72,8 +102,10 @@ export default class BackupEncryptedDataEventListener { } else { startBlock = 1; } - - console.log(`[BACKUP] Starting backup event listener from block ${startBlock}`); + + console.log( + `[BACKUP] Starting backup event listener from block ${startBlock}`, + ); // Store as class property to prevent garbage collection this.eventSubscription = this.instance.events[eventName]({ @@ -87,23 +119,25 @@ export default class BackupEncryptedDataEventListener { this.eventSubscription.on('connected', subscriptionId => { console.log(`[BACKUP] Connected, ID: ${subscriptionId}`); }); - + this.eventSubscription.on('data', async eventData => { try { this.lastEventReceived = Date.now(); this.lastProcessedBlock = Number(eventData.blockNumber); - console.log(`[BACKUP] Event received, block ${eventData.blockNumber}`); + console.log( + `[BACKUP] Event received, block ${eventData.blockNumber}`, + ); await this.processBackupEventData(eventData); } catch (error) { console.error('[BACKUP] Error processing backup event data:', error); } }); - + this.eventSubscription.on('error', async error => { console.error('[BACKUP] ❌ Subscription error:', error); await this.reconnect(); }); - + this.eventSubscription.on('close', async () => { console.log('[BACKUP] Subscription closed, reconnecting...'); await this.reconnect(); @@ -119,50 +153,75 @@ export default class BackupEncryptedDataEventListener { const now = Date.now(); const timeSinceLastEvent = now - this.lastEventReceived; const minutesSinceLastEvent = Math.floor(timeSinceLastEvent / 60000); - - console.log('[BACKUP] ❤️ Heartbeat - Time since last event:', minutesSinceLastEvent, 'minutes'); - + + console.log( + '[BACKUP] ❤️ Heartbeat - Time since last event:', + minutesSinceLastEvent, + 'minutes', + ); + // Check if subscription object still exists and has an ID if (!this.eventSubscription || !this.eventSubscription.id) { console.warn('[BACKUP] ⚠️ WARNING: Event subscription is dead!'); - console.log('[BACKUP] Subscription exists?', !!this.eventSubscription); + console.log( + '[BACKUP] Subscription exists?', + !!this.eventSubscription, + ); if (this.eventSubscription) { console.log('[BACKUP] Subscription ID:', this.eventSubscription.id); } await this.reconnect(); } else { - console.log('[BACKUP] Subscription healthy, ID:', this.eventSubscription.id); - + console.log( + '[BACKUP] Subscription healthy, ID:', + this.eventSubscription.id, + ); + // Check for past events to see if we're missing any try { const currentBlock = Number(await this.web3.eth.getBlockNumber()); - + // Only check if we've processed at least one event if (this.lastProcessedBlock > 0) { const checkFromBlock = this.lastProcessedBlock + 1; // Start AFTER last processed - + // Only check if there are new blocks since last processed if (checkFromBlock <= currentBlock) { - const pastEvents = await this.instance.getPastEvents('EncryptedBackupData', { - fromBlock: checkFromBlock, - toBlock: 'latest' - }); - + const pastEvents = await this.instance.getPastEvents( + 'EncryptedBackupData', + { + fromBlock: checkFromBlock, + toBlock: 'latest', + }, + ); + if (pastEvents.length > 0) { - console.log(`[BACKUP] ⚠️ Found ${pastEvents.length} past events from block ${checkFromBlock} to ${currentBlock} that subscription didn't receive!`); + console.log( + `[BACKUP] ⚠️ Found ${pastEvents.length} past events from block ${checkFromBlock} to ${currentBlock} that subscription didn't receive!`, + ); pastEvents.forEach(evt => { - console.log(`[BACKUP] - Event in block ${evt.blockNumber}, tx: ${evt.transactionHash}`); + console.log( + `[BACKUP] - Event in block ${evt.blockNumber}, tx: ${evt.transactionHash}`, + ); }); - console.log('[BACKUP] Subscription is broken. Forcing reconnect...'); + console.log( + '[BACKUP] Subscription is broken. Forcing reconnect...', + ); await this.reconnect(); } else { - console.log(`[BACKUP] No new events from block ${checkFromBlock} to ${currentBlock} - subscription OK`); + console.log( + `[BACKUP] No new events from block ${checkFromBlock} to ${currentBlock} - subscription OK`, + ); } } else { - console.log(`[BACKUP] No new blocks since last processed (${this.lastProcessedBlock})`); + console.log( + `[BACKUP] No new blocks since last processed (${this.lastProcessedBlock})`, + ); } } else { - console.log('[BACKUP] No events processed yet, skipping past event check'); + console.log( + '[BACKUP] No events processed yet, skipping past event check', + ); } } catch (e) { console.log('[BACKUP] Error checking past events:', e.message); @@ -170,192 +229,205 @@ export default class BackupEncryptedDataEventListener { } }, 30000); // Every 30 seconds - console.log('[BACKUP] Event handlers and health monitor attached, waiting for events...'); + console.log( + '[BACKUP] Event handlers and health monitor attached, waiting for events...', + ); } catch (error) { console.error('[BACKUP] ❌ Listener startup failed:', error); } } async processBackupEventData(eventData) { - const keyPairs = [ - { secretKey: this.secretKey, publicKey: this.publicKey }, - { secretKey: this.sharedSecretKey, publicKey: this.sharedPublicKey }, - ]; - - for (const kp of keyPairs) { - if (!kp.secretKey) continue; - for (let i = 0; i < eventData.returnValues.encPreimages.length; i++) { - let { cipherText, ephPublicKey, varName } = - eventData.returnValues.encPreimages[i]; - const name = varName.split(' ')[0]; - const structProperties = varName.split('props:')[1]?.trim(); - varName = varName.split('props:')[0]?.trim(); - let isArray = false; - let isStruct = false; - if (varName.includes(' a')) { - isArray = true; - } - if (varName.includes(' s')) { - isStruct = true; - } - const plainText = decrypt(cipherText, kp.secretKey.hex(32), [ - decompressStarlightKey(generalise(ephPublicKey))[0].hex(32), - decompressStarlightKey(generalise(ephPublicKey))[1].hex(32), - ]); - let mappingKey = null; - let stateVarId; - let value; - console.log( - 'Decrypted pre-image of commitment for variable name: ' + name + ': ', - ); - const salt = generalise(plainText[0]); - console.log(`\tSalt: ${salt.integer}`); - let count; - if (isArray) { - console.log(`\tState variable StateVarId: ${plainText[2]}`); - mappingKey = generalise(plainText[1]); - console.log(`\tMapping Key: ${mappingKey.integer}`); - const reGenStateVarId = generalise( - utils.mimcHash( - [ - generalise(plainText[2]).bigInt, - generalise(plainText[1]).bigInt, - ], - 'ALT_BN_254', - ), - ); - stateVarId = reGenStateVarId; - console.log(`Regenerated StateVarId: ${reGenStateVarId.bigInt}`); - count = 3; - } else { - stateVarId = generalise(plainText[1]); - console.log(`\tStateVarId: ${plainText[1]}`); - count = 2; - } - if (isStruct) { - value = {}; - let count = isArray ? 3 : 2; - for (const prop of structProperties.split(' ')) { - value[prop] = plainText[count]; - count++; + activeBackupProcesses += 1; + try { + const keyPairs = [ + { secretKey: this.secretKey, publicKey: this.publicKey }, + { secretKey: this.sharedSecretKey, publicKey: this.sharedPublicKey }, + ]; + + for (const kp of keyPairs) { + if (!kp.secretKey) continue; // eslint-disable-line no-continue + for (let i = 0; i < eventData.returnValues.encPreimages.length; i++) { + const { cipherText, ephPublicKey } = + eventData.returnValues.encPreimages[i]; + let { varName } = eventData.returnValues.encPreimages[i]; + const name = varName.split(' ')[0]; + const structProperties = varName.split('props:')[1]?.trim(); + varName = varName.split('props:')[0]?.trim(); + let isArray = false; + let isStruct = false; + if (varName.includes(' a')) { + isArray = true; } - console.log(`\tValue: ${value}`); - } else { - value = generalise(plainText[count]); - console.log(`\tValue: ${value.integer}`); - } - let newCommitment; - if (isStruct) { - const hashInput = [BigInt(stateVarId.hex(32))]; - const start = isArray ? 3 : 2; - for (let i = start; i < plainText.length; i++) { - hashInput.push(BigInt(generalise(plainText[i]).hex(32))); + if (varName.includes(' s')) { + isStruct = true; } - hashInput.push(BigInt(kp.publicKey.hex(32))); - hashInput.push(BigInt(salt.hex(32))); - newCommitment = generalise(poseidonHash(hashInput)); - } else { - newCommitment = generalise( - poseidonHash([ - BigInt(stateVarId.hex(32)), - BigInt(value.hex(32)), - BigInt(kp.publicKey.hex(32)), - BigInt(salt.hex(32)), - ]), + const plainText = decrypt(cipherText, kp.secretKey.hex(32), [ + decompressStarlightKey(generalise(ephPublicKey))[0].hex(32), + decompressStarlightKey(generalise(ephPublicKey))[1].hex(32), + ]); + let mappingKey = null; + let stateVarId; + let value; + console.log( + `Decrypted pre-image of commitment for variable name: ${name}: `, ); - } - const index = await getLeafIndex( - 'CONTRACT_NAME', - newCommitment.integer, - undefined, - 1, - ); - if (index === undefined) { - console.log(index, 'index'); - console.warn( - 'Could not find leaf index for', + const salt = generalise(plainText[0]); + console.log(`\tSalt: ${salt.integer}`); + let count; + if (isArray) { + console.log(`\tState variable StateVarId: ${plainText[2]}`); + mappingKey = generalise(plainText[1]); + console.log(`\tMapping Key: ${mappingKey.integer}`); + const reGenStateVarId = generalise( + utils.mimcHash( + [ + generalise(plainText[2]).bigInt, + generalise(plainText[1]).bigInt, + ], + 'ALT_BN_254', + ), + ); + stateVarId = reGenStateVarId; + console.log(`Regenerated StateVarId: ${reGenStateVarId.bigInt}`); + count = 3; + } else { + stateVarId = generalise(plainText[1]); + console.log(`\tStateVarId: ${plainText[1]}`); + count = 2; + } + if (isStruct) { + value = {}; + count = isArray ? 3 : 2; + for (const prop of structProperties.split(' ')) { + value[prop] = plainText[count]; + count++; + } + console.log(`\tValue: ${value}`); + } else { + value = generalise(plainText[count]); + console.log(`\tValue: ${value.integer}`); + } + let newCommitment; + if (isStruct) { + const hashInput = [BigInt(stateVarId.hex(32))]; + const start = isArray ? 3 : 2; + for (let j = start; j < plainText.length; j++) { + hashInput.push(BigInt(generalise(plainText[j]).hex(32))); + } + hashInput.push(BigInt(kp.publicKey.hex(32))); + hashInput.push(BigInt(salt.hex(32))); + newCommitment = generalise(poseidonHash(hashInput)); + } else { + newCommitment = generalise( + poseidonHash([ + BigInt(stateVarId.hex(32)), + BigInt(value.hex(32)), + BigInt(kp.publicKey.hex(32)), + BigInt(salt.hex(32)), + ]), + ); + } + // eslint-disable-next-line no-await-in-loop + const index = await getLeafIndex( + 'CONTRACT_NAME', newCommitment.integer, - ', Possibly this commitment has a different public key and so decryption failed.', + undefined, + 1, ); - continue; - } - const nullifier = poseidonHash([ - BigInt(stateVarId.hex(32)), - BigInt(kp.secretKey.hex(32)), - BigInt(salt.hex(32)), - ]); - let isNullified = false; - // Check if nullifiers method exists on the contract - if (this.instance.methods.nullifiers) { - let nullification = await this.instance.methods - .nullifiers(nullifier.integer) - .call(); - if (nullification === 0n) { - isNullified = false; - } else if (nullification === BigInt(nullifier.integer)) { - isNullified = true; + if (index === undefined) { + console.log(index, 'index'); + console.warn( + 'Could not find leaf index for', + newCommitment.integer, + ', Possibly this commitment has a different public key and so decryption failed.', + ); + continue; // eslint-disable-line no-continue + } + const nullifier = poseidonHash([ + BigInt(stateVarId.hex(32)), + BigInt(kp.secretKey.hex(32)), + BigInt(salt.hex(32)), + ]); + let isNullified = false; + // Check if nullifiers method exists on the contract + if (this.instance.methods.nullifiers) { + // eslint-disable-next-line no-await-in-loop + const nullification = await this.instance.methods + .nullifiers(nullifier.integer) + .call(); + if (nullification === 0n) { + isNullified = false; + } else if (nullification === BigInt(nullifier.integer)) { + isNullified = true; + } else { + throw new Error( + `The nullifier value: ${nullifier.integer} does not match the on-chain nullifier: ${nullification}`, + ); + } } else { - throw new Error("The nullifier value: " + nullifier.integer + - ' does not match the on-chain nullifier: ' + - nullification, + console.log( + 'Contract does not have nullifiers method, assuming not nullified', ); + isNullified = false; + } + try { + // eslint-disable-next-line no-await-in-loop + await storeCommitment({ + hash: newCommitment, + name, + mappingKey: mappingKey?.integer, + preimage: { + stateVarId, + value, + salt, + publicKey: kp.publicKey, + }, + secretKey: kp.secretKey, + isNullified, + }); + console.log('Added commitment', newCommitment.hex(32)); + } catch (e) { + if (e.toString().includes('E11000 duplicate key')) { + console.log( + 'encrypted-data-listener -', + 'This commitment already exists. Ignore it.', + ); + } } - } else { - console.log( - 'Contract does not have nullifiers method, assuming not nullified', - ); - isNullified = false; } - try { - await storeCommitment({ - hash: newCommitment, - name: name, - mappingKey: mappingKey?.integer, - preimage: { - stateVarId: stateVarId, - value: value, - salt: salt, - publicKey: kp.publicKey, - }, - secretKey: kp.secretKey, - isNullified: isNullified, - }); - console.log("Added commitment", newCommitment.hex(32)); - } catch (e) { - if (e.toString().includes("E11000 duplicate key")) { - console.log( - "encrypted-data-listener -", - "This commitment already exists. Ignore it." - ); - } - } - } - } - } + } + } finally { + activeBackupProcesses = Math.max(0, activeBackupProcesses - 1); + } + } async reconnect() { console.log('[BACKUP] Reconnecting...'); - + // Clear heartbeat interval if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } - + // Clean up old subscription if it exists if (this.eventSubscription) { try { console.log('[BACKUP] Unsubscribing from old subscription...'); await this.eventSubscription.unsubscribe(); } catch (e) { - console.log('[BACKUP] Error unsubscribing (may already be dead):', e.message); + console.log( + '[BACKUP] Error unsubscribing (may already be dead):', + e.message, + ); } this.eventSubscription = null; } - + // Reset last event timestamp this.lastEventReceived = Date.now(); - + try { await this.startBackupRecovery(); console.log('[BACKUP] Reconnected successfully'); @@ -364,5 +436,4 @@ export default class BackupEncryptedDataEventListener { setTimeout(() => this.reconnect(), 5000); // Retry after 5 seconds } } - } diff --git a/src/codeGenerators/orchestration/files/toOrchestration.ts b/src/codeGenerators/orchestration/files/toOrchestration.ts index 7b745eea..9418ab67 100644 --- a/src/codeGenerators/orchestration/files/toOrchestration.ts +++ b/src/codeGenerators/orchestration/files/toOrchestration.ts @@ -851,6 +851,8 @@ const prepareBackupDataRetriever = (node: any) => { scalarMult, } from "./common/number-theory.mjs"; import { getLeafIndex} from "./common/timber.mjs"; + + import { waitForBackupListenerIdle } from "./common/backup-encrypted-data-listener.mjs"; const { generalise } = GN; const web3 = Web3.connection(); @@ -864,6 +866,9 @@ const prepareBackupDataRetriever = (node: any) => { const connection = await mongo.connection(MONGO_URL); const db = connection.db(COMMITMENTS_DB); + // wait for backup listener to be idle before deleting commitments + await waitForBackupListenerIdle(); + try { // Get the list of all collections in the database const collections = await db.listCollections({ name: COMMITMENTS_COLLECTION }).toArray(); @@ -1026,6 +1031,7 @@ const prepareBackupDataRetriever = (node: any) => { secretKey: kp.secretKey, isNullified: isNullified, }); + console.log("Added commitment", newCommitment.hex(32)); } }; }; From 49a01b29d9460c6e12637ff62f86680a7a482afb Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Tue, 23 Dec 2025 09:58:00 +0000 Subject: [PATCH 3/4] fix(orchestration): wait for event listener to finish before deleting commitments for /backupVariable API --- src/codeGenerators/orchestration/files/toOrchestration.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/codeGenerators/orchestration/files/toOrchestration.ts b/src/codeGenerators/orchestration/files/toOrchestration.ts index 9418ab67..41b476dd 100644 --- a/src/codeGenerators/orchestration/files/toOrchestration.ts +++ b/src/codeGenerators/orchestration/files/toOrchestration.ts @@ -643,6 +643,7 @@ const prepareBackupVariable = (node: any) => { scalarMult, } from "./common/number-theory.mjs"; import { getLeafIndex} from "./common/timber.mjs"; + import { waitForBackupListenerIdle } from "./common/backup-encrypted-data-listener.mjs"; const { generalise } = GN; const web3 = Web3.connection(); @@ -654,6 +655,8 @@ const prepareBackupVariable = (node: any) => { let requestedName = _name; + // wait for backup listener to be idle before deleting commitments + await waitForBackupListenerIdle(); deleteCommitmentsByState(requestedName, null); const instance = await getContractInstance("CONTRACT_NAME"); @@ -809,6 +812,7 @@ const prepareBackupVariable = (node: any) => { secretKey: kp.secretKey, isNullified: isNullified, }); + console.log("Added commitment", newCommitment.hex(32)); } }; }; From 84a79c86207701924bba5bbc4cf291a0e0ba4f7f Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Tue, 23 Dec 2025 11:58:28 +0000 Subject: [PATCH 4/4] fix(orchestration): remove emojis --- .../common/backup-encrypted-data-listener.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/boilerplate/common/backup-encrypted-data-listener.mjs b/src/boilerplate/common/backup-encrypted-data-listener.mjs index d0f73310..c9174f6e 100644 --- a/src/boilerplate/common/backup-encrypted-data-listener.mjs +++ b/src/boilerplate/common/backup-encrypted-data-listener.mjs @@ -134,7 +134,7 @@ export default class BackupEncryptedDataEventListener { }); this.eventSubscription.on('error', async error => { - console.error('[BACKUP] ❌ Subscription error:', error); + console.error('[BACKUP] Subscription error:', error); await this.reconnect(); }); @@ -155,14 +155,14 @@ export default class BackupEncryptedDataEventListener { const minutesSinceLastEvent = Math.floor(timeSinceLastEvent / 60000); console.log( - '[BACKUP] ❤️ Heartbeat - Time since last event:', + '[BACKUP] Heartbeat - Time since last event:', minutesSinceLastEvent, 'minutes', ); // Check if subscription object still exists and has an ID if (!this.eventSubscription || !this.eventSubscription.id) { - console.warn('[BACKUP] ⚠️ WARNING: Event subscription is dead!'); + console.warn('[BACKUP] WARNING: Event subscription is dead!'); console.log( '[BACKUP] Subscription exists?', !!this.eventSubscription, @@ -197,7 +197,7 @@ export default class BackupEncryptedDataEventListener { if (pastEvents.length > 0) { console.log( - `[BACKUP] ⚠️ Found ${pastEvents.length} past events from block ${checkFromBlock} to ${currentBlock} that subscription didn't receive!`, + `[BACKUP] Found ${pastEvents.length} past events from block ${checkFromBlock} to ${currentBlock} that subscription didn't receive!`, ); pastEvents.forEach(evt => { console.log( @@ -233,7 +233,7 @@ export default class BackupEncryptedDataEventListener { '[BACKUP] Event handlers and health monitor attached, waiting for events...', ); } catch (error) { - console.error('[BACKUP] ❌ Listener startup failed:', error); + console.error('[BACKUP] Listener startup failed:', error); } } @@ -432,7 +432,7 @@ export default class BackupEncryptedDataEventListener { await this.startBackupRecovery(); console.log('[BACKUP] Reconnected successfully'); } catch (error) { - console.error('[BACKUP] ❌ Reconnection attempt failed:', error); + console.error('[BACKUP] Reconnection attempt failed:', error); setTimeout(() => this.reconnect(), 5000); // Retry after 5 seconds } }