From 0ce9eaab3db4516cc7631a2a71f224cbc5b71ab0 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 10:53:07 +0200 Subject: [PATCH] make server crash more visible in logs, try fixing bug with query builder --- database/src/queries/communities.test.ts | 15 +++- .../src/queries/communityHandshakes.test.ts | 79 +++++++++++++++++++ .../1_0/resolver/AuthenticationResolver.ts | 26 +++--- federation/src/index.ts | 8 +- shared/src/helper/onShutdown.ts | 9 ++- 5 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 database/src/queries/communityHandshakes.test.ts diff --git a/database/src/queries/communities.test.ts b/database/src/queries/communities.test.ts index 2d208a224..b435c3649 100644 --- a/database/src/queries/communities.test.ts +++ b/database/src/queries/communities.test.ts @@ -1,8 +1,9 @@ import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from '..' import { AppDatabase } from '../AppDatabase' -import { getHomeCommunity, getHomeCommunityWithFederatedCommunityOrFail, getReachableCommunities } from './communities' +import { getCommunityByPublicKeyOrFail, getHomeCommunity, getHomeCommunityWithFederatedCommunityOrFail, getReachableCommunities } from './communities' import { describe, expect, it, beforeEach, beforeAll, afterAll } from 'vitest' import { createCommunity, createVerifiedFederatedCommunity } from '../seeds/community' +import { Ed25519PublicKey } from 'shared' const db = AppDatabase.getInstance() @@ -56,6 +57,18 @@ describe('community.queries', () => { await createCommunity(false) expect(() => getHomeCommunityWithFederatedCommunityOrFail('1_0')).rejects.toThrow() }) + + it('load community by public key returned from getHomeCommunityWithFederatedCommunityOrFail', async () => { + const homeCom = await createCommunity(false) + await createVerifiedFederatedCommunity('1_0', 100, homeCom) + const community = await getHomeCommunityWithFederatedCommunityOrFail('1_0') + expect(community).toBeDefined() + expect(community?.federatedCommunities).toHaveLength(1) + const ed25519PublicKey = new Ed25519PublicKey(community.federatedCommunities![0].publicKey) + const communityByPublicKey = await getCommunityByPublicKeyOrFail(ed25519PublicKey) + expect(communityByPublicKey).toBeDefined() + expect(communityByPublicKey?.communityUuid).toBe(homeCom.communityUuid) + }) }) describe('getReachableCommunities', () => { it('home community counts also to reachable communities', async () => { diff --git a/database/src/queries/communityHandshakes.test.ts b/database/src/queries/communityHandshakes.test.ts new file mode 100644 index 000000000..badf5ad96 --- /dev/null +++ b/database/src/queries/communityHandshakes.test.ts @@ -0,0 +1,79 @@ +import { AppDatabase } from '../AppDatabase' +import { + CommunityHandshakeState as DbCommunityHandshakeState, + Community as DbCommunity, + FederatedCommunity as DbFederatedCommunity, + getHomeCommunityWithFederatedCommunityOrFail, + getCommunityByPublicKeyOrFail, + findPendingCommunityHandshake, + CommunityHandshakeStateType +} from '..' +import { describe, expect, it, beforeEach, beforeAll, afterAll } from 'vitest' +import { createCommunity, createVerifiedFederatedCommunity } from '../seeds/community' +import { Ed25519PublicKey } from 'shared' + +const db = AppDatabase.getInstance() + +beforeAll(async () => { + await db.init() +}) +afterAll(async () => { + await db.destroy() +}) + +describe('communityHandshakes', () => { + // clean db for every test case + beforeEach(async () => { + await DbCommunity.clear() + await DbFederatedCommunity.clear() + await DbCommunityHandshakeState.clear() + }) + + it('should find pending community handshake by public key', async () => { + const com1 = await createCommunity(false) + await createVerifiedFederatedCommunity('1_0', 100, com1) + const state = new DbCommunityHandshakeState() + state.publicKey = com1.publicKey + state.apiVersion = '1_0' + state.status = CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK + state.handshakeId = 1 + await state.save() + const communityHandshakeState = await findPendingCommunityHandshake(new Ed25519PublicKey(com1.publicKey), '1_0') + expect(communityHandshakeState).toBeDefined() + expect(communityHandshakeState).toMatchObject({ + publicKey: com1.publicKey, + apiVersion: '1_0', + status: CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK, + handshakeId: 1 + }) + }) + + it('try to use returned public key for loading community', async () => { + // test this explicit case, because in federation.authentication it lead to server crash + const com1 = await createCommunity(false) + await createVerifiedFederatedCommunity('1_0', 100, com1) + const state = new DbCommunityHandshakeState() + state.publicKey = com1.publicKey + state.apiVersion = '1_0' + state.status = CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK + state.handshakeId = 1 + await state.save() + const communityHandshakeState = await findPendingCommunityHandshake(new Ed25519PublicKey(com1.publicKey), '1_0') + expect(communityHandshakeState).toBeDefined() + expect(communityHandshakeState?.federatedCommunity?.community).toBeDefined() + const ed25519PublicKey = new Ed25519PublicKey(communityHandshakeState?.federatedCommunity?.community?.publicKey) + const community = await DbCommunity.findOneBy({ publicKey: ed25519PublicKey.asBuffer() }) + expect(community).toBeDefined() + expect(community).toMatchObject({ + communityUuid: com1.communityUuid, + name: com1.name, + description: com1.description, + url: com1.url, + creationDate: com1.creationDate, + authenticatedAt: com1.authenticatedAt, + foreign: com1.foreign, + publicKey: com1.publicKey, + privateKey: com1.privateKey + }) + }) +}) \ No newline at end of file diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 930563e87..4e3c0eaeb 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -10,7 +10,7 @@ import { FederatedCommunityLoggingView, getHomeCommunity, findPendingCommunityHandshakeOrFailByOneTimeCode, - getCommunityByPublicKeyOrFail, + Community as DbCommunity, } from 'database' import { getLogger } from 'log4js' import { @@ -170,16 +170,20 @@ export class AuthenticationResolver { `invalid uuid: ${authArgs.uuid} for community with publicKey ${authComPublicKey.asHex()}` ) } - methodLogger.debug('before loading auth community again from db') - const authComFresh = await getCommunityByPublicKeyOrFail(argsPublicKey) - authComFresh.communityUuid = communityUuid.data - authComFresh.authenticatedAt = new Date() - methodLogger.debug('after loading auth community again from db') - methodLogger.debug('try to save: ', authComFresh) - await authComFresh.save().catch((err) => { - methodLogger.fatal('failed to save authCom:', err) - }) - methodLogger.debug('store authCom.uuid successfully:', new CommunityLoggingView(authCom)) + methodLogger.debug('before updating auth community again from db') + // need to use query builder, loading from db, changing and save lead to server crash with this error: + // TypeError [ERR_INVALID_ARG_TYPE]: The "otherBuffer" argument must be of type Buffer or Uint8Array. Received an instance of Object + // seems to be a typeorm problem with Buffer, even if I give a freshly created Buffer for public_key + await DbCommunity.createQueryBuilder() + .update(DbCommunity) + .set({ + communityUuid: communityUuid.data, + authenticatedAt: new Date(), + }) + .where({ id: authCom.id }) + .execute() + methodLogger.debug('update authCom.uuid successfully') + const homeComB = await getHomeCommunity() if (homeComB?.communityUuid) { const responseArgs = new AuthenticationResponseJwtPayloadType(args.handshakeID,homeComB.communityUuid) diff --git a/federation/src/index.ts b/federation/src/index.ts index d2249badb..a4247a2cc 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -6,9 +6,10 @@ import { getLogger } from 'log4js' // config import { CONFIG } from './config' import { LOG4JS_BASE_CATEGORY_NAME } from './config/const' -import { onShutdown, ShutdownReason } from 'shared' +import { onShutdown, printServerCrashAsciiArt, ShutdownReason } from 'shared' async function main() { + const startTime = new Date() // init logger const log4jsConfigFileName = CONFIG.LOG4JS_CONFIG_PLACEHOLDER.replace('%v', CONFIG.FEDERATION_API) initLogger( @@ -32,7 +33,10 @@ async function main() { if (ShutdownReason.SIGINT === reason || ShutdownReason.SIGTERM === reason) { logger.info(`graceful shutdown: ${reason}`) } else { - logger.error(`crash: ${reason}`, error) + const endTime = new Date() + const duration = endTime.getTime() - startTime.getTime() + printServerCrashAsciiArt(logger, `reason: ${reason}`, `duration: ${duration}ms`, '') + logger.error(error) } }) }) diff --git a/shared/src/helper/onShutdown.ts b/shared/src/helper/onShutdown.ts index c4d046071..c6e166d43 100644 --- a/shared/src/helper/onShutdown.ts +++ b/shared/src/helper/onShutdown.ts @@ -1,3 +1,5 @@ +import { Logger } from "log4js" + export enum ShutdownReason { SIGINT = 'SIGINT', SIGTERM = 'SIGTERM', @@ -38,6 +40,11 @@ export function onShutdown(shutdownHandler: (reason: ShutdownReason, error?: Err process.emit("SIGINT" as any) }) } +} - +export function printServerCrashAsciiArt(logger: Logger, msg1: string, msg2: string, msg3: string) { + logger.error(` /\\_/\\ ${msg1}`) + logger.error(`( x.x ) ${msg2}`) + logger.error(`> < ${msg3}`) + logger.error('') } \ No newline at end of file