diff --git a/packages/daemon/__tests__/machines/SyncMachine.test.ts b/packages/daemon/__tests__/machines/SyncMachine.test.ts index 2fa7ef47..2c628a59 100644 --- a/packages/daemon/__tests__/machines/SyncMachine.test.ts +++ b/packages/daemon/__tests__/machines/SyncMachine.test.ts @@ -590,3 +590,83 @@ describe('Event handling', () => { expect(currentState.matches(`${SYNC_MACHINE_STATES.CONNECTED}.${CONNECTED_STATES.handlingReorgStarted}`)).toBeTruthy(); }); }); + +describe('Error handling', () => { + it('should transition to ERROR state when metadataDiff invoke errors', () => { + const MockedFetchMachine = SyncMachine.withConfig({ + actions: { + startStream: () => {}, + storeEvent: () => {}, + logEventError: () => {}, + stopHealthcheckPing: () => {}, + }, + guards: { + invalidPeerId: () => false, + invalidStreamId: () => false, + invalidNetwork: () => false, + }, + }); + + let currentState = untilIdle(MockedFetchMachine); + + // Transition to detectingDiff state + currentState = MockedFetchMachine.transition(currentState, { + type: EventTypes.FULLNODE_EVENT, + event: VERTEX_METADATA_CHANGED as unknown as FullNodeEvent, + }); + + expect(currentState.matches(`${SYNC_MACHINE_STATES.CONNECTED}.${CONNECTED_STATES.handlingMetadataChanged}.detectingDiff`)).toBeTruthy(); + + // Simulate metadataDiff service error by sending error.platform event + // This is what xstate sends internally when an invoked service rejects + currentState = MockedFetchMachine.transition(currentState, { + type: 'error.platform.SyncMachine.CONNECTED.handlingMetadataChanged.detectingDiff:invocation[0]', + data: new Error('Database connection failed'), + } as any); + + // Should have transitioned to ERROR state due to metadataDiff failure + expect(currentState.matches(SYNC_MACHINE_STATES.ERROR)).toBeTruthy(); + }); + + it('should NOT get stuck in detectingDiff when metadataDiff errors (regression test for COE)', () => { + // This test ensures the fix for the COE is in place: + // Previously, without onError handler, the machine would get stuck in detectingDiff + // Now it should transition to ERROR state + + const MockedFetchMachine = SyncMachine.withConfig({ + actions: { + startStream: () => {}, + storeEvent: () => {}, + logEventError: () => {}, + stopHealthcheckPing: () => {}, + }, + guards: { + invalidPeerId: () => false, + invalidStreamId: () => false, + invalidNetwork: () => false, + }, + }); + + let currentState = untilIdle(MockedFetchMachine); + + // Transition to detectingDiff state + currentState = MockedFetchMachine.transition(currentState, { + type: EventTypes.FULLNODE_EVENT, + event: VERTEX_METADATA_CHANGED as unknown as FullNodeEvent, + }); + + expect(currentState.matches(`${SYNC_MACHINE_STATES.CONNECTED}.${CONNECTED_STATES.handlingMetadataChanged}.detectingDiff`)).toBeTruthy(); + + // Send error event - this should trigger the onError handler + currentState = MockedFetchMachine.transition(currentState, { + type: 'error.platform.SyncMachine.CONNECTED.handlingMetadataChanged.detectingDiff:invocation[0]', + data: new Error('Connection pool exhausted'), + } as any); + + // The machine should NOT be stuck in detectingDiff anymore + expect(currentState.matches(`${SYNC_MACHINE_STATES.CONNECTED}.${CONNECTED_STATES.handlingMetadataChanged}.detectingDiff`)).toBeFalsy(); + + // It should be in ERROR state + expect(currentState.matches(SYNC_MACHINE_STATES.ERROR)).toBeTruthy(); + }); +}); diff --git a/packages/daemon/__tests__/services/services.test.ts b/packages/daemon/__tests__/services/services.test.ts index 69e07daf..6b874bd4 100644 --- a/packages/daemon/__tests__/services/services.test.ts +++ b/packages/daemon/__tests__/services/services.test.ts @@ -861,7 +861,7 @@ describe('metadataDiff', () => { await expect(metadataDiff({} as any, event as any)).rejects.toThrow('Mock Error'); expect(mockDb.destroy).toHaveBeenCalled(); - expect(logger.error).toHaveBeenCalledWith('e', new Error('Mock Error')); + expect(logger.error).toHaveBeenCalledWith('metadataDiff error', new Error('Mock Error')); }); it('should handle transaction transactions that are not voided anymore', async () => { diff --git a/packages/daemon/src/machines/SyncMachine.ts b/packages/daemon/src/machines/SyncMachine.ts index 7bb50b2c..2d507ff6 100644 --- a/packages/daemon/src/machines/SyncMachine.ts +++ b/packages/daemon/src/machines/SyncMachine.ts @@ -207,6 +207,7 @@ export const SyncMachine = Machine({ invoke: { src: 'metadataDiff', onDone: { actions: ['metadataDecided'] }, + onError: `#${SYNC_MACHINE_STATES.ERROR}`, }, on: { METADATA_DECIDED: [ diff --git a/packages/daemon/src/services/index.ts b/packages/daemon/src/services/index.ts index 87e5a30f..86ad1308 100644 --- a/packages/daemon/src/services/index.ts +++ b/packages/daemon/src/services/index.ts @@ -96,9 +96,11 @@ export const METADATA_DIFF_EVENT_TYPES = { const DUPLICATE_TX_ALERT_GRACE_PERIOD = 10; // seconds export const metadataDiff = async (_context: Context, event: Event) => { - const mysql = await getDbConnection(); + let mysql; try { + mysql = await getDbConnection(); + const fullNodeEvent = event.event as StandardFullNodeEvent; const { hash, @@ -166,10 +168,12 @@ export const metadataDiff = async (_context: Context, event: Event) => { originalEvent: event, }; } catch (e) { - logger.error('e', e); + logger.error('metadataDiff error', e); return Promise.reject(e); } finally { - mysql.destroy(); + if (mysql) { + mysql.destroy(); + } } }; @@ -466,10 +470,7 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { await mysql.commit(); } catch (e) { await mysql.rollback(); - console.error('Error handling vertex accepted', { - error: (e as Error).message, - stack: (e as Error).stack, - }); + logger.error('Error handling vertex accepted', e); throw e; } finally {