From 6f89f6d55dfd20a77537ecd952323a218a745012 Mon Sep 17 00:00:00 2001 From: 0xKurt Date: Tue, 1 Apr 2025 16:25:34 +0200 Subject: [PATCH 1/3] add logging --- apps/indexer/config.yaml | 406 +++++++++--------- packages/data-flow/src/orchestrator.ts | 158 ++++++- .../src/providers/publicGateway.provider.ts | 130 +++++- .../src/providers/cachingProxy.provider.ts | 273 +++++++++--- .../src/providers/coingecko.provider.ts | 169 +++++++- .../src/processors/allo/allo.processor.ts | 147 +++++-- .../allo/handlers/poolCreated.handler.ts | 203 ++++++++- .../allo/handlers/poolFunded.handler.ts | 63 ++- .../handlers/poolMetadataUpdated.handler.ts | 95 +++- .../allo/handlers/roleGranted.handler.ts | 64 ++- .../allo/handlers/roleRevoked.handler.ts | 62 ++- .../alloV1ToV2ProfileMigration.processor.ts | 77 +++- .../handlers/ProfileMigrated.ts | 37 +- .../handlers/onAttested.handler.ts | 80 +++- .../handlers/profileCreated.handler.ts | 110 ++++- .../profileMetadataUpdated.handler.ts | 84 +++- .../handlers/profileNameUpdated.handler.ts | 42 +- .../handlers/profileOwnerUpdated.handler.ts | 47 +- .../registry/handlers/roleGranted.handler.ts | 54 ++- .../registry/handlers/roleRevoked.handler.ts | 55 ++- .../processors/registry/registry.processor.ts | 166 +++++-- .../processors/strategy/strategy.processor.ts | 76 +++- packages/shared/src/retry/retry.ts | 98 ++++- 23 files changed, 2212 insertions(+), 484 deletions(-) diff --git a/apps/indexer/config.yaml b/apps/indexer/config.yaml index 10a8d02c..a3a0467b 100644 --- a/apps/indexer/config.yaml +++ b/apps/indexer/config.yaml @@ -135,209 +135,209 @@ networks: ####################### # MAINNET # ####################### - - id: 1 # mainnet - start_block: 18486688 - rpc: ${ENVIO_MAINNET_FALLBACK_RPC:-https://eth.llamarpc.com} - contracts: - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - name: Strategy - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - - id: 10 # optimism - rpc: ${ENVIO_OPTIMISM_FALLBACK_RPC:-https://optimism.llamarpc.com} - start_block: 111678968 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - - id: 42 # lukso-mainnet - start_block: 2400000 - rpc: http://34.91.99.187:854 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0xB087535DB0df98fC4327136e897A5985E5Cfbd66 - - - id: 100 # gnosis - rpc: ${ENVIO_GNOSIS_FALLBACK_RPC:-https://gnosis-rpc.publicnode.com} - start_block: 35900000 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - - id: 137 # polygon - rpc: ${ENVIO_POLYGON_FALLBACK_RPC:-https://polygon.llamarpc.com} - start_block: 49466006 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - - id: 250 # fantom - rpc: ${ENVIO_FANTOM_FALLBACK_RPC:-https://rpc.ankr.com/fantom} - start_block: 77624278 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - - id: 324 # zksync-era-mainnet - rpc: ${ENVIO_ZKSYNC_FALLBACK_RPC:-https://1rpc.io/zksync2-era} - start_block: 31154341 - contracts: - - name: Registry - address: - - 0xaa376Ef759c1f5A8b0B5a1e2FEC5C23f3bF30246 - - name: Strategy - - name: Allo - address: - - 0x9D1D1BF2835935C291C0f5228c86d5C4e235A249 - - - id: 1088 # metis - rpc: ${ENVIO_METIS_FALLBACK_RPC:-https://metis-rpc.publicnode.com} - start_block: 17860000 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - - id: 8453 # base - rpc: ${ENVIO_BASE_FALLBACK_RPC:-https://base-rpc.publicnode.com} - start_block: 6083365 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - - id: 42161 # arbitrum - rpc: ${ENVIO_ARBITRUM_FALLBACK_RPC:-https://arbitrum.llamarpc.com} - start_block: 146489425 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - name: AlloV1ToV2ProfileMigration - address: - - 0x1bFda15Ad5FC82E74Da81F0B8DcA486b3Ad14c71 - - name: GitcoinAttestationNetwork - address: - - 0x2ce7E4cB5Edb140A9327e67De85463186E757C8f - - - id: 43114 # avalanche - rpc: ${ENVIO_AVALANCHE_FALLBACK_RPC:-https://avalanche.public-rpc.com} - start_block: 34540051 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - - id: 42220 # celo-mainnet - rpc: ${ENVIO_CELO_FALLBACK_RPC:-https://1rpc.io/celo} - start_block: 22257475 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - - id: 534352 # scroll - rpc: ${ENVIO_SCROLL_FALLBACK_RPC:-https://1rpc.io/scroll} - start_block: 2683205 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - ####################### - # Custom RPC # - ####################### - - - id: 295 # hedera-mainnet - rpc_config: - url: ${ENVIO_HEDERA_RPC_URL:-https://mainnet.hashio.io/api} - # initial_block_interval: 9000 # Number of blocks to request initially - # backoff_multiplicative: 0 # Factor to reduce batch size on error - # acceleration_additive: 0 # Increase batch size if no errors - # interval_ceiling: 9000 # Maximum blocks per request - # backoff_millis: 1000 # Wait time before retrying in milliseconds - # query_timeout_millis: 20000 # Timeout for RPC requests in milliseconds - start_block: 75239000 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 - - - id: 1329 # sei-mainnet - rpc_config: - url: ${ENVIO_SEI_RPC_URL:-https://evm-rpc.sei-apis.com} - # initial_block_interval: 2000 # Number of blocks to request initially - # backoff_multiplicative: 0 # Factor to reduce batch size on error - # acceleration_additive: 0 # Increase batch size if no errors - interval_ceiling: 2000 # Maximum blocks per request - # backoff_millis: 1000 # Wait time before retrying in milliseconds - # query_timeout_millis: 20000 # Timeout for RPC requests in milliseconds - start_block: 78000000 - contracts: - - name: Registry - address: - - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 - - name: Strategy - - name: Allo - address: - - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + # - id: 1 # mainnet + # start_block: 18486688 + # rpc: ${ENVIO_MAINNET_FALLBACK_RPC:-https://eth.llamarpc.com} + # contracts: + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + # - name: Strategy + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + + # - id: 10 # optimism + # rpc: ${ENVIO_OPTIMISM_FALLBACK_RPC:-https://optimism.llamarpc.com} + # start_block: 111678968 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # - id: 42 # lukso-mainnet + # start_block: 2400000 + # rpc: http://34.91.99.187:854 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0xB087535DB0df98fC4327136e897A5985E5Cfbd66 + + # - id: 100 # gnosis + # rpc: ${ENVIO_GNOSIS_FALLBACK_RPC:-https://gnosis-rpc.publicnode.com} + # start_block: 35900000 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # - id: 137 # polygon + # rpc: ${ENVIO_POLYGON_FALLBACK_RPC:-https://polygon.llamarpc.com} + # start_block: 49466006 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # - id: 250 # fantom + # rpc: ${ENVIO_FANTOM_FALLBACK_RPC:-https://rpc.ankr.com/fantom} + # start_block: 77624278 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # - id: 324 # zksync-era-mainnet + # rpc: ${ENVIO_ZKSYNC_FALLBACK_RPC:-https://1rpc.io/zksync2-era} + # start_block: 31154341 + # contracts: + # - name: Registry + # address: + # - 0xaa376Ef759c1f5A8b0B5a1e2FEC5C23f3bF30246 + # - name: Strategy + # - name: Allo + # address: + # - 0x9D1D1BF2835935C291C0f5228c86d5C4e235A249 + + # - id: 1088 # metis + # rpc: ${ENVIO_METIS_FALLBACK_RPC:-https://metis-rpc.publicnode.com} + # start_block: 17860000 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # - id: 8453 # base + # rpc: ${ENVIO_BASE_FALLBACK_RPC:-https://base-rpc.publicnode.com} + # start_block: 6083365 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # - id: 42161 # arbitrum + # rpc: ${ENVIO_ARBITRUM_FALLBACK_RPC:-https://arbitrum.llamarpc.com} + # start_block: 146489425 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + # - name: AlloV1ToV2ProfileMigration + # address: + # - 0x1bFda15Ad5FC82E74Da81F0B8DcA486b3Ad14c71 + # - name: GitcoinAttestationNetwork + # address: + # - 0x2ce7E4cB5Edb140A9327e67De85463186E757C8f + + # - id: 43114 # avalanche + # rpc: ${ENVIO_AVALANCHE_FALLBACK_RPC:-https://avalanche.public-rpc.com} + # start_block: 34540051 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # - id: 42220 # celo-mainnet + # rpc: ${ENVIO_CELO_FALLBACK_RPC:-https://1rpc.io/celo} + # start_block: 22257475 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # - id: 534352 # scroll + # rpc: ${ENVIO_SCROLL_FALLBACK_RPC:-https://1rpc.io/scroll} + # start_block: 2683205 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # ####################### + # # Custom RPC # + # ####################### + + # - id: 295 # hedera-mainnet + # rpc_config: + # url: ${ENVIO_HEDERA_RPC_URL:-https://mainnet.hashio.io/api} + # # initial_block_interval: 9000 # Number of blocks to request initially + # # backoff_multiplicative: 0 # Factor to reduce batch size on error + # # acceleration_additive: 0 # Increase batch size if no errors + # # interval_ceiling: 9000 # Maximum blocks per request + # # backoff_millis: 1000 # Wait time before retrying in milliseconds + # # query_timeout_millis: 20000 # Timeout for RPC requests in milliseconds + # start_block: 75239000 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 + + # - id: 1329 # sei-mainnet + # rpc_config: + # url: ${ENVIO_SEI_RPC_URL:-https://evm-rpc.sei-apis.com} + # # initial_block_interval: 2000 # Number of blocks to request initially + # # backoff_multiplicative: 0 # Factor to reduce batch size on error + # # acceleration_additive: 0 # Increase batch size if no errors + # interval_ceiling: 2000 # Maximum blocks per request + # # backoff_millis: 1000 # Wait time before retrying in milliseconds + # # query_timeout_millis: 20000 # Timeout for RPC requests in milliseconds + # start_block: 78000000 + # contracts: + # - name: Registry + # address: + # - 0x4AAcca72145e1dF2aeC137E1f3C5E3D75DB8b5f3 + # - name: Strategy + # - name: Allo + # address: + # - 0x1133eA7Af70876e64665ecD07C0A0476d09465a1 ####################### # TESTNET # diff --git a/packages/data-flow/src/orchestrator.ts b/packages/data-flow/src/orchestrator.ts index 61070384..b6ad123c 100644 --- a/packages/data-flow/src/orchestrator.ts +++ b/packages/data-flow/src/orchestrator.ts @@ -163,8 +163,32 @@ export class Orchestrator { await this.retryHandler.execute( async () => { + this.logger.debug("Starting event processing", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + blockNumber: event!.blockNumber, + logIndex: event!.logIndex, + }); + const changesets = await this.handleEvent(event!); + + this.logger.debug("Event handling completed", { + className: Orchestrator.name, + chainId: this.chainId, + hasChangesets: !!changesets, + changesetsCount: changesets?.length ?? 0, + eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + }); + if (changesets) { + this.logger.debug("Applying changesets with processed event", { + className: Orchestrator.name, + chainId: this.chainId, + totalChangesets: changesets.length + 1, + eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + }); + await this.dataLoader.applyChanges([ ...changesets, { @@ -179,6 +203,12 @@ export class Orchestrator { }, ]); } else { + this.logger.debug("Applying only processed event record", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + }); + await this.dataLoader.applyChanges([ { type: "InsertProcessedEvent", @@ -192,6 +222,12 @@ export class Orchestrator { }, ]); } + + this.logger.debug("Changes applied successfully", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + }); }, { abortSignal: signal }, ); @@ -199,14 +235,32 @@ export class Orchestrator { this.logger.info(`Processed events: ${processedEvents}/${totalEvents}`, { className: Orchestrator.name, chainId: this.chainId, + progress: `${((processedEvents / totalEvents) * 100).toFixed(2)}%`, + currentEventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + currentBlock: event!.blockNumber, }); } catch (error: unknown) { + this.logger.warn("Error encountered during event processing", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: event ? `${event.blockNumber}:${event.logIndex}` : undefined, + errorType: error instanceof Error ? error.constructor.name : typeof error, + }); + if (event) { + this.logger.debug("Saving last processed event before error handling", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: `${event.blockNumber}:${event.logIndex}`, + blockNumber: event.blockNumber, + }); + await this.eventsRegistry.saveLastProcessedEvent(this.chainId, { ...event, rawEvent: event, }); } + if ( error instanceof UnsupportedStrategy || error instanceof InvalidEvent || @@ -218,6 +272,8 @@ export class Orchestrator { className: Orchestrator.name, chainId: this.chainId, event, + errorType: error.constructor.name, + errorDetails: error.message, }, ); } else { @@ -227,6 +283,8 @@ export class Orchestrator { event, className: Orchestrator.name, chainId: this.chainId, + lastRetryDelay: error.metadata?.retryAfterInMs, + reason: error.metadata?.failureReason, }); void this.notifier.send(error.message, { chainId: this.chainId, @@ -238,11 +296,21 @@ export class Orchestrator { error, event!, ); + + this.logger.debug("Checking if error should be ignored", { + className: Orchestrator.name, + chainId: this.chainId, + shouldIgnoreError, + errorType: error.constructor.name, + eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + }); + if (!shouldIgnoreError) { this.logger.error(error, { event, className: Orchestrator.name, chainId: this.chainId, + errorStack: error.stack, }); void this.notifier.send(error.message, { chainId: this.chainId, @@ -251,20 +319,16 @@ export class Orchestrator { }); } } else { - this.logger.error( - new Error(`Error processing event: ${stringify(event)} ${error}`), - { - className: Orchestrator.name, - chainId: this.chainId, - }, - ); - void this.notifier.send( - `Error processing event: ${stringify(event)} ${error}`, - { - chainId: this.chainId, - event: event!, - }, - ); + const errorMessage = `Error processing event: ${stringify(event)} ${error}`; + this.logger.error(new Error(errorMessage), { + className: Orchestrator.name, + chainId: this.chainId, + unknownErrorType: typeof error, + }); + void this.notifier.send(errorMessage, { + chainId: this.chainId, + event: event!, + }); } } } @@ -405,23 +469,79 @@ export class Orchestrator { * @returns The token prices */ private async bulkFetchTokens(tokens: TokenWithTimestamps[]): Promise { + this.logger.info(`Starting bulk fetch for ${tokens.length} token prices`, { + className: Orchestrator.name, + chainId: this.chainId, + tokens: tokens.map((t) => t.token.priceSourceCode), + timestampCount: tokens[0]?.timestamps.length ?? 0, + }); + const results = await Promise.allSettled( tokens.map(({ token, timestamps }) => this.retryHandler.execute(async () => { - const prices = await this.dependencies.pricingProvider.getTokenPrices( - token.priceSourceCode, - timestamps, - ); - return prices; + this.logger.debug(`Fetching prices for token ${token.priceSourceCode}`, { + className: Orchestrator.name, + chainId: this.chainId, + timestampCount: timestamps.length, + }); + + try { + const prices = await this.dependencies.pricingProvider.getTokenPrices( + token.priceSourceCode, + timestamps, + ); + this.logger.debug( + `Successfully fetched prices for ${token.priceSourceCode}`, + { + className: Orchestrator.name, + chainId: this.chainId, + priceCount: prices.length, + }, + ); + return prices; + } catch (error) { + this.logger.error(`Failed to fetch prices for ${token.priceSourceCode}`, { + className: Orchestrator.name, + chainId: this.chainId, + error: error instanceof Error ? error.message : String(error), + timestamps: timestamps.map((t) => new Date(t).toISOString()), + }); + throw error; + } }), ), ); + const tokenPrices: TokenPrice[] = []; + let fulfilledCount = 0; + let rejectedCount = 0; + for (const result of results) { if (result.status === "fulfilled" && result.value) { tokenPrices.push(...result.value); + fulfilledCount++; + } else if (result.status === "rejected") { + rejectedCount++; + this.logger.warn(`Token price fetch rejected`, { + className: Orchestrator.name, + chainId: this.chainId, + error: + result.reason instanceof Error + ? result.reason.message + : String(result.reason), + }); } } + + this.logger.info(`Completed bulk token price fetch`, { + className: Orchestrator.name, + chainId: this.chainId, + totalRequests: results.length, + successful: fulfilledCount, + failed: rejectedCount, + totalPricesFetched: tokenPrices.length, + }); + return tokenPrices; } diff --git a/packages/metadata/src/providers/publicGateway.provider.ts b/packages/metadata/src/providers/publicGateway.provider.ts index 50cb30c0..6c2f59b5 100644 --- a/packages/metadata/src/providers/publicGateway.provider.ts +++ b/packages/metadata/src/providers/publicGateway.provider.ts @@ -12,7 +12,16 @@ export class PublicGatewayProvider implements IMetadataProvider { private readonly gateways: string[], private readonly logger: ILogger, ) { + this.logger.debug("Initializing PublicGatewayProvider", { + className: "PublicGatewayProvider", + gatewayCount: gateways.length, + gateways, + }); + if (gateways.length === 0) { + this.logger.error("No gateways provided", { + className: "PublicGatewayProvider", + }); throw new EmptyGatewaysUrlsException(); } @@ -24,36 +33,119 @@ export class PublicGatewayProvider implements IMetadataProvider { ipfsCid: string, validateContent?: z.ZodSchema, ): Promise { + this.logger.debug("Getting metadata", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + ipfsCid, + hasValidator: !!validateContent, + }); + if (ipfsCid === "" || !isValidCid(ipfsCid)) { + this.logger.warn("Invalid IPFS CID", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + ipfsCid, + isEmptyCid: ipfsCid === "", + isValidCid: isValidCid(ipfsCid), + }); return undefined; } + for (let i = 0; i < this.gateways.length; i++) { const gateway = this.gateways[i]; const url = `${gateway}/ipfs/${ipfsCid}`; + + this.logger.debug("Attempting gateway fetch", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + gatewayIndex: i + 1, + totalGateways: this.gateways.length, + gateway, + url, + }); + try { - //TODO: retry policy for each gateway - this.logger.debug("Fetching metadata from gateway", { + const startTime = Date.now(); + const { data } = await this.axiosInstance.get(url); + const duration = Date.now() - startTime; + + this.logger.debug("Gateway response received", { + className: "PublicGatewayProvider", + methodName: "getMetadata", gateway, - url, + responseTimeMs: duration, + dataSize: JSON.stringify(data).length, }); - const { data } = await this.axiosInstance.get(url); if (typeof data !== "object" && !isJSON(data)) { - this.logger.error(`Invalid JSON: ${JSON.stringify(data, undefined, 4)}`); + this.logger.error("Invalid JSON response", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + gateway, + url, + data: JSON.stringify(data, undefined, 4), + }); throw new InvalidContentException("Invalid JSON"); } - return this.validateData(data, validateContent); + this.logger.debug("Validating response data", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + gateway, + hasValidator: !!validateContent, + }); + + const validatedData = this.validateData(data, validateContent); + + this.logger.info("Successfully retrieved and validated metadata", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + gateway, + ipfsCid, + responseTimeMs: duration, + }); + + return validatedData; } catch (error: unknown) { - if (error instanceof InvalidContentException) throw error; + if (error instanceof InvalidContentException) { + this.logger.error("Content validation failed", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + gateway, + url, + error: error.message, + }); + throw error; + } if (axios.isAxiosError(error)) { - this.logger.warn(`Failed to fetch from ${url}: ${error.message}`); + this.logger.warn("Gateway request failed", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + gateway, + url, + status: error.response?.status, + statusText: error.response?.statusText, + error: error.message, + }); } else { - this.logger.error(`Failed to fetch from ${url}: ${error}`); + this.logger.error("Unexpected error during fetch", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + gateway, + url, + error: error instanceof Error ? error.message : String(error), + }); } } } + + this.logger.warn("All gateways failed", { + className: "PublicGatewayProvider", + methodName: "getMetadata", + ipfsCid, + attemptedGateways: this.gateways, + }); return undefined; } @@ -66,14 +158,28 @@ export class PublicGatewayProvider implements IMetadataProvider { * @throws InvalidContentException if the data does not match the schema. */ private validateData(data: T, validateContent?: z.ZodSchema): T { + this.logger.debug("Validating data", { + className: "PublicGatewayProvider", + methodName: "validateData", + hasValidator: !!validateContent, + }); + if (validateContent) { const parsedData = validateContent.safeParse(data); if (parsedData.success) { + this.logger.debug("Data validation successful", { + className: "PublicGatewayProvider", + methodName: "validateData", + }); return parsedData.data; } else { - throw new InvalidContentException( - parsedData.error.issues.map((issue) => stringify(issue)).join("\n"), - ); + const issues = parsedData.error.issues.map((issue) => stringify(issue)).join("\n"); + this.logger.error("Data validation failed", { + className: "PublicGatewayProvider", + methodName: "validateData", + validationIssues: issues, + }); + throw new InvalidContentException(issues); } } diff --git a/packages/pricing/src/providers/cachingProxy.provider.ts b/packages/pricing/src/providers/cachingProxy.provider.ts index a89531bb..79f769d6 100644 --- a/packages/pricing/src/providers/cachingProxy.provider.ts +++ b/packages/pricing/src/providers/cachingProxy.provider.ts @@ -20,7 +20,12 @@ export class CachingPricingProvider implements IPricingProvider, ICacheable { private readonly provider: IPricingProvider, private readonly cache: Partial & IPricingCache, private readonly logger: ILogger, - ) {} + ) { + this.logger.debug("Initializing CachingPricingProvider", { + className: "CachingPricingProvider", + providerType: provider.constructor.name, + }); + } /** @inheritdoc */ async getTokenPrice( @@ -28,28 +33,81 @@ export class CachingPricingProvider implements IPricingProvider, ICacheable { startTimestampMs: TimestampMs, endTimestampMs?: TimestampMs, ): Promise { + this.logger.debug("Getting token price", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + startTimestamp: new Date(startTimestampMs).toISOString(), + endTimestamp: endTimestampMs ? new Date(endTimestampMs).toISOString() : undefined, + }); + let cachedPrices: TokenPrice[] = []; + const cacheStartTime = (startTimestampMs as number) - MIN_GRANULARITY_MS * PROXIMITY_FACTOR; try { + this.logger.debug("Checking cache for prices", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + cacheTimeRange: { + start: new Date(cacheStartTime).toISOString(), + end: new Date(startTimestampMs).toISOString(), + }, + }); + cachedPrices = ( await this.cache.getPricesByTimeRange( tokenCode, - ((startTimestampMs as number) - - MIN_GRANULARITY_MS * PROXIMITY_FACTOR) as TimestampMs, + cacheStartTime as TimestampMs, startTimestampMs, ) ).sort((a, b) => a.timestampMs - b.timestampMs); + + this.logger.debug("Cache lookup result", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + cachedPricesCount: cachedPrices.length, + }); } catch (error) { - this.logger.debug( - `Failed to get cached prices for token ${tokenCode} at ${startTimestampMs}`, - { error }, - ); + this.logger.warn("Cache lookup failed", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + timestamp: new Date(startTimestampMs).toISOString(), + error: error instanceof Error ? error.message : String(error), + }); } if (cachedPrices.length > 0) { - return this.getClosestPrices([startTimestampMs], cachedPrices)[0]; + const closestPrice = this.getClosestPrices([startTimestampMs], cachedPrices)[0]; + if (!closestPrice) { + this.logger.warn("No closest price found in cache", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + requestedTimestamp: new Date(startTimestampMs).toISOString(), + }); + return undefined; + } + this.logger.debug("Returning cached price", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + requestedTimestamp: new Date(startTimestampMs).toISOString(), + priceTimestamp: new Date(closestPrice.timestampMs).toISOString(), + price: closestPrice.priceUsd, + }); + return closestPrice; } + this.logger.info("Cache miss, fetching from provider", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + startTimestamp: new Date(startTimestampMs).toISOString(), + }); + const price = await this.provider.getTokenPrice( tokenCode, startTimestampMs, @@ -57,21 +115,30 @@ export class CachingPricingProvider implements IPricingProvider, ICacheable { ); if (price) { - // we don't await this, because it's not critical - this.cache - .set( - { - tokenCode, - timestampMs: startTimestampMs, - }, - price, - ) - .catch((error) => { - this.logger.debug( - `Failed to cache price for token ${tokenCode} at ${startTimestampMs}`, - { error }, - ); + this.logger.debug("Caching fetched price", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + timestamp: new Date(price.timestampMs).toISOString(), + price: price.priceUsd, + }); + + this.cache.set({ tokenCode, timestampMs: startTimestampMs }, price).catch((error) => { + this.logger.warn("Failed to cache price", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + timestamp: new Date(startTimestampMs).toISOString(), + error: error instanceof Error ? error.message : String(error), }); + }); + } else { + this.logger.warn("No price returned from provider", { + className: "CachingPricingProvider", + methodName: "getTokenPrice", + tokenCode, + startTimestamp: new Date(startTimestampMs).toISOString(), + }); } return price; @@ -94,10 +161,36 @@ export class CachingPricingProvider implements IPricingProvider, ICacheable { * Uses binary search to find the closest price for each requested timestamp. */ async getTokenPrices(tokenCode: TokenCode, timestamps: TimestampMs[]): Promise { + this.logger.info("Getting token prices", { + className: "CachingPricingProvider", + methodName: "getTokenPrices", + tokenCode, + timestampCount: timestamps.length, + timeRange: + timestamps.length > 0 + ? { + start: new Date(Math.min(...timestamps)).toISOString(), + end: new Date(Math.max(...timestamps)).toISOString(), + } + : undefined, + }); + if (timestamps.length === 0) return []; + const sortedTimestamps = timestamps.sort((a, b) => a - b); const fromTimestampMs = sortedTimestamps[0] as TimestampMs; const toTimestampMs = sortedTimestamps[sortedTimestamps.length - 1] as TimestampMs; + + this.logger.debug("Fetching cached prices", { + className: "CachingPricingProvider", + methodName: "getTokenPrices", + tokenCode, + timeRange: { + start: new Date(fromTimestampMs).toISOString(), + end: new Date(toTimestampMs).toISOString(), + }, + }); + const cachedPrices = ( await this.getCachedPrices( tokenCode, @@ -106,35 +199,75 @@ export class CachingPricingProvider implements IPricingProvider, ICacheable { ) ).sort((a, b) => a.timestampMs - b.timestampMs); + this.logger.debug("Cache lookup result", { + className: "CachingPricingProvider", + methodName: "getTokenPrices", + tokenCode, + cachedPricesCount: cachedPrices.length, + }); + try { const prices = this.getClosestPrices(sortedTimestamps, cachedPrices); + this.logger.info("Successfully matched cached prices", { + className: "CachingPricingProvider", + methodName: "getTokenPrices", + tokenCode, + requestedCount: timestamps.length, + matchedCount: prices.length, + }); return prices; } catch (error) { if (error instanceof NoClosePriceFound) { + this.logger.info("No close prices found in cache, fetching from provider", { + className: "CachingPricingProvider", + methodName: "getTokenPrices", + tokenCode, + timestampCount: sortedTimestamps.length, + }); + const fetchedPrices = await this.provider.getTokenPrices( tokenCode, sortedTimestamps, ); + + this.logger.debug("Caching fetched prices", { + className: "CachingPricingProvider", + methodName: "getTokenPrices", + tokenCode, + fetchedCount: fetchedPrices.length, + }); + for (const price of fetchedPrices) { this.cache - .set( - { - tokenCode, - timestampMs: price.timestampMs, - }, - price, - ) + .set({ tokenCode, timestampMs: price.timestampMs }, price) .catch((error) => { - this.logger.debug( - `Failed to cache price for token ${tokenCode} at ${price.timestampMs}`, - { error }, - ); + this.logger.warn("Failed to cache price", { + className: "CachingPricingProvider", + methodName: "getTokenPrices", + tokenCode, + timestamp: new Date(price.timestampMs).toISOString(), + error: error instanceof Error ? error.message : String(error), + }); }); } - return this.getClosestPrices(sortedTimestamps, fetchedPrices).sort( - (a, b) => a.timestampMs - b.timestampMs, - ); + + const closestPrices = this.getClosestPrices(sortedTimestamps, fetchedPrices); + this.logger.info("Successfully matched fetched prices", { + className: "CachingPricingProvider", + methodName: "getTokenPrices", + tokenCode, + requestedCount: timestamps.length, + matchedCount: closestPrices.length, + }); + + return closestPrices.sort((a, b) => a.timestampMs - b.timestampMs); } + this.logger.error("Unexpected error while getting prices", { + className: "CachingPricingProvider", + methodName: "getTokenPrices", + tokenCode, + error: error instanceof Error ? error.message : String(error), + }); throw error; } } @@ -176,36 +309,66 @@ export class CachingPricingProvider implements IPricingProvider, ICacheable { * @returns The closest matching price or null if no prices available */ private findClosestPrice(prices: TokenPrice[], targetTimestamp: number): TokenPrice | null { + this.logger.debug("Finding closest price", { + className: "CachingPricingProvider", + methodName: "findClosestPrice", + targetTimestamp: new Date(targetTimestamp).toISOString(), + availablePrices: prices.length, + }); + if (prices.length === 0) { + this.logger.debug("No prices available"); return null; } - // Handle edge cases - if (targetTimestamp <= prices[0]!.timestampMs) return prices[0]!; + // Handle edge cases with detailed logging + if (targetTimestamp <= prices[0]!.timestampMs) { + this.logger.debug("Using first price (target before range)", { + priceTimestamp: new Date(prices[0]!.timestampMs).toISOString(), + price: prices[0]!.priceUsd, + }); + return prices[0]!; + } + if (targetTimestamp >= prices[prices.length - 1]!.timestampMs) { - if ( - Math.abs(prices[prices.length - 1]!.timestampMs - targetTimestamp) > - MIN_GRANULARITY_MS * PROXIMITY_FACTOR - ) { + const timeDiff = Math.abs(prices[prices.length - 1]!.timestampMs - targetTimestamp); + if (timeDiff > MIN_GRANULARITY_MS * PROXIMITY_FACTOR) { + this.logger.debug("Last price too far from target", { + timeDifference: `${timeDiff / 1000} seconds`, + }); return null; } + this.logger.debug("Using last price (target after range)", { + priceTimestamp: new Date(prices[prices.length - 1]!.timestampMs).toISOString(), + price: prices[prices.length - 1]!.priceUsd, + }); return prices[prices.length - 1]!; } - // Binary search + // Binary search with logging let left = 0; let right = prices.length - 1; while (left + 1 < right) { const mid = Math.floor((left + right) / 2); + this.logger.debug("Binary search iteration", { + left: new Date(prices[left]!.timestampMs).toISOString(), + mid: new Date(prices[mid]!.timestampMs).toISOString(), + right: new Date(prices[right]!.timestampMs).toISOString(), + }); if (prices[mid]!.timestampMs === targetTimestamp) { - if ( - Math.abs(prices[mid]!.timestampMs - targetTimestamp) > - MIN_GRANULARITY_MS * PROXIMITY_FACTOR - ) { + const timeDiff = Math.abs(prices[mid]!.timestampMs - targetTimestamp); + if (timeDiff > MIN_GRANULARITY_MS * PROXIMITY_FACTOR) { + this.logger.debug("Exact match found but too far from target", { + timeDifference: `${timeDiff / 1000} seconds`, + }); return null; } + this.logger.debug("Exact match found", { + priceTimestamp: new Date(prices[mid]!.timestampMs).toISOString(), + price: prices[mid]!.priceUsd, + }); return prices[mid]!; } @@ -216,13 +379,19 @@ export class CachingPricingProvider implements IPricingProvider, ICacheable { } } - // Return the floor value (largest timestamp <= target) - if ( - Math.abs(prices[left]!.timestampMs - targetTimestamp) > - MIN_GRANULARITY_MS * PROXIMITY_FACTOR - ) { + const timeDiff = Math.abs(prices[left]!.timestampMs - targetTimestamp); + if (timeDiff > MIN_GRANULARITY_MS * PROXIMITY_FACTOR) { + this.logger.debug("Closest price too far from target", { + timeDifference: `${timeDiff / 1000} seconds`, + }); return null; } + + this.logger.debug("Found closest price", { + priceTimestamp: new Date(prices[left]!.timestampMs).toISOString(), + price: prices[left]!.priceUsd, + timeDifference: `${timeDiff / 1000} seconds`, + }); return prices[left]!; } } diff --git a/packages/pricing/src/providers/coingecko.provider.ts b/packages/pricing/src/providers/coingecko.provider.ts index 3252ebf9..649f82fc 100644 --- a/packages/pricing/src/providers/coingecko.provider.ts +++ b/packages/pricing/src/providers/coingecko.provider.ts @@ -66,6 +66,56 @@ const TokenMapping: { [key: string]: CoingeckoTokenId | undefined } = { // sometimes coingecko returns no prices for 1 hour range, 2 hours works better const TIME_DELTA = 2 * 60 * 60 * 1000; +/** + * Utility function to retry API calls with delay + */ +const withRetry = async ( + operation: () => Promise, + retries = 3, + delayMs = 3000, + logger?: ILogger, + context?: { tokenId?: string; method?: string }, +): Promise => { + let lastError: Error = new Error("No error occurred"); + for (let attempt = 1; attempt <= retries; attempt++) { + try { + logger?.debug(`Attempt ${attempt}/${retries} for CoinGecko API call`, { + className: "CoingeckoProvider", + method: context?.method, + tokenId: context?.tokenId, + attempt, + retries, + }); + return await operation(); + } catch (error) { + lastError = error as Error; + logger?.warn(`CoinGecko API call failed (attempt ${attempt}/${retries})`, { + className: "CoingeckoProvider", + method: context?.method, + tokenId: context?.tokenId, + error: error instanceof Error ? error.message : String(error), + attempt, + retries, + willRetry: attempt < retries, + }); + if (attempt === retries) break; + logger?.debug(`Waiting ${delayMs}ms before retry`, { + className: "CoingeckoProvider", + method: context?.method, + tokenId: context?.tokenId, + }); + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } + } + logger?.error(`All retry attempts failed for CoinGecko API call`, { + className: "CoingeckoProvider", + method: context?.method, + tokenId: context?.tokenId, + error: lastError instanceof Error ? lastError.message : String(lastError), + }); + throw lastError; +}; + /** * The Coingecko provider is a pricing provider that uses the Coingecko API to get the price of a token. */ @@ -100,8 +150,20 @@ export class CoingeckoProvider implements IPricingProvider { startTimestampMs: TimestampMs, endTimestampMs?: TimestampMs, ): Promise { + this.logger.debug(`Getting token price`, { + className: CoingeckoProvider.name, + methodName: "getTokenPrice", + tokenCode, + startTimestampMs: new Date(startTimestampMs).toISOString(), + endTimestampMs: endTimestampMs ? new Date(endTimestampMs).toISOString() : undefined, + }); + const tokenId = TokenMapping[tokenCode]; if (!tokenId) { + this.logger.warn(`Unsupported token ${tokenCode}`, { + className: CoingeckoProvider.name, + methodName: "getTokenPrice", + }); throw new UnsupportedToken(tokenCode, { className: CoingeckoProvider.name, methodName: "getTokenPrice", @@ -125,18 +187,54 @@ export class CoingeckoProvider implements IPricingProvider { const path = `/coins/${tokenId}/market_chart/range?vs_currency=usd&from=${startTimestampMs / 1000}&to=${endTimestampMs / 1000}&precision=full`; try { - const { data } = await this.axios.get(path); + this.logger.debug(`Fetching price from CoinGecko`, { + className: CoingeckoProvider.name, + methodName: "getTokenPrice", + tokenCode, + path, + }); + + const { data } = await withRetry( + () => this.axios.get(path), + 3, + 3000, + this.logger, + { tokenId, method: "getTokenPrice" }, + ); + const closestEntry = data.prices.at(0); if (!closestEntry) { + this.logger.warn(`No price data returned from CoinGecko`, { + className: CoingeckoProvider.name, + methodName: "getTokenPrice", + tokenCode, + path, + }); return undefined; } + this.logger.debug(`Successfully fetched price`, { + className: CoingeckoProvider.name, + methodName: "getTokenPrice", + tokenCode, + timestamp: new Date(closestEntry[0]).toISOString(), + price: closestEntry[1], + }); + return { timestampMs: closestEntry[0] as TimestampMs, priceUsd: closestEntry[1], }; } catch (error: unknown) { if (isAxiosError(error)) { + this.logger.error(`Axios error in price fetch`, { + className: CoingeckoProvider.name, + methodName: "getTokenPrice", + tokenCode, + status: error.response?.status, + statusText: error.response?.statusText, + path, + }); this.handleAxiosError(error, path, "getTokenPrice"); } @@ -144,18 +242,38 @@ export class CoingeckoProvider implements IPricingProvider { `Unknown Coingecko API error: failed to fetch token price ` + stringify(error, Object.getOwnPropertyNames(error)); + this.logger.error(errorMessage, { + className: CoingeckoProvider.name, + methodName: "getTokenPrice", + tokenCode, + path, + error: error instanceof Error ? error.message : String(error), + }); + throw new UnknownPricingException(errorMessage, { className: CoingeckoProvider.name, methodName: "getTokenPrice", - additionalData: { - path, - }, + additionalData: { path }, }); } } /* @inheritdoc */ async getTokenPrices(tokenCode: TokenCode, timestamps: TimestampMs[]): Promise { + this.logger.info(`Starting price fetch for token ${tokenCode}`, { + className: CoingeckoProvider.name, + methodName: "getTokenPrices", + tokenCode, + timestampCount: timestamps.length, + timeRange: + timestamps.length > 0 + ? { + start: new Date(Math.min(...timestamps)).toISOString(), + end: new Date(Math.max(...timestamps)).toISOString(), + } + : undefined, + }); + const tokenId = TokenMapping[tokenCode]; if (!tokenId) { throw new UnsupportedToken(tokenCode, { @@ -190,11 +308,13 @@ export class CoingeckoProvider implements IPricingProvider { path = `/coins/${tokenId}/market_chart/range?vs_currency=usd&from=${Math.floor(currentStart / 1000)}&to=${Math.floor(currentEnd / 1000)}&precision=full`; // Push the promise for the current segment segments.push( - this.axios.get(path).then(({ data }) => - data.prices.map(([timestampMs, priceUsd]) => ({ - timestampMs, - priceUsd, - })), + withRetry(() => + this.axios.get(path).then(({ data }) => + data.prices.map(([timestampMs, priceUsd]) => ({ + timestampMs, + priceUsd, + })), + ), ), ); @@ -206,7 +326,7 @@ export class CoingeckoProvider implements IPricingProvider { return results.flat(); // Flatten the array of results } path = `/coins/${tokenId}/market_chart/range?vs_currency=usd&from=${effectiveMin / 1000}&to=${effectiveMax / 1000}&precision=full`; - const { data } = await this.axios.get(path); + const { data } = await withRetry(() => this.axios.get(path)); return data.prices.map(([timestampMs, priceUsd]) => ({ timestampMs, priceUsd, @@ -231,21 +351,36 @@ export class CoingeckoProvider implements IPricingProvider { } private handleAxiosError(error: AxiosError, path: string, methodName: string): void { + this.logger.error(`Handling Axios error`, { + className: CoingeckoProvider.name, + methodName, + status: error.status, + message: error.message, + path, + }); + const errorContext = { className: CoingeckoProvider.name, methodName, - additionalData: { - path, - }, + additionalData: { path }, }; if (error.status! >= 400 && error.status! < 500) { if (error.status === 429) { - throw new RateLimitError( - errorContext, - error.response?.headers["retry-after"] * 1000 || 60000, - ); + const retryAfter = error.response?.headers["retry-after"] * 1000 || 60000; + this.logger.warn(`Rate limit exceeded`, { + className: CoingeckoProvider.name, + methodName, + retryAfter: `${retryAfter / 1000} seconds`, + }); + throw new RateLimitError(errorContext, retryAfter); } else { + this.logger.error(`Non-retriable client error`, { + className: CoingeckoProvider.name, + methodName, + status: error.status, + code: error.code, + }); throw new NonRetriableError( `${error.status} ${error.code} - Coingecko API error: failed to fetch token price`, errorContext, diff --git a/packages/processors/src/processors/allo/allo.processor.ts b/packages/processors/src/processors/allo/allo.processor.ts index c0638c92..a695a3a3 100644 --- a/packages/processors/src/processors/allo/allo.processor.ts +++ b/packages/processors/src/processors/allo/allo.processor.ts @@ -18,42 +18,121 @@ export class AlloProcessor implements IProcessor<"Allo", AlloEvent> { constructor( private readonly chainId: ChainId, private readonly dependencies: ProcessorDependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing AlloProcessor", { + className: "AlloProcessor", + chainId: this.chainId, + }); + } async process(event: ProcessorEvent<"Allo", AlloEvent>): Promise { - switch (event.eventName) { - case "PoolCreated": - return new PoolCreatedHandler( - event as ProcessorEvent<"Allo", "PoolCreated">, - this.chainId, - this.dependencies, - ).handle(); - case "PoolFunded": - return new PoolFundedHandler( - event as ProcessorEvent<"Allo", "PoolFunded">, - this.chainId, - this.dependencies, - ).handle(); - case "RoleGranted": - return new RoleGrantedHandler( - event as ProcessorEvent<"Allo", "RoleGranted">, - this.chainId, - this.dependencies, - ).handle(); - case "PoolMetadataUpdated": - return new PoolMetadataUpdatedHandler( - event as ProcessorEvent<"Allo", "PoolMetadataUpdated">, - this.chainId, - this.dependencies, - ).handle(); - case "RoleRevoked": - return new RoleRevokedHandler( - event as ProcessorEvent<"Allo", "RoleRevoked">, - this.chainId, - this.dependencies, - ).handle(); - default: - throw new UnsupportedEventException("Allo", event.eventName); + const { logger } = this.dependencies; + + logger?.debug("Starting event processing", { + className: "AlloProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + blockNumber: event.blockNumber, + }); + + try { + let result: Changeset[]; + + switch (event.eventName) { + case "PoolCreated": + logger?.debug("Delegating to PoolCreatedHandler", { + className: "AlloProcessor", + methodName: "process", + params: event.params, + }); + result = await new PoolCreatedHandler( + event as ProcessorEvent<"Allo", "PoolCreated">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "PoolFunded": + logger?.debug("Delegating to PoolFundedHandler", { + className: "AlloProcessor", + methodName: "process", + params: event.params, + }); + result = await new PoolFundedHandler( + event as ProcessorEvent<"Allo", "PoolFunded">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "RoleGranted": + logger?.debug("Delegating to RoleGrantedHandler", { + className: "AlloProcessor", + methodName: "process", + params: event.params, + }); + result = await new RoleGrantedHandler( + event as ProcessorEvent<"Allo", "RoleGranted">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "PoolMetadataUpdated": + logger?.debug("Delegating to PoolMetadataUpdatedHandler", { + className: "AlloProcessor", + methodName: "process", + params: event.params, + }); + result = await new PoolMetadataUpdatedHandler( + event as ProcessorEvent<"Allo", "PoolMetadataUpdated">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "RoleRevoked": + logger?.debug("Delegating to RoleRevokedHandler", { + className: "AlloProcessor", + methodName: "process", + params: event.params, + }); + result = await new RoleRevokedHandler( + event as ProcessorEvent<"Allo", "RoleRevoked">, + this.chainId, + this.dependencies, + ).handle(); + break; + + default: + logger?.error("Unsupported event encountered", { + className: "AlloProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + }); + throw new UnsupportedEventException("Allo", event.eventName); + } + + logger?.info("Event processing completed", { + className: "AlloProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + changesetCount: result.length, + }); + + return result; + } catch (error) { + logger?.error("Error processing event", { + className: "AlloProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + error: error instanceof Error ? error.message : String(error), + }); + throw error; } } } diff --git a/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts b/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts index 82b7ae45..1b3e5c42 100644 --- a/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts +++ b/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts @@ -11,7 +11,7 @@ import { RoundMetadataSchema } from "../../../schemas/index.js"; type Dependencies = Pick< ProcessorDependencies, - "evmProvider" | "pricingProvider" | "metadataProvider" | "roundRepository" + "evmProvider" | "pricingProvider" | "metadataProvider" | "roundRepository" | "logger" >; /** @@ -30,10 +30,17 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> readonly event: ProcessorEvent<"Allo", "PoolCreated">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing PoolCreatedHandler", { + className: "PoolCreatedHandler", + chainId: this.chainId, + poolId: this.event.params.poolId, + blockNumber: this.event.blockNumber, + }); + } async handle(): Promise { - const { metadataProvider, evmProvider } = this.dependencies; + const { metadataProvider, evmProvider, logger } = this.dependencies; const metadataPointer = this.event.params.metadata[1]; const { poolId, @@ -41,21 +48,62 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> strategy: strategyAddress, amount, } = this.event.params; + + logger?.debug("Starting pool creation handling", { + className: "PoolCreatedHandler", + methodName: "handle", + poolId: poolId.toString(), + strategyAddress, + tokenAddress, + amount: amount.toString(), + metadataPointer, + }); + const fundedAmount = BigInt(amount); const { hash: txHash, from: txFrom } = this.event.transactionFields; const strategyId = this.event.strategyId; + logger?.debug("Fetching pool metadata", { + className: "PoolCreatedHandler", + methodName: "handle", + metadataPointer, + poolId: poolId.toString(), + }); + const metadata = await metadataProvider.getMetadata<{ round?: unknown; application?: unknown; }>(metadataPointer); - // TODO: this might not work for other gitcoin strategies like retro funding + + logger?.debug("Parsing round metadata", { + className: "PoolCreatedHandler", + methodName: "handle", + hasRoundMetadata: !!metadata?.round, + hasApplicationMetadata: !!metadata?.application, + poolId: poolId.toString(), + }); + const parsedRoundMetadata = RoundMetadataSchema.safeParse(metadata?.round); + logger?.debug("Round metadata validation result", { + className: "PoolCreatedHandler", + methodName: "handle", + isValid: parsedRoundMetadata.success, + poolId: poolId.toString(), + }); + const checksummedTokenAddress = getAddress(tokenAddress); const matchTokenAddress = isAlloNativeToken(checksummedTokenAddress) ? zeroAddress : checksummedTokenAddress; + + logger?.debug("Creating strategy handler", { + className: "PoolCreatedHandler", + methodName: "handle", + strategyId, + poolId: poolId.toString(), + }); + const strategyHandler = StrategyHandlerFactory.createHandler( this.chainId, this.dependencies as ProcessorDependencies, @@ -63,6 +111,14 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> ); const token = getToken(this.chainId, matchTokenAddress); + logger?.debug("Token details", { + className: "PoolCreatedHandler", + methodName: "handle", + hasToken: !!token, + tokenAddress: matchTokenAddress, + isNative: isAlloNativeToken(checksummedTokenAddress), + poolId: poolId.toString(), + }); let strategyTimings: StrategyTimings = { applicationsStartTime: null, @@ -76,29 +132,83 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> }; if (strategyHandler) { + logger?.debug("Fetching strategy timings", { + className: "PoolCreatedHandler", + methodName: "handle", + strategyAddress, + poolId: poolId.toString(), + }); + strategyTimings = await strategyHandler.fetchStrategyTimings(strategyAddress); + + logger?.debug("Strategy timings fetched", { + className: "PoolCreatedHandler", + methodName: "handle", + timings: strategyTimings, + poolId: poolId.toString(), + }); + if (parsedRoundMetadata.success && token) { + logger?.debug("Fetching match amount", { + className: "PoolCreatedHandler", + methodName: "handle", + matchingFundsAvailable: + parsedRoundMetadata.data.quadraticFundingConfig.matchingFundsAvailable, + poolId: poolId.toString(), + }); + matchAmountObj = await strategyHandler.fetchMatchAmount( Number(parsedRoundMetadata.data.quadraticFundingConfig.matchingFundsAvailable), token, this.event.blockTimestamp, ); + + logger?.debug("Match amount fetched", { + className: "PoolCreatedHandler", + methodName: "handle", + matchAmount: matchAmountObj.matchAmount.toString(), + matchAmountInUsd: matchAmountObj.matchAmountInUsd, + poolId: poolId.toString(), + }); } } let fundedAmountInUsd = "0"; if (token && fundedAmount > 0n) { + logger?.debug("Calculating funded amount in USD", { + className: "PoolCreatedHandler", + methodName: "handle", + fundedAmount: fundedAmount.toString(), + tokenAddress: token.address, + poolId: poolId.toString(), + }); + fundedAmountInUsd = await this.getTokenAmountInUsd( token, fundedAmount, this.event.blockTimestamp, ); + + logger?.debug("Funded amount in USD calculated", { + className: "PoolCreatedHandler", + methodName: "handle", + fundedAmountInUsd, + poolId: poolId.toString(), + }); } - // transaction sender const createdBy = txFrom ?? (await evmProvider.getTransaction(txHash)).from; const roundRoles = getRoundRoles(BigInt(poolId)); + + logger?.debug("Creating new round object", { + className: "PoolCreatedHandler", + methodName: "handle", + poolId: poolId.toString(), + createdBy, + strategyName: strategyHandler?.name ?? "", + }); + const newRound: NewRound = { chainId: this.chainId, id: poolId.toString(), @@ -137,7 +247,22 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> }, ]; - changes.push(...(await this.handlePendingRoles(this.chainId, poolId.toString()))); + logger?.debug("Handling pending roles", { + className: "PoolCreatedHandler", + methodName: "handle", + poolId: poolId.toString(), + }); + + const pendingRoleChanges = await this.handlePendingRoles(this.chainId, poolId.toString()); + changes.push(...pendingRoleChanges); + + logger?.info("Pool creation handling completed", { + className: "PoolCreatedHandler", + methodName: "handle", + poolId: poolId.toString(), + changeCount: changes.length, + }); + return changes; } @@ -153,13 +278,37 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> * pending roles to actual round roles. */ private async handlePendingRoles(chainId: ChainId, roundId: string): Promise { - const { roundRepository } = this.dependencies; + const { roundRepository, logger } = this.dependencies; const changes: Changeset[] = []; const allPendingRoles: PendingRoundRole[] = []; const { adminRole, managerRole } = getRoundRoles(BigInt(roundId)); + logger?.debug("Starting pending roles handling", { + className: "PoolCreatedHandler", + methodName: "handlePendingRoles", + roundId, + adminRole, + managerRole, + }); + for (const roleHash of [adminRole, managerRole] as const) { + logger?.debug("Fetching pending roles", { + className: "PoolCreatedHandler", + methodName: "handlePendingRoles", + roundId, + roleHash, + }); + const pendingRoles = await roundRepository.getPendingRoundRoles(chainId, roleHash); + + logger?.debug("Processing pending roles", { + className: "PoolCreatedHandler", + methodName: "handlePendingRoles", + roundId, + roleHash, + pendingRolesCount: pendingRoles.length, + }); + for (const pr of pendingRoles) { changes.push({ type: "InsertRoundRole", @@ -179,12 +328,26 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> const pendingRoleIds = [...new Set(allPendingRoles.map((r) => r.id!))]; if (pendingRoleIds.length > 0) { + logger?.debug("Deleting processed pending roles", { + className: "PoolCreatedHandler", + methodName: "handlePendingRoles", + roundId, + pendingRoleCount: pendingRoleIds.length, + }); + changes.push({ type: "DeletePendingRoundRoles", args: { ids: pendingRoleIds }, }); } + logger?.debug("Pending roles handling completed", { + className: "PoolCreatedHandler", + methodName: "handlePendingRoles", + roundId, + totalChanges: changes.length, + }); + return changes; } @@ -193,13 +356,37 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> amount: bigint, timestamp: TimestampMs, ): Promise { - const { pricingProvider } = this.dependencies; + const { pricingProvider, logger } = this.dependencies; + + logger?.debug("Fetching token price", { + className: "PoolCreatedHandler", + methodName: "getTokenAmountInUsd", + tokenAddress: token.address, + priceSourceCode: token.priceSourceCode, + timestamp, + }); + const tokenPrice = await pricingProvider.getTokenPrice(token.priceSourceCode, timestamp); if (!tokenPrice) { + logger?.error("Token price not found", { + className: "PoolCreatedHandler", + methodName: "getTokenAmountInUsd", + tokenAddress: token.address, + timestamp, + }); throw new TokenPriceNotFoundError(token.address, timestamp); } + logger?.debug("Calculating USD amount", { + className: "PoolCreatedHandler", + methodName: "getTokenAmountInUsd", + tokenAddress: token.address, + amount: amount.toString(), + priceUsd: tokenPrice.priceUsd, + decimals: token.decimals, + }); + return calculateAmountInUsd(amount, tokenPrice.priceUsd, token.decimals); } } diff --git a/packages/processors/src/processors/allo/handlers/poolFunded.handler.ts b/packages/processors/src/processors/allo/handlers/poolFunded.handler.ts index 116ea4dc..38a56266 100644 --- a/packages/processors/src/processors/allo/handlers/poolFunded.handler.ts +++ b/packages/processors/src/processors/allo/handlers/poolFunded.handler.ts @@ -19,19 +19,65 @@ export class PoolFundedHandler implements IEventHandler<"Allo", "PoolFunded"> { readonly event: ProcessorEvent<"Allo", "PoolFunded">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing PoolFundedHandler", { + className: "PoolFundedHandler", + chainId: this.chainId, + poolId: this.event.params.poolId.toString(), + blockNumber: this.event.blockNumber, + }); + } /* @inheritdoc */ async handle(): Promise { + const { roundRepository, pricingProvider, logger } = this.dependencies; const poolId = this.event.params.poolId.toString(); const fundedAmount = BigInt(this.event.params.amount); - const { roundRepository, pricingProvider } = this.dependencies; + + logger?.debug("Starting pool funding handling", { + className: "PoolFundedHandler", + methodName: "handle", + poolId, + fundedAmount: fundedAmount.toString(), + blockNumber: this.event.blockNumber, + }); + + logger?.debug("Fetching round data", { + className: "PoolFundedHandler", + methodName: "handle", + poolId, + chainId: this.chainId, + }); const round = await roundRepository.getRoundByIdOrThrow(this.chainId, poolId); + logger?.debug("Getting token information", { + className: "PoolFundedHandler", + methodName: "handle", + poolId, + matchTokenAddress: round.matchTokenAddress, + }); + const token = getToken(this.chainId, round.matchTokenAddress); - //TODO: Review this on Advace Recovery Milestone - if (!token) throw new UnknownToken(round.matchTokenAddress, this.chainId); + if (!token) { + logger?.error("Unknown token encountered", { + className: "PoolFundedHandler", + methodName: "handle", + poolId, + tokenAddress: round.matchTokenAddress, + chainId: this.chainId, + }); + throw new UnknownToken(round.matchTokenAddress, this.chainId); + } + + logger?.debug("Calculating USD amount", { + className: "PoolFundedHandler", + methodName: "handle", + poolId, + tokenAddress: token.address, + amount: fundedAmount.toString(), + timestamp: this.event.blockTimestamp, + }); const { amountInUsd } = await getTokenAmountInUsd( pricingProvider, @@ -40,6 +86,15 @@ export class PoolFundedHandler implements IEventHandler<"Allo", "PoolFunded"> { this.event.blockTimestamp, ); + logger?.info("Pool funding processed successfully", { + className: "PoolFundedHandler", + methodName: "handle", + poolId, + fundedAmount: fundedAmount.toString(), + amountInUsd, + tokenAddress: token.address, + }); + return [ { type: "IncrementRoundFundedAmount", diff --git a/packages/processors/src/processors/allo/handlers/poolMetadataUpdated.handler.ts b/packages/processors/src/processors/allo/handlers/poolMetadataUpdated.handler.ts index fd8bd11e..ee62cc59 100644 --- a/packages/processors/src/processors/allo/handlers/poolMetadataUpdated.handler.ts +++ b/packages/processors/src/processors/allo/handlers/poolMetadataUpdated.handler.ts @@ -10,7 +10,7 @@ import { RoundMetadataSchema } from "../../../schemas/index.js"; type Dependencies = Pick< ProcessorDependencies, - "metadataProvider" | "roundRepository" | "pricingProvider" + "metadataProvider" | "roundRepository" | "pricingProvider" | "logger" >; /** @@ -25,33 +25,93 @@ export class PoolMetadataUpdatedHandler implements IEventHandler<"Allo", "PoolMe readonly event: ProcessorEvent<"Allo", "PoolMetadataUpdated">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} - /* @inheritdoc */ + ) { + this.dependencies.logger?.debug("Initializing PoolMetadataUpdatedHandler", { + className: "PoolMetadataUpdatedHandler", + chainId: this.chainId, + poolId: this.event.params.poolId.toString(), + blockNumber: this.event.blockNumber, + }); + } + async handle(): Promise { + const { metadataProvider, pricingProvider, roundRepository, logger } = this.dependencies; const [_protocol, metadataPointer] = this.event.params.metadata; - const { metadataProvider, pricingProvider, roundRepository } = this.dependencies; + const poolId = this.event.params.poolId.toString(); + + logger?.debug("Starting pool metadata update handling", { + className: "PoolMetadataUpdatedHandler", + methodName: "handle", + poolId, + metadataPointer, + }); + + logger?.debug("Fetching metadata", { + className: "PoolMetadataUpdatedHandler", + methodName: "handle", + poolId, + metadataPointer, + }); const metadata = await metadataProvider.getMetadata<{ round?: unknown; application?: unknown; }>(metadataPointer); - const round = await roundRepository.getRoundByIdOrThrow( - this.chainId, - this.event.params.poolId.toString(), - ); + logger?.debug("Fetching round data", { + className: "PoolMetadataUpdatedHandler", + methodName: "handle", + poolId, + hasRoundMetadata: !!metadata?.round, + hasApplicationMetadata: !!metadata?.application, + }); + + const round = await roundRepository.getRoundByIdOrThrow(this.chainId, poolId); let matchAmount = round.matchAmount; let matchAmountInUsd = round.matchAmountInUsd; + logger?.debug("Parsing round metadata", { + className: "PoolMetadataUpdatedHandler", + methodName: "handle", + poolId, + currentMatchAmount: matchAmount.toString(), + }); + const parsedRoundMetadata = RoundMetadataSchema.safeParse(metadata?.round); const token = getToken(this.chainId, round.matchTokenAddress); + logger?.debug("Token and metadata validation", { + className: "PoolMetadataUpdatedHandler", + methodName: "handle", + poolId, + hasToken: !!token, + metadataParseSuccess: parsedRoundMetadata.success, + tokenAddress: round.matchTokenAddress, + }); + if (parsedRoundMetadata.success && token) { + logger?.debug("Updating match amounts", { + className: "PoolMetadataUpdatedHandler", + methodName: "handle", + poolId, + matchingFundsAvailable: + parsedRoundMetadata.data.quadraticFundingConfig.matchingFundsAvailable, + tokenDecimals: token.decimals, + }); + matchAmount = parseUnits( parsedRoundMetadata.data.quadraticFundingConfig.matchingFundsAvailable.toString(), token.decimals, ); + + logger?.debug("Calculating USD amount", { + className: "PoolMetadataUpdatedHandler", + methodName: "handle", + poolId, + newMatchAmount: matchAmount.toString(), + }); + matchAmountInUsd = ( await getTokenAmountInUsd( pricingProvider, @@ -60,14 +120,31 @@ export class PoolMetadataUpdatedHandler implements IEventHandler<"Allo", "PoolMe this.event.blockTimestamp, ) ).amountInUsd; + + logger?.debug("Match amounts updated", { + className: "PoolMetadataUpdatedHandler", + methodName: "handle", + poolId, + matchAmount: matchAmount.toString(), + matchAmountInUsd, + }); } + logger?.info("Pool metadata update completed", { + className: "PoolMetadataUpdatedHandler", + methodName: "handle", + poolId, + metadataPointer, + matchAmount: matchAmount.toString(), + matchAmountInUsd, + }); + return [ { type: "UpdateRound", args: { chainId: this.chainId, - roundId: this.event.params.poolId.toString(), + roundId: poolId, round: { matchAmount, matchAmountInUsd, diff --git a/packages/processors/src/processors/allo/handlers/roleGranted.handler.ts b/packages/processors/src/processors/allo/handlers/roleGranted.handler.ts index b9502122..8089ecac 100644 --- a/packages/processors/src/processors/allo/handlers/roleGranted.handler.ts +++ b/packages/processors/src/processors/allo/handlers/roleGranted.handler.ts @@ -5,7 +5,7 @@ import type { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; import type { IEventHandler, ProcessorDependencies } from "../../../internal.js"; -type Dependencies = Pick; +type Dependencies = Pick; /** * Handles the RoleGranted event for the Allo protocol. @@ -20,18 +20,49 @@ export class RoleGrantedHandler implements IEventHandler<"Allo", "RoleGranted"> readonly event: ProcessorEvent<"Allo", "RoleGranted">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing RoleGrantedHandler", { + className: "RoleGrantedHandler", + chainId: this.chainId, + role: this.event.params.role, + account: this.event.params.account, + blockNumber: this.event.blockNumber, + }); + } /* @inheritdoc */ async handle(): Promise { + const { roundRepository, logger } = this.dependencies; const role = this.event.params.role.toLowerCase(); const account = getAddress(this.event.params.account); - const { roundRepository } = this.dependencies; + + logger?.debug("Starting role grant handling", { + className: "RoleGrantedHandler", + methodName: "handle", + role, + account, + blockNumber: this.event.blockNumber, + }); let round: Round | undefined = undefined; - // search for a round where the admin role is the role granted + logger?.debug("Searching for round with admin role", { + className: "RoleGrantedHandler", + methodName: "handle", + role, + account, + }); + round = await roundRepository.getRoundByRole(this.chainId, "admin", role); + if (round) { + logger?.info("Found round with matching admin role", { + className: "RoleGrantedHandler", + methodName: "handle", + roundId: round.id, + role, + account, + }); + return [ { type: "InsertRoundRole", @@ -48,9 +79,24 @@ export class RoleGrantedHandler implements IEventHandler<"Allo", "RoleGranted"> ]; } - // search for a round where the manager role is the role granted + logger?.debug("Searching for round with manager role", { + className: "RoleGrantedHandler", + methodName: "handle", + role, + account, + }); + round = await roundRepository.getRoundByRole(this.chainId, "manager", role); + if (round) { + logger?.info("Found round with matching manager role", { + className: "RoleGrantedHandler", + methodName: "handle", + roundId: round.id, + role, + account, + }); + return [ { type: "InsertRoundRole", @@ -67,6 +113,14 @@ export class RoleGrantedHandler implements IEventHandler<"Allo", "RoleGranted"> ]; } + logger?.info("No matching round found, creating pending role", { + className: "RoleGrantedHandler", + methodName: "handle", + role, + account, + chainId: this.chainId, + }); + return [ { type: "InsertPendingRoundRole", diff --git a/packages/processors/src/processors/allo/handlers/roleRevoked.handler.ts b/packages/processors/src/processors/allo/handlers/roleRevoked.handler.ts index 47b238bc..8f7e730c 100644 --- a/packages/processors/src/processors/allo/handlers/roleRevoked.handler.ts +++ b/packages/processors/src/processors/allo/handlers/roleRevoked.handler.ts @@ -19,17 +19,49 @@ export class RoleRevokedHandler implements IEventHandler<"Allo", "RoleRevoked"> readonly event: ProcessorEvent<"Allo", "RoleRevoked">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing RoleRevokedHandler", { + className: "RoleRevokedHandler", + chainId: this.chainId, + role: this.event.params.role, + account: this.event.params.account, + blockNumber: this.event.blockNumber, + }); + } /* @inheritdoc */ async handle(): Promise { const role = this.event.params.role.toLowerCase(); const account = getAddress(this.event.params.account); const { roundRepository, logger } = this.dependencies; + + logger?.debug("Starting role revocation handling", { + className: "RoleRevokedHandler", + methodName: "handle", + role, + account, + blockNumber: this.event.blockNumber, + }); + let round: Round | undefined = undefined; - // search for a round where the admin role is the role granted + logger?.debug("Searching for round with admin role", { + className: "RoleRevokedHandler", + methodName: "handle", + role, + account, + }); + round = await roundRepository.getRoundByRole(this.chainId, "admin", role); + if (round) { + logger?.info("Found round with matching admin role, deleting role", { + className: "RoleRevokedHandler", + methodName: "handle", + roundId: round.id, + role, + account, + }); + return [ { type: "DeleteAllRoundRolesByRoleAndAddress", @@ -45,9 +77,24 @@ export class RoleRevokedHandler implements IEventHandler<"Allo", "RoleRevoked"> ]; } - // search for a round where the manager role is the role granted + logger?.debug("Searching for round with manager role", { + className: "RoleRevokedHandler", + methodName: "handle", + role, + account, + }); + round = await roundRepository.getRoundByRole(this.chainId, "manager", role); + if (round) { + logger?.info("Found round with matching manager role, deleting role", { + className: "RoleRevokedHandler", + methodName: "handle", + roundId: round.id, + role, + account, + }); + return [ { type: "DeleteAllRoundRolesByRoleAndAddress", @@ -63,7 +110,14 @@ export class RoleRevokedHandler implements IEventHandler<"Allo", "RoleRevoked"> ]; } - logger.warn(`No round found for role ${role} on chain ${this.chainId}`); + logger?.warn("No matching round found for role revocation", { + className: "RoleRevokedHandler", + methodName: "handle", + role, + account, + chainId: this.chainId, + }); + return []; } } diff --git a/packages/processors/src/processors/alloV1ToV2ProfileMigration/alloV1ToV2ProfileMigration.processor.ts b/packages/processors/src/processors/alloV1ToV2ProfileMigration/alloV1ToV2ProfileMigration.processor.ts index 187d637a..4370583d 100644 --- a/packages/processors/src/processors/alloV1ToV2ProfileMigration/alloV1ToV2ProfileMigration.processor.ts +++ b/packages/processors/src/processors/alloV1ToV2ProfileMigration/alloV1ToV2ProfileMigration.processor.ts @@ -18,20 +18,77 @@ export class AlloV1ToV2ProfileMigrationProcessor constructor( private readonly chainId: ChainId, private readonly dependencies: ProcessorDependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing AlloV1ToV2ProfileMigrationProcessor", { + className: "AlloV1ToV2ProfileMigrationProcessor", + chainId: this.chainId, + }); + } async process( event: ProcessorEvent<"AlloV1ToV2ProfileMigration", AlloV1ToV2ProfileMigrationEvent>, ): Promise { - switch (event.eventName) { - case "ProfileMigrated": - return new ProfileMigratedHandler( - event as ProcessorEvent<"AlloV1ToV2ProfileMigration", "ProfileMigrated">, - this.chainId, - this.dependencies, - ).handle(); - default: - throw new UnsupportedEventException("AlloV1ToV2ProfileMigration", event.eventName); + const { logger } = this.dependencies; + + logger?.debug("Starting event processing", { + className: "AlloV1ToV2ProfileMigrationProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + blockNumber: event.blockNumber, + }); + + try { + let result: Changeset[]; + + switch (event.eventName) { + case "ProfileMigrated": + logger?.debug("Delegating to ProfileMigratedHandler", { + className: "AlloV1ToV2ProfileMigrationProcessor", + methodName: "process", + v1ProjectId: event.params.alloV1, + v1ChainId: event.params.alloV1ChainId.toString(), + v2ProjectId: event.params.alloV2, + }); + + result = await new ProfileMigratedHandler( + event as ProcessorEvent<"AlloV1ToV2ProfileMigration", "ProfileMigrated">, + this.chainId, + this.dependencies, + ).handle(); + break; + + default: + logger?.error("Unsupported event encountered", { + className: "AlloV1ToV2ProfileMigrationProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + }); + throw new UnsupportedEventException( + "AlloV1ToV2ProfileMigration", + event.eventName, + ); + } + + logger?.info("Event processing completed", { + className: "AlloV1ToV2ProfileMigrationProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + changesetCount: result.length, + }); + + return result; + } catch (error) { + logger?.error("Error processing event", { + className: "AlloV1ToV2ProfileMigrationProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + error: error instanceof Error ? error.message : String(error), + }); + throw error; } } } diff --git a/packages/processors/src/processors/alloV1ToV2ProfileMigration/handlers/ProfileMigrated.ts b/packages/processors/src/processors/alloV1ToV2ProfileMigration/handlers/ProfileMigrated.ts index ea05fff9..8fad9bf5 100644 --- a/packages/processors/src/processors/alloV1ToV2ProfileMigration/handlers/ProfileMigrated.ts +++ b/packages/processors/src/processors/alloV1ToV2ProfileMigration/handlers/ProfileMigrated.ts @@ -17,10 +17,36 @@ export class ProfileMigratedHandler readonly event: ProcessorEvent<"AlloV1ToV2ProfileMigration", "ProfileMigrated">, readonly chainId: ChainId, private dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing ProfileMigratedHandler", { + className: "ProfileMigratedHandler", + chainId: this.chainId, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } + async handle(): Promise { + const { logger } = this.dependencies; const { alloV1, alloV1ChainId, alloV2 } = this.event.params; + logger?.debug("Starting profile migration handling", { + className: "ProfileMigratedHandler", + methodName: "handle", + v1ProjectId: alloV1, + v1ChainId: alloV1ChainId.toString(), + v2ProjectId: alloV2, + blockNumber: this.event.blockNumber, + }); + + logger?.debug("Creating legacy project mapping", { + className: "ProfileMigratedHandler", + methodName: "handle", + v1ProjectId: alloV1, + v1ChainId: alloV1ChainId.toString(), + v2ProjectId: alloV2, + }); + const changes: Changeset[] = [ { type: "InsertLegacyProject", @@ -34,6 +60,15 @@ export class ProfileMigratedHandler }, ]; + logger?.info("Profile migration completed", { + className: "ProfileMigratedHandler", + methodName: "handle", + v1ProjectId: alloV1, + v1ChainId: alloV1ChainId.toString(), + v2ProjectId: alloV2, + changeCount: changes.length, + }); + return changes; } } diff --git a/packages/processors/src/processors/gitcoinAttestationNetwork/handlers/onAttested.handler.ts b/packages/processors/src/processors/gitcoinAttestationNetwork/handlers/onAttested.handler.ts index eefd007e..b27f3187 100644 --- a/packages/processors/src/processors/gitcoinAttestationNetwork/handlers/onAttested.handler.ts +++ b/packages/processors/src/processors/gitcoinAttestationNetwork/handlers/onAttested.handler.ts @@ -22,27 +22,85 @@ export class OnAttestedHandler implements IEventHandler<"GitcoinAttestationNetwo readonly event: ProcessorEvent<"GitcoinAttestationNetwork", "OnAttested">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing OnAttestedHandler", { + className: "OnAttestedHandler", + chainId: this.chainId, + attestationId: this.event.params.uid, + blockNumber: this.event.blockNumber, + }); + } async handle(): Promise { - const { metadataProvider } = this.dependencies; + const { metadataProvider, logger } = this.dependencies; const { fee, refUID, recipient: _recipient, data } = this.event.params; const attestationId = this.event.params.uid; const recipient = getAddress(_recipient); + logger?.debug("Starting attestation handling", { + className: "OnAttestedHandler", + methodName: "handle", + attestationId, + recipient, + refUID, + fee: fee.toString(), + }); + + logger?.debug("Decoding attestation data", { + className: "OnAttestedHandler", + methodName: "handle", + attestationId, + dataLength: data.length, + }); + const decodedAttestationData = decodeAttestedData(data); + logger?.debug("Fetching attestation metadata", { + className: "OnAttestedHandler", + methodName: "handle", + attestationId, + metadataCid: decodedAttestationData.metadataCid, + }); + let attestationMetadata = await metadataProvider.getMetadata< AttestationMetadata[] | undefined >(decodedAttestationData.metadataCid); + logger?.debug("Processing attestation metadata", { + className: "OnAttestedHandler", + methodName: "handle", + attestationId, + hasMetadata: !!attestationMetadata, + metadataLength: attestationMetadata?.length ?? 0, + }); + if (attestationMetadata === null || attestationMetadata === undefined) { + logger?.debug("No metadata found, defaulting to empty array", { + className: "OnAttestedHandler", + methodName: "handle", + attestationId, + }); attestationMetadata = []; } const transactionsData: AttestationTxnData[] = []; + logger?.debug("Processing transaction metadata", { + className: "OnAttestedHandler", + methodName: "handle", + attestationId, + metadataCount: attestationMetadata.length, + }); + for (let i = 0; i < attestationMetadata.length; i++) { const metadata = attestationMetadata[i]!; + logger?.debug("Processing transaction", { + className: "OnAttestedHandler", + methodName: "handle", + attestationId, + index: i, + chainId: metadata.chainId, + txnHash: metadata.txnHash, + }); transactionsData.push({ chainId: metadata.chainId, @@ -50,6 +108,16 @@ export class OnAttestedHandler implements IEventHandler<"GitcoinAttestationNetwo }); } + logger?.debug("Creating attestation data", { + className: "OnAttestedHandler", + methodName: "handle", + attestationId, + projectsCount: decodedAttestationData.projectsContributed, + roundsCount: decodedAttestationData.roundsContributed, + chainsCount: decodedAttestationData.chainIdsContributed, + totalUSDAmount: decodedAttestationData.totalUSDAmount, + }); + const attestationData: Attestation = { uid: attestationId, chainId: this.chainId, @@ -75,6 +143,14 @@ export class OnAttestedHandler implements IEventHandler<"GitcoinAttestationNetwo }, ]; + logger?.info("Attestation processing completed", { + className: "OnAttestedHandler", + methodName: "handle", + attestationId, + transactionsCount: transactionsData.length, + changeCount: changes.length, + }); + return changes; } } diff --git a/packages/processors/src/processors/registry/handlers/profileCreated.handler.ts b/packages/processors/src/processors/registry/handlers/profileCreated.handler.ts index 1e09ac49..922b047a 100644 --- a/packages/processors/src/processors/registry/handlers/profileCreated.handler.ts +++ b/packages/processors/src/processors/registry/handlers/profileCreated.handler.ts @@ -25,13 +25,44 @@ export class ProfileCreatedHandler implements IEventHandler<"Registry", "Profile readonly event: ProcessorEvent<"Registry", "ProfileCreated">, readonly chainId: ChainId, private dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing ProfileCreatedHandler", { + className: "ProfileCreatedHandler", + chainId: this.chainId, + profileId: this.event.params.profileId, + blockNumber: this.event.blockNumber, + }); + } + async handle(): Promise { - const { metadataProvider, evmProvider, projectRepository } = this.dependencies; + const { metadataProvider, evmProvider, projectRepository, logger } = this.dependencies; const profileId = this.event.params.profileId; const metadataCid = this.event.params.metadata[1]; + + logger?.debug("Starting profile creation handling", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + metadataCid, + name: this.event.params.name, + }); + + logger?.debug("Fetching profile metadata", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + metadataCid, + }); + const metadata = await metadataProvider.getMetadata(metadataCid); + logger?.debug("Parsing metadata", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + hasMetadata: !!metadata, + }); + const parsedMetadata = ProjectMetadataSchema.safeParse(metadata); let projectType: ProjectType = "canonical"; @@ -39,16 +70,46 @@ export class ProfileCreatedHandler implements IEventHandler<"Registry", "Profile let metadataValue = null; if (parsedMetadata.success) { + logger?.debug("Metadata validation successful", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + metadataType: parsedMetadata.data.type, + }); + projectType = this.getProjectTypeFromMetadata(parsedMetadata.data); isProgram = parsedMetadata.data.type === "program"; metadataValue = parsedMetadata.data; + } else { + logger?.warn("Invalid metadata format", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + errors: parsedMetadata.error.errors, + }); } + logger?.debug("Fetching transaction creator", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + txHash: this.event.transactionFields.hash, + }); + const createdBy = this.event.transactionFields.from ?? (await evmProvider.getTransaction(this.event.transactionFields.hash)).from; const programTags = isProgram ? ["program"] : []; + logger?.debug("Creating project record", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + projectType, + isProgram, + createdBy, + }); + const changes: Changeset[] = [ { type: "InsertProject", @@ -77,7 +138,7 @@ export class ProfileCreatedHandler implements IEventHandler<"Registry", "Profile args: { projectRole: { chainId: this.chainId, - projectId: this.event.params.profileId, + projectId: profileId, address: getAddress(this.event.params.owner), role: "owner", createdAtBlock: BigInt(this.event.blockNumber), @@ -86,13 +147,33 @@ export class ProfileCreatedHandler implements IEventHandler<"Registry", "Profile }, ]; + logger?.debug("Fetching pending project roles", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + }); + const pendingProjectRoles = await projectRepository.getPendingProjectRolesByRole( this.chainId, profileId, ); if (pendingProjectRoles.length !== 0) { + logger?.debug("Processing pending project roles", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + pendingRolesCount: pendingProjectRoles.length, + }); + for (const role of pendingProjectRoles) { + logger?.debug("Adding member role", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + memberAddress: role.address, + }); + changes.push({ type: "InsertProjectRole", args: { @@ -113,15 +194,26 @@ export class ProfileCreatedHandler implements IEventHandler<"Registry", "Profile }); } + logger?.info("Profile creation completed", { + className: "ProfileCreatedHandler", + methodName: "handle", + profileId, + projectType, + changeCount: changes.length, + pendingRolesProcessed: pendingProjectRoles.length, + }); + return changes; } private getProjectTypeFromMetadata(metadata: ProjectMetadata): ProjectType { - // if the metadata contains a canonical reference, it's a linked project - if ("canonical" in metadata) { - return "linked"; - } - - return "canonical"; + const projectType = "canonical" in metadata ? "linked" : "canonical"; + this.dependencies.logger?.debug("Determined project type", { + className: "ProfileCreatedHandler", + methodName: "getProjectTypeFromMetadata", + projectType, + hasCanonicalField: "canonical" in metadata, + }); + return projectType; } } diff --git a/packages/processors/src/processors/registry/handlers/profileMetadataUpdated.handler.ts b/packages/processors/src/processors/registry/handlers/profileMetadataUpdated.handler.ts index 0da46273..364e329c 100644 --- a/packages/processors/src/processors/registry/handlers/profileMetadataUpdated.handler.ts +++ b/packages/processors/src/processors/registry/handlers/profileMetadataUpdated.handler.ts @@ -24,22 +24,67 @@ export class ProfileMetadataUpdatedHandler readonly event: ProcessorEvent<"Registry", "ProfileMetadataUpdated">, readonly chainId: ChainId, private dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing ProfileMetadataUpdatedHandler", { + className: "ProfileMetadataUpdatedHandler", + chainId: this.chainId, + profileId: this.event.params.profileId, + blockNumber: this.event.blockNumber, + }); + } /* @inheritdoc */ async handle(): Promise { - const { metadataProvider } = this.dependencies; - + const { metadataProvider, logger } = this.dependencies; + const profileId = this.event.params.profileId; const metadataCid = this.event.params.metadata[1]; + + logger?.debug("Starting profile metadata update", { + className: "ProfileMetadataUpdatedHandler", + methodName: "handle", + profileId, + metadataCid, + blockNumber: this.event.blockNumber, + }); + + logger?.debug("Fetching metadata", { + className: "ProfileMetadataUpdatedHandler", + methodName: "handle", + profileId, + metadataCid, + }); + const metadata = await metadataProvider.getMetadata(metadataCid); + + logger?.debug("Parsing metadata", { + className: "ProfileMetadataUpdatedHandler", + methodName: "handle", + profileId, + hasMetadata: !!metadata, + }); + const parsedMetadata = ProjectMetadataSchema.safeParse(metadata); if (!parsedMetadata.success) { + logger?.warn("Invalid metadata format", { + className: "ProfileMetadataUpdatedHandler", + methodName: "handle", + profileId, + errors: parsedMetadata.error.errors, + }); + + logger?.debug("Creating update with null metadata", { + className: "ProfileMetadataUpdatedHandler", + methodName: "handle", + profileId, + projectType: "canonical", + }); + return [ { type: "UpdateProject", args: { chainId: this.chainId, - projectId: this.event.params.profileId, + projectId: profileId, project: { metadataCid: metadataCid, metadata: null, @@ -50,14 +95,29 @@ export class ProfileMetadataUpdatedHandler ]; } + logger?.debug("Determining project type from metadata", { + className: "ProfileMetadataUpdatedHandler", + methodName: "handle", + profileId, + metadataType: parsedMetadata.data.type, + }); + const projectType = this.getProjectTypeFromMetadata(parsedMetadata.data); + logger?.info("Profile metadata update completed", { + className: "ProfileMetadataUpdatedHandler", + methodName: "handle", + profileId, + projectType, + metadataCid, + }); + return [ { type: "UpdateProject", args: { chainId: this.chainId, - projectId: this.event.params.profileId, + projectId: profileId, project: { metadataCid: metadataCid, metadata: metadata, @@ -68,11 +128,13 @@ export class ProfileMetadataUpdatedHandler ]; } private getProjectTypeFromMetadata(metadata: ProjectMetadata): ProjectType { - // if the metadata contains a canonical reference, it's a linked project - if ("canonical" in metadata) { - return "linked"; - } - - return "canonical"; + const projectType = "canonical" in metadata ? "linked" : "canonical"; + this.dependencies.logger?.debug("Determined project type", { + className: "ProfileMetadataUpdatedHandler", + methodName: "getProjectTypeFromMetadata", + projectType, + hasCanonicalField: "canonical" in metadata, + }); + return projectType; } } diff --git a/packages/processors/src/processors/registry/handlers/profileNameUpdated.handler.ts b/packages/processors/src/processors/registry/handlers/profileNameUpdated.handler.ts index b2461119..d502a032 100644 --- a/packages/processors/src/processors/registry/handlers/profileNameUpdated.handler.ts +++ b/packages/processors/src/processors/registry/handlers/profileNameUpdated.handler.ts @@ -17,21 +17,53 @@ export class ProfileNameUpdatedHandler implements IEventHandler<"Registry", "Pro readonly event: ProcessorEvent<"Registry", "ProfileNameUpdated">, readonly chainId: ChainId, private dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing ProfileNameUpdatedHandler", { + className: "ProfileNameUpdatedHandler", + chainId: this.chainId, + profileId: this.event.params.profileId, + blockNumber: this.event.blockNumber, + }); + } /* @inheritdoc */ async handle(): Promise { - return [ + const { logger } = this.dependencies; + const profileId = this.event.params.profileId; + const newName = this.event.params.name; + const newAnchor = getAddress(this.event.params.anchor); + + logger?.debug("Starting profile name update", { + className: "ProfileNameUpdatedHandler", + methodName: "handle", + profileId, + newName, + newAnchor, + blockNumber: this.event.blockNumber, + }); + + const changes: Changeset[] = [ { type: "UpdateProject", args: { chainId: this.chainId, - projectId: this.event.params.profileId, + projectId: profileId, project: { - name: this.event.params.name, - anchorAddress: getAddress(this.event.params.anchor), + name: newName, + anchorAddress: newAnchor, }, }, }, ]; + + logger?.info("Profile name update completed", { + className: "ProfileNameUpdatedHandler", + methodName: "handle", + profileId, + newName, + newAnchor, + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/registry/handlers/profileOwnerUpdated.handler.ts b/packages/processors/src/processors/registry/handlers/profileOwnerUpdated.handler.ts index 45fefca5..5ba91f20 100644 --- a/packages/processors/src/processors/registry/handlers/profileOwnerUpdated.handler.ts +++ b/packages/processors/src/processors/registry/handlers/profileOwnerUpdated.handler.ts @@ -21,16 +21,42 @@ export class ProfileOwnerUpdatedHandler readonly event: ProcessorEvent<"Registry", "ProfileOwnerUpdated">, readonly chainId: ChainId, private dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing ProfileOwnerUpdatedHandler", { + className: "ProfileOwnerUpdatedHandler", + chainId: this.chainId, + profileId: this.event.params.profileId, + blockNumber: this.event.blockNumber, + }); + } /* @inheritdoc */ async handle(): Promise { - return [ + const { logger } = this.dependencies; + const profileId = this.event.params.profileId; + const newOwner = getAddress(this.event.params.owner); + + logger?.debug("Starting profile owner update", { + className: "ProfileOwnerUpdatedHandler", + methodName: "handle", + profileId, + newOwner, + blockNumber: this.event.blockNumber, + }); + + logger?.debug("Removing existing owner roles", { + className: "ProfileOwnerUpdatedHandler", + methodName: "handle", + profileId, + chainId: this.chainId, + }); + + const changes: Changeset[] = [ { type: "DeleteAllProjectRolesByRole", args: { projectRole: { chainId: this.chainId, - projectId: this.event.params.profileId, + projectId: profileId, role: "owner", }, }, @@ -40,13 +66,24 @@ export class ProfileOwnerUpdatedHandler args: { projectRole: { chainId: this.chainId, - projectId: this.event.params.profileId, - address: getAddress(this.event.params.owner), + projectId: profileId, + address: newOwner, role: "owner", createdAtBlock: BigInt(this.event.blockNumber), }, }, }, ]; + + logger?.info("Profile owner update completed", { + className: "ProfileOwnerUpdatedHandler", + methodName: "handle", + profileId, + newOwner, + changeCount: changes.length, + blockNumber: this.event.blockNumber, + }); + + return changes; } } diff --git a/packages/processors/src/processors/registry/handlers/roleGranted.handler.ts b/packages/processors/src/processors/registry/handlers/roleGranted.handler.ts index 2308eee9..63a154bf 100644 --- a/packages/processors/src/processors/registry/handlers/roleGranted.handler.ts +++ b/packages/processors/src/processors/registry/handlers/roleGranted.handler.ts @@ -14,23 +14,55 @@ export class RoleGrantedHandler implements IEventHandler<"Registry", "RoleGrante readonly event: ProcessorEvent<"Registry", "RoleGranted">, readonly chainId: ChainId, private readonly dependencies: ProcessorDependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing RoleGrantedHandler", { + className: "RoleGrantedHandler", + chainId: this.chainId, + role: this.event.params.role, + blockNumber: this.event.blockNumber, + }); + } + async handle(): Promise { - const { projectRepository } = this.dependencies; + const { projectRepository, logger } = this.dependencies; const role = this.event.params.role.toLowerCase(); + + logger?.debug("Starting role grant handling", { + className: "RoleGrantedHandler", + methodName: "handle", + role, + blockNumber: this.event.blockNumber, + }); + if (role === ALLO_OWNER_ROLE) { + logger?.debug("Skipping Allo owner role grant", { + className: "RoleGrantedHandler", + methodName: "handle", + role, + }); return []; } const account = getAddress(this.event.params.account); + + logger?.debug("Fetching project for role", { + className: "RoleGrantedHandler", + methodName: "handle", + role, + account, + }); + const project = await projectRepository.getProjectById(this.chainId, role); - // The member role for an Allo V2 profile, is the profileId itself. - // If a project exist with that id, we create the member role - // If it doesn't exist we create a pending project role. This can happen - // when a new project is created, since in Allo V2 the RoleGranted event for a member is - // emitted before the ProfileCreated event. if (project) { + logger?.info("Creating member role for existing project", { + className: "RoleGrantedHandler", + methodName: "handle", + projectId: project.id, + account, + role, + }); + return [ { type: "InsertProjectRole", @@ -47,6 +79,14 @@ export class RoleGrantedHandler implements IEventHandler<"Registry", "RoleGrante ]; } + logger?.info("Creating pending project role", { + className: "RoleGrantedHandler", + methodName: "handle", + role, + account, + chainId: this.chainId, + }); + return [ { type: "InsertPendingProjectRole", diff --git a/packages/processors/src/processors/registry/handlers/roleRevoked.handler.ts b/packages/processors/src/processors/registry/handlers/roleRevoked.handler.ts index c2d5d7b3..f1e90184 100644 --- a/packages/processors/src/processors/registry/handlers/roleRevoked.handler.ts +++ b/packages/processors/src/processors/registry/handlers/roleRevoked.handler.ts @@ -21,22 +21,60 @@ export class RoleRevokedHandler implements IEventHandler<"Registry", "RoleRevoke readonly event: ProcessorEvent<"Registry", "RoleRevoked">, readonly chainId: ChainId, private dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing RoleRevokedHandler", { + className: "RoleRevokedHandler", + chainId: this.chainId, + role: this.event.params.role, + blockNumber: this.event.blockNumber, + }); + } /* @inheritdoc */ async handle(): Promise { - const { projectRepository } = this.dependencies; + const { projectRepository, logger } = this.dependencies; const account = getAddress(this.event.params.account); const role = this.event.params.role.toLowerCase(); + + logger?.debug("Starting role revocation handling", { + className: "RoleRevokedHandler", + methodName: "handle", + role, + account, + blockNumber: this.event.blockNumber, + }); + + logger?.debug("Fetching project for role", { + className: "RoleRevokedHandler", + methodName: "handle", + role, + chainId: this.chainId, + }); + const project = await projectRepository.getProjectById(this.chainId, role); // The role value for a member is the profileId in Allo V1 // which is the project id in this database. // If we don't find a project with that id we can't remove the role. if (!project) { + logger?.error("Project not found for role", { + className: "RoleRevokedHandler", + methodName: "handle", + role, + chainId: this.chainId, + account, + }); throw new ProjectByRoleNotFound(this.chainId, role); } - return [ + logger?.debug("Revoking member role", { + className: "RoleRevokedHandler", + methodName: "handle", + projectId: project.id, + account, + role, + }); + + const changes: Changeset[] = [ { type: "DeleteAllProjectRolesByRoleAndAddress", args: { @@ -49,5 +87,16 @@ export class RoleRevokedHandler implements IEventHandler<"Registry", "RoleRevoke }, }, ]; + + logger?.info("Role revocation completed", { + className: "RoleRevokedHandler", + methodName: "handle", + projectId: project.id, + account, + role, + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/registry/registry.processor.ts b/packages/processors/src/processors/registry/registry.processor.ts index 07ba60c2..d9e44dde 100644 --- a/packages/processors/src/processors/registry/registry.processor.ts +++ b/packages/processors/src/processors/registry/registry.processor.ts @@ -17,50 +17,134 @@ export class RegistryProcessor implements IProcessor<"Registry", RegistryEvent> constructor( private readonly chainId: ChainId, private readonly dependencies: ProcessorDependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing RegistryProcessor", { + className: "RegistryProcessor", + chainId: this.chainId, + }); + } async process(event: ProcessorEvent<"Registry", RegistryEvent>): Promise { - //TODO: Implement robust error handling and retry logic - switch (event.eventName) { - case "ProfileCreated": - return new ProfileCreatedHandler( - event as ProcessorEvent<"Registry", "ProfileCreated">, - this.chainId, - this.dependencies, - ).handle(); - case "ProfileMetadataUpdated": - return new ProfileMetadataUpdatedHandler( - event as ProcessorEvent<"Registry", "ProfileMetadataUpdated">, - this.chainId, - this.dependencies, - ).handle(); - case "ProfileNameUpdated": - return new ProfileNameUpdatedHandler( - event as ProcessorEvent<"Registry", "ProfileNameUpdated">, - this.chainId, - this.dependencies, - ).handle(); - case "ProfileOwnerUpdated": - return new ProfileOwnerUpdatedHandler( - event as ProcessorEvent<"Registry", "ProfileOwnerUpdated">, - this.chainId, - this.dependencies, - ).handle(); - case "RoleGranted": - return new RoleGrantedHandler( - event as ProcessorEvent<"Registry", "RoleGranted">, - this.chainId, - this.dependencies, - ).handle(); - case "RoleRevoked": - return new RoleRevokedHandler( - event as ProcessorEvent<"Registry", "RoleRevoked">, - this.chainId, - this.dependencies, - ).handle(); + const { logger } = this.dependencies; + + logger?.debug("Starting event processing", { + className: "RegistryProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + blockNumber: event.blockNumber, + }); + + try { + let result: Changeset[]; + + switch (event.eventName) { + case "ProfileCreated": + logger?.debug("Delegating to ProfileCreatedHandler", { + className: "RegistryProcessor", + methodName: "process", + params: event.params, + }); + result = await new ProfileCreatedHandler( + event as ProcessorEvent<"Registry", "ProfileCreated">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "ProfileMetadataUpdated": + logger?.debug("Delegating to ProfileMetadataUpdatedHandler", { + className: "RegistryProcessor", + methodName: "process", + params: event.params, + }); + result = await new ProfileMetadataUpdatedHandler( + event as ProcessorEvent<"Registry", "ProfileMetadataUpdated">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "ProfileNameUpdated": + logger?.debug("Delegating to ProfileNameUpdatedHandler", { + className: "RegistryProcessor", + methodName: "process", + params: event.params, + }); + result = await new ProfileNameUpdatedHandler( + event as ProcessorEvent<"Registry", "ProfileNameUpdated">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "ProfileOwnerUpdated": + logger?.debug("Delegating to ProfileOwnerUpdatedHandler", { + className: "RegistryProcessor", + methodName: "process", + params: event.params, + }); + result = await new ProfileOwnerUpdatedHandler( + event as ProcessorEvent<"Registry", "ProfileOwnerUpdated">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "RoleGranted": + logger?.debug("Delegating to RoleGrantedHandler", { + className: "RegistryProcessor", + methodName: "process", + params: event.params, + }); + result = await new RoleGrantedHandler( + event as ProcessorEvent<"Registry", "RoleGranted">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "RoleRevoked": + logger?.debug("Delegating to RoleRevokedHandler", { + className: "RegistryProcessor", + methodName: "process", + params: event.params, + }); + result = await new RoleRevokedHandler( + event as ProcessorEvent<"Registry", "RoleRevoked">, + this.chainId, + this.dependencies, + ).handle(); + break; + + default: + logger?.error("Unsupported event encountered", { + className: "RegistryProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + }); + throw new UnsupportedEventException("Registry", event.eventName); + } + + logger?.info("Event processing completed", { + className: "RegistryProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + changesetCount: result.length, + }); - default: - throw new UnsupportedEventException("Registry", event.eventName); + return result; + } catch (error) { + logger?.error("Error processing event", { + className: "RegistryProcessor", + methodName: "process", + eventName: event.eventName, + chainId: this.chainId, + error: error instanceof Error ? error.message : String(error), + }); + throw error; } } } diff --git a/packages/processors/src/processors/strategy/strategy.processor.ts b/packages/processors/src/processors/strategy/strategy.processor.ts index 035e91b5..2981d0aa 100644 --- a/packages/processors/src/processors/strategy/strategy.processor.ts +++ b/packages/processors/src/processors/strategy/strategy.processor.ts @@ -9,21 +9,79 @@ export class StrategyProcessor implements IProcessor<"Strategy", StrategyEvent> constructor( private readonly chainId: ChainId, private readonly dependencies: ProcessorDependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing StrategyProcessor", { + className: "StrategyProcessor", + chainId: this.chainId, + }); + } async process(event: ProcessorEvent<"Strategy", StrategyEvent>): Promise { + const { logger } = this.dependencies; const strategyId = event.strategyId; - const strategyHandler = StrategyHandlerFactory.createHandler( - this.chainId, - this.dependencies, + logger?.debug("Starting strategy event processing", { + className: "StrategyProcessor", + methodName: "process", + eventName: event.eventName, strategyId, - ); + chainId: this.chainId, + blockNumber: event.blockNumber, + }); - if (!strategyHandler) { - throw new UnsupportedStrategy(strategyId); - } + try { + logger?.debug("Creating strategy handler", { + className: "StrategyProcessor", + methodName: "process", + strategyId, + chainId: this.chainId, + }); + + const strategyHandler = StrategyHandlerFactory.createHandler( + this.chainId, + this.dependencies, + strategyId, + ); + + if (!strategyHandler) { + logger?.error("Unsupported strategy encountered", { + className: "StrategyProcessor", + methodName: "process", + strategyId, + chainId: this.chainId, + }); + throw new UnsupportedStrategy(strategyId); + } - return strategyHandler.handle(event); + logger?.debug("Delegating to strategy handler", { + className: "StrategyProcessor", + methodName: "process", + strategyId, + handlerType: strategyHandler.constructor.name, + }); + + const result = await strategyHandler.handle(event); + + logger?.info("Strategy event processing completed", { + className: "StrategyProcessor", + methodName: "process", + strategyId, + eventName: event.eventName, + chainId: this.chainId, + changesetCount: result.length, + }); + + return result; + } catch (error) { + logger?.error("Error processing strategy event", { + className: "StrategyProcessor", + methodName: "process", + strategyId, + eventName: event.eventName, + chainId: this.chainId, + error: error instanceof Error ? error.message : String(error), + }); + throw error; + } } } diff --git a/packages/shared/src/retry/retry.ts b/packages/shared/src/retry/retry.ts index e627c068..5248033d 100644 --- a/packages/shared/src/retry/retry.ts +++ b/packages/shared/src/retry/retry.ts @@ -14,7 +14,12 @@ export class RetryHandler { constructor( private readonly strategy: RetryStrategy = new ExponentialBackoff(), private readonly logger: ILogger, - ) {} + ) { + this.logger.debug("Initializing RetryHandler", { + className: "RetryHandler", + strategyType: strategy.constructor.name, + }); + } /** * Executes an operation with retry logic @@ -29,37 +34,102 @@ export class RetryHandler { operation: () => Promise, params: { abortSignal?: AbortSignal } = {}, ): Promise { + this.logger.debug("Starting operation execution", { + className: "RetryHandler", + methodName: "execute", + hasAbortSignal: !!params.abortSignal, + }); + let result: T | undefined; let attemptCount = 0; + const startTime = Date.now(); + while (true && !params.abortSignal?.aborted) { + attemptCount++; + + this.logger.debug("Attempting operation", { + className: "RetryHandler", + methodName: "execute", + attemptCount, + elapsedMs: Date.now() - startTime, + }); + try { result = await operation(); + const duration = Date.now() - startTime; + + this.logger.info("Operation completed successfully", { + className: "RetryHandler", + methodName: "execute", + attemptCount, + durationMs: duration, + hasResult: result !== undefined, + }); + break; } catch (error) { - if (!(error instanceof RetriableError)) { + const isRetriable = error instanceof RetriableError; + + this.logger.warn("Operation attempt failed", { + className: "RetryHandler", + methodName: "execute", + attemptCount, + isRetriable, + error: error instanceof Error ? error.message : String(error), + errorType: error instanceof Error ? error.constructor.name : typeof error, + metadata: error instanceof RetriableError ? error.metadata : undefined, + }); + + if (!isRetriable) { + this.logger.error("Non-retriable error encountered", { + className: "RetryHandler", + methodName: "execute", + attemptCount, + error: error instanceof Error ? error.message : String(error), + elapsedMs: Date.now() - startTime, + }); throw error; } - attemptCount++; if (!this.strategy.shouldRetry(attemptCount)) { - throw error; - } else { - const delay = this.strategy.getDelay( + this.logger.error("Max retries exceeded", { + className: "RetryHandler", + methodName: "execute", attemptCount, - error.metadata?.retryAfterInMs, - ); - - this.logger.debug(`Retrying in ${delay}ms`, { - className: RetryHandler.name, - delay, + elapsedMs: Date.now() - startTime, + error: error instanceof Error ? error.message : String(error), }); - - await new Promise((resolve) => setTimeout(resolve, delay, params.abortSignal)); + throw error; } + + const delay = this.strategy.getDelay( + attemptCount, + error instanceof RetriableError ? error.metadata?.retryAfterInMs : undefined, + ); + + this.logger.debug("Scheduling retry", { + className: "RetryHandler", + methodName: "execute", + attemptCount, + nextDelayMs: delay, + elapsedMs: Date.now() - startTime, + retryAfterMs: + error instanceof RetriableError + ? error.metadata?.retryAfterInMs + : undefined, + }); + + await new Promise((resolve) => setTimeout(resolve, delay, params.abortSignal)); } } if (params.abortSignal?.aborted) { + this.logger.warn("Operation aborted", { + className: "RetryHandler", + methodName: "execute", + attemptCount, + elapsedMs: Date.now() - startTime, + }); throw new Error("Operation aborted"); } From 4f3d7928090c86860006991d959213c84188fa7a Mon Sep 17 00:00:00 2001 From: 0xKurt Date: Tue, 1 Apr 2025 18:58:15 +0200 Subject: [PATCH 2/3] add more logs --- .../src/services/processing.service.ts | 34 +- packages/data-flow/src/orchestrator.ts | 70 ++++ .../allo/handlers/poolCreated.handler.ts | 4 +- .../common/baseDistributionUpdated.handler.ts | 73 ++++- .../common/baseFundsDistributed.handler.ts | 70 +++- .../baseRecipientStatusUpdated.handler.ts | 100 +++++- .../handlers/directAllocated.handler.ts | 73 ++++- .../directGrantsLite.handler.ts | 216 +++++++++---- .../handlers/allocated.handler.ts | 90 +++++- .../handlers/registered.handler.ts | 71 +++- .../handlers/timestampsUpdated.handler.ts | 61 +++- .../handlers/updatedRegistration.handler.ts | 98 +++++- .../directGrantsSimple.handler.ts | 104 ++++-- .../handlers/registered.handler.ts | 70 +++- .../handlers/timestampsUpdated.handler.ts | 61 +++- .../dvmdDirectTransfer.handler.ts | 306 +++++++++++++----- .../handlers/allocated.handler.ts | 117 ++++++- .../handlers/registered.handler.ts | 71 +++- .../handlers/timestampsUpdated.handler.ts | 65 +++- .../handlers/updatedRegistration.handler.ts | 98 +++++- .../easyRetroFunding.handler.ts | 268 +++++++++++---- .../processors/strategy/strategy.processor.ts | 14 +- setup.sh | 52 +++ 23 files changed, 1855 insertions(+), 331 deletions(-) create mode 100755 setup.sh diff --git a/apps/processing/src/services/processing.service.ts b/apps/processing/src/services/processing.service.ts index de283937..b38dfd15 100644 --- a/apps/processing/src/services/processing.service.ts +++ b/apps/processing/src/services/processing.service.ts @@ -45,13 +45,15 @@ export class ProcessingService { static async initialize(env: Environment): Promise { const sharedDependencies = await SharedDependenciesService.initialize(env); + const { logger } = sharedDependencies; + logger.debug("Shared dependencies initialized"); + const { CHAINS: chains } = env; const { core, registriesRepositories, indexerClient, kyselyDatabase, - logger, retryStrategy, notifier, } = sharedDependencies; @@ -60,29 +62,37 @@ export class ProcessingService { strategyRegistryRepository, strategyProcessingCheckpointRepository, } = registriesRepositories; + const orchestrators: Map = new Map(); + logger.debug("Starting chain initialization", { chainCount: chains.length }); const strategyRegistry = new DatabaseStrategyRegistry(logger, strategyRegistryRepository); const eventsRegistry = new DatabaseEventRegistry(logger, eventRegistryRepository); + logger.debug("Created base registries"); const viemChainsArray = Object.values(viemChains) as Chain[]; for (const chain of chains) { - // Initialize EVM provider + logger.debug("Processing chain configuration", { chainId: chain.id }); + const viemChain = extractChain({ chains: viemChainsArray, id: chain.id, }); if (!viemChain) { + logger.error("Invalid chain configuration", { chainId: chain.id }); throw new InvalidChainId(chain.id); } + const evmProvider = new EvmProvider(chain.rpcUrls, viemChain, logger); + logger.debug("EVM provider created", { chainId: chain.id }); const cachedStrategyRegistry = await InMemoryCachedStrategyRegistry.initialize( logger, strategyRegistry, chain.id as ChainId, ); + logger.debug("Cached strategy registry initialized", { chainId: chain.id }); const orchestrator = new Orchestrator( chain.id as ChainId, @@ -98,6 +108,8 @@ export class ProcessingService { logger, notifier, ); + logger.debug("Orchestrator created", { chainId: chain.id }); + const retroactiveProcessor = new RetroactiveProcessor( chain.id as ChainId, { ...core, evmProvider }, @@ -111,10 +123,13 @@ export class ProcessingService { retryStrategy, logger, ); + logger.debug("Retroactive processor created", { chainId: chain.id }); orchestrators.set(chain.id as ChainId, [orchestrator, retroactiveProcessor]); + logger.debug("Chain setup completed", { chainId: chain.id }); } + logger.debug("All chains initialized", { chainCount: orchestrators.size }); return new ProcessingService(orchestrators, kyselyDatabase, logger); } @@ -127,10 +142,10 @@ export class ProcessingService { this.logger.info("Starting processor service..."); const abortController = new AbortController(); + this.logger.debug("Created abort controller"); const orchestratorProcesses: Promise[] = []; - // Handle graceful shutdown process.on("SIGINT", () => { this.logger.info("Received SIGINT signal. Shutting down..."); abortController.abort(); @@ -140,14 +155,20 @@ export class ProcessingService { this.logger.info("Received SIGTERM signal. Shutting down..."); abortController.abort(); }); + this.logger.debug("Signal handlers registered"); try { for (const [orchestrator, _] of this.orchestrators.values()) { this.logger.info(`Starting orchestrator for chain ${orchestrator.chainId}...`); orchestratorProcesses.push(orchestrator.run(abortController.signal)); + this.logger.debug("Orchestrator process queued", { chainId: orchestrator.chainId }); } + this.logger.debug("Waiting for all orchestrator processes", { + processCount: orchestratorProcesses.length, + }); await Promise.allSettled(orchestratorProcesses); + this.logger.debug("All orchestrator processes completed"); } catch (error) { this.logger.error(`Processor service failed: ${error}`); throw error; @@ -161,7 +182,13 @@ export class ProcessingService { async processRetroactiveEvents(): Promise { this.logger.info("Processing retroactive events..."); for (const [_, retroactiveProcessor] of this.orchestrators.values()) { + this.logger.debug("Starting retroactive processing", { + chainId: retroactiveProcessor.chainId, + }); await retroactiveProcessor.processRetroactiveStrategies(); + this.logger.debug("Completed retroactive processing", { + chainId: retroactiveProcessor.chainId, + }); } } @@ -173,6 +200,7 @@ export class ProcessingService { try { this.logger.info("Releasing resources..."); await this.kyselyDatabase.destroy(); + this.logger.debug("Database resources released"); } catch (error) { this.logger.error(`Error releasing resources: ${error}`); } diff --git a/packages/data-flow/src/orchestrator.ts b/packages/data-flow/src/orchestrator.ts index b6ad123c..e1063ad9 100644 --- a/packages/data-flow/src/orchestrator.ts +++ b/packages/data-flow/src/orchestrator.ts @@ -278,6 +278,15 @@ export class Orchestrator { ); } else { if (error instanceof RetriableError) { + this.logger.debug("Processing RetriableError", { + className: Orchestrator.name, + chainId: this.chainId, + errorType: "RetriableError", + eventIdentifier: event + ? `${event.blockNumber}:${event.logIndex}` + : undefined, + }); + error.message = `Error processing event after retries. ${error.message}`; this.logger.error(error, { event, @@ -286,12 +295,30 @@ export class Orchestrator { lastRetryDelay: error.metadata?.retryAfterInMs, reason: error.metadata?.failureReason, }); + + this.logger.debug("Sending notification for RetriableError", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: event + ? `${event.blockNumber}:${event.logIndex}` + : undefined, + }); + void this.notifier.send(error.message, { chainId: this.chainId, event: event!, stack: error.getFullStack(), }); } else if (error instanceof Error || isNativeError(error)) { + this.logger.debug("Processing standard Error", { + className: Orchestrator.name, + chainId: this.chainId, + errorType: error.constructor.name, + eventIdentifier: event + ? `${event.blockNumber}:${event.logIndex}` + : undefined, + }); + const shouldIgnoreError = this.shouldIgnoreTimestampsUpdatedError( error, event!, @@ -303,15 +330,30 @@ export class Orchestrator { shouldIgnoreError, errorType: error.constructor.name, eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + errorMessage: error.message, }); if (!shouldIgnoreError) { + this.logger.debug("Error not ignored, preparing to send notification", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + errorType: error.constructor.name, + }); + this.logger.error(error, { event, className: Orchestrator.name, chainId: this.chainId, errorStack: error.stack, }); + + this.logger.debug("Sending notification for standard error", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + }); + void this.notifier.send(error.message, { chainId: this.chainId, event: event!, @@ -319,12 +361,40 @@ export class Orchestrator { }); } } else { + this.logger.debug("Processing unknown error type", { + className: Orchestrator.name, + chainId: this.chainId, + errorType: typeof error, + eventIdentifier: event + ? `${event.blockNumber}:${event.logIndex}` + : undefined, + }); + const errorMessage = `Error processing event: ${stringify(event)} ${error}`; + + this.logger.debug("Created error message for unknown error", { + className: Orchestrator.name, + chainId: this.chainId, + errorMessage, + eventIdentifier: event + ? `${event.blockNumber}:${event.logIndex}` + : undefined, + }); + this.logger.error(new Error(errorMessage), { className: Orchestrator.name, chainId: this.chainId, unknownErrorType: typeof error, }); + + this.logger.debug("Sending notification for unknown error", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: event + ? `${event.blockNumber}:${event.logIndex}` + : undefined, + }); + void this.notifier.send(errorMessage, { chainId: this.chainId, event: event!, diff --git a/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts b/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts index 1b3e5c42..6af52056 100644 --- a/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts +++ b/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts @@ -6,7 +6,7 @@ import { getToken, isAlloNativeToken } from "@grants-stack-indexer/shared"; import type { IEventHandler, ProcessorDependencies, StrategyTimings } from "../../../internal.js"; import { calculateAmountInUsd, getRoundRoles } from "../../../helpers/index.js"; -import { StrategyHandlerFactory, TokenPriceNotFoundError } from "../../../internal.js"; +import { getHandler, StrategyHandlerFactory, TokenPriceNotFoundError } from "../../../internal.js"; import { RoundMetadataSchema } from "../../../schemas/index.js"; type Dependencies = Pick< @@ -100,7 +100,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> logger?.debug("Creating strategy handler", { className: "PoolCreatedHandler", methodName: "handle", - strategyId, + strategyId: getHandler(strategyId), poolId: poolId.toString(), }); diff --git a/packages/processors/src/processors/strategy/common/baseDistributionUpdated.handler.ts b/packages/processors/src/processors/strategy/common/baseDistributionUpdated.handler.ts index c66e61b1..beb91b47 100644 --- a/packages/processors/src/processors/strategy/common/baseDistributionUpdated.handler.ts +++ b/packages/processors/src/processors/strategy/common/baseDistributionUpdated.handler.ts @@ -36,35 +36,87 @@ export class BaseDistributionUpdatedHandler >, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing BaseDistributionUpdatedHandler", { + className: "BaseDistributionUpdatedHandler", + chainId: this.chainId, + eventName: this.event.eventName, + blockNumber: this.event.blockNumber, + strategyAddress: this.event.srcAddress, + }); + } /* @inheritdoc */ async handle(): Promise { const { logger, metadataProvider } = this.dependencies; const [_, pointer] = this.event.params.metadata; - const strategyAddress = getAddress(this.event.srcAddress); + + logger?.debug("Starting distribution update handling", { + className: "BaseDistributionUpdatedHandler", + methodName: "handle", + strategyAddress, + metadataPointer: pointer, + chainId: this.chainId, + }); + + logger?.debug("Fetching distribution metadata", { + className: "BaseDistributionUpdatedHandler", + methodName: "handle", + strategyAddress, + metadataPointer: pointer, + }); + const rawDistribution = await metadataProvider.getMetadata< MatchingDistribution | undefined >(pointer); if (!rawDistribution) { - logger.warn(`No matching distribution found for pointer: ${pointer}`); + logger?.warn("Distribution metadata not found", { + className: "BaseDistributionUpdatedHandler", + methodName: "handle", + strategyAddress, + metadataPointer: pointer, + chainId: this.chainId, + }); throw new MetadataNotFound(`No matching distribution found for pointer: ${pointer}`); } + logger?.debug("Parsing distribution metadata", { + className: "BaseDistributionUpdatedHandler", + methodName: "handle", + strategyAddress, + metadataPointer: pointer, + hasRawDistribution: true, + }); + const distribution = MatchingDistributionSchema.safeParse(rawDistribution); if (!distribution.success) { - logger.warn(`Failed to parse matching distribution: ${distribution.error.message}`); + logger?.warn("Distribution metadata parsing failed", { + className: "BaseDistributionUpdatedHandler", + methodName: "handle", + strategyAddress, + metadataPointer: pointer, + errorMessage: distribution.error.message, + errors: distribution.error.errors, + }); throw new MetadataParsingFailed( `Failed to parse matching distribution: ${distribution.error.message}`, ); } - return [ + logger?.debug("Creating update changeset", { + className: "BaseDistributionUpdatedHandler", + methodName: "handle", + strategyAddress, + transactionHash: this.event.transactionFields.hash, + distributionSize: distribution.data.matchingDistribution.length, + }); + + const changes: Changeset[] = [ { type: "UpdateRoundByStrategyAddress", args: { @@ -77,5 +129,16 @@ export class BaseDistributionUpdatedHandler }, }, ]; + + logger?.info("Distribution update completed", { + className: "BaseDistributionUpdatedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + changeCount: changes.length, + distributionSize: distribution.data.matchingDistribution.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/strategy/common/baseFundsDistributed.handler.ts b/packages/processors/src/processors/strategy/common/baseFundsDistributed.handler.ts index ce5893ae..b471a457 100644 --- a/packages/processors/src/processors/strategy/common/baseFundsDistributed.handler.ts +++ b/packages/processors/src/processors/strategy/common/baseFundsDistributed.handler.ts @@ -29,7 +29,14 @@ export class BaseFundsDistributedHandler implements IEventHandler<"Strategy", "F readonly event: ProcessorEvent<"Strategy", "FundsDistributed">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing BaseFundsDistributedHandler", { + className: "BaseFundsDistributedHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + }); + } /** * Handles the FundsDistributed event. @@ -40,23 +47,65 @@ export class BaseFundsDistributedHandler implements IEventHandler<"Strategy", "F * 2. IncrementRoundTotalDistributed: Increments the total distributed amount for a round. */ async handle(): Promise { - const { roundRepository, applicationRepository } = this.dependencies; - + const { roundRepository, applicationRepository, logger } = this.dependencies; const strategyAddress = getAddress(this.event.srcAddress); + const amount = BigInt(this.event.params.amount); + + logger?.debug("Starting funds distribution handling", { + className: "BaseFundsDistributedHandler", + methodName: "handle", + strategyAddress, + amount: amount.toString(), + recipientId: this.event.params.recipientId, + chainId: this.chainId, + }); + + logger?.debug("Fetching round by strategy address", { + className: "BaseFundsDistributedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); + logger?.debug("Round found", { + className: "BaseFundsDistributedHandler", + methodName: "handle", + roundId: round.id, + strategyAddress, + }); + const roundId = round.id; const anchorAddress = getAddress(this.event.params.recipientId); + + logger?.debug("Fetching application by anchor address", { + className: "BaseFundsDistributedHandler", + methodName: "handle", + roundId, + anchorAddress, + chainId: this.chainId, + }); + const application = await applicationRepository.getApplicationByAnchorAddressOrThrow( this.chainId, roundId, anchorAddress, ); - return [ + logger?.debug("Creating distribution changesets", { + className: "BaseFundsDistributedHandler", + methodName: "handle", + roundId, + applicationId: application.id, + amount: amount.toString(), + transactionHash: this.event.transactionFields.hash, + }); + + const changes: Changeset[] = [ { type: "UpdateApplication", args: { @@ -73,9 +122,20 @@ export class BaseFundsDistributedHandler implements IEventHandler<"Strategy", "F args: { chainId: this.chainId, roundId: round.id, - amount: BigInt(this.event.params.amount), + amount, }, }, ]; + + logger?.info("Funds distribution completed", { + className: "BaseFundsDistributedHandler", + methodName: "handle", + roundId, + applicationId: application.id, + amount: amount.toString(), + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/strategy/common/baseRecipientStatusUpdated.handler.ts b/packages/processors/src/processors/strategy/common/baseRecipientStatusUpdated.handler.ts index 0f8482c8..af3870a1 100644 --- a/packages/processors/src/processors/strategy/common/baseRecipientStatusUpdated.handler.ts +++ b/packages/processors/src/processors/strategy/common/baseRecipientStatusUpdated.handler.ts @@ -40,6 +40,14 @@ export class BaseRecipientStatusUpdatedHandler private readonly dependencies: Dependencies, ) { this.bitmap = new StatusesBitmap(256n, 4n); + this.dependencies.logger?.debug("Initializing BaseRecipientStatusUpdatedHandler", { + className: "BaseRecipientStatusUpdatedHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + rowIndex: this.event.params.rowIndex, + itemsPerRow: this.bitmap.itemsPerRow, + }); } /** @@ -47,20 +55,57 @@ export class BaseRecipientStatusUpdatedHandler * @returns An array of changesets to update application statuses. */ async handle(): Promise { - const { roundRepository } = this.dependencies; - + const { roundRepository, logger } = this.dependencies; const strategyAddress = getAddress(this.event.srcAddress); + + logger?.debug("Starting recipient status update handling", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "handle", + strategyAddress, + rowIndex: this.event.params.rowIndex, + fullRow: this.event.params.fullRow, + }); + + logger?.debug("Fetching round by strategy address", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); + logger?.debug("Getting applications to update", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "handle", + roundId: round.id, + rowIndex: this.event.params.rowIndex, + }); + const applicationsToUpdate = await this.getApplicationsToUpdate(round.id); - return applicationsToUpdate.map(({ application, status }) => { + logger?.debug("Creating status update changesets", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "handle", + roundId: round.id, + applicationCount: applicationsToUpdate.length, + }); + + const changes = applicationsToUpdate.map(({ application, status }) => { const statusString = ApplicationStatus[status] as Application["status"]; + logger?.debug("Creating status update for application", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "handle", + applicationId: application.id, + currentStatus: application.status, + newStatus: statusString, + }); + return { - type: "UpdateApplication", + type: "UpdateApplication" as const, args: { chainId: this.chainId, roundId: round.id, @@ -74,6 +119,15 @@ export class BaseRecipientStatusUpdatedHandler }, }; }); + + logger?.info("Recipient status updates completed", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "handle", + roundId: round.id, + updatedCount: changes.length, + }); + + return changes; } /** @@ -82,14 +136,39 @@ export class BaseRecipientStatusUpdatedHandler * @returns An array of application updates. */ private async getApplicationsToUpdate(roundId: string): Promise { + const { logger } = this.dependencies; const { rowIndex, fullRow } = this.event.params; + + logger?.debug("Processing bitmap row", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "getApplicationsToUpdate", + rowIndex, + fullRow, + }); + this.bitmap.setRow(BigInt(rowIndex), BigInt(fullRow)); const startIndex = BigInt(rowIndex) * BigInt(this.bitmap.itemsPerRow); const applications: { application: Application; status: number }[] = []; + logger?.debug("Starting application status scan", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "getApplicationsToUpdate", + startIndex: startIndex.toString(), + itemsPerRow: this.bitmap.itemsPerRow, + }); + for (let i = startIndex; i < startIndex + this.bitmap.itemsPerRow; i++) { const status = this.bitmap.getStatus(i); + + logger?.debug("Processing application index", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "getApplicationsToUpdate", + index: i.toString(), + status, + isValidStatus: isValidApplicationStatus(status), + }); + if (isValidApplicationStatus(status)) { const application = await this.dependencies.applicationRepository.getApplicationById( @@ -99,14 +178,23 @@ export class BaseRecipientStatusUpdatedHandler ); if (application) { - applications.push({ - application, + logger?.debug("Found valid application for update", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "getApplicationsToUpdate", + applicationId: application.id, status, }); + applications.push({ application, status }); } } } + logger?.debug("Application status scan completed", { + className: "BaseRecipientStatusUpdatedHandler", + methodName: "getApplicationsToUpdate", + validApplicationsFound: applications.length, + }); + return applications; } } diff --git a/packages/processors/src/processors/strategy/directAllocation/handlers/directAllocated.handler.ts b/packages/processors/src/processors/strategy/directAllocation/handlers/directAllocated.handler.ts index 9a064720..f867a115 100644 --- a/packages/processors/src/processors/strategy/directAllocation/handlers/directAllocated.handler.ts +++ b/packages/processors/src/processors/strategy/directAllocation/handlers/directAllocated.handler.ts @@ -28,7 +28,15 @@ export class DirectAllocatedHandler implements IEventHandler<"Strategy", "Direct readonly event: ProcessorEvent<"Strategy", "DirectAllocated">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DirectAllocatedHandler", { + className: "DirectAllocatedHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** * Handles the DirectAllocated event for the Direct Allocation strategy. @@ -39,24 +47,56 @@ export class DirectAllocatedHandler implements IEventHandler<"Strategy", "Direct * @throws {TokenPriceNotFoundError} if the token price is not found */ async handle(): Promise { - const { projectRepository, roundRepository, pricingProvider } = this.dependencies; + const { projectRepository, roundRepository, pricingProvider, logger } = this.dependencies; const strategyAddress = getAddress(this.event.srcAddress); + logger?.debug("Starting direct allocation handling", { + className: "DirectAllocatedHandler", + methodName: "handle", + strategyAddress, + profileId: this.event.params.profileId, + amount: this.event.params.amount, + token: this.event.params.token, + }); + + logger?.debug("Fetching round by strategy address", { + className: "DirectAllocatedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); + + logger?.debug("Fetching project details", { + className: "DirectAllocatedHandler", + methodName: "handle", + chainId: this.chainId, + profileId: this.event.params.profileId, + }); + const project = await projectRepository.getProjectByIdOrThrow( this.chainId, this.event.params.profileId, ); const donationId = getDonationId(this.event.blockNumber, this.event.logIndex); - const amount = BigInt(this.event.params.amount); const token = getTokenOrThrow(this.chainId, this.event.params.token); const sender = getAddress(this.event.params.sender); + logger?.debug("Calculating USD amount for donation", { + className: "DirectAllocatedHandler", + methodName: "handle", + donationId, + amount: amount.toString(), + token: token, + timestamp: this.event.blockTimestamp, + }); + const { amountInUsd } = await getTokenAmountInUsd( pricingProvider, token, @@ -64,6 +104,15 @@ export class DirectAllocatedHandler implements IEventHandler<"Strategy", "Direct this.event.blockTimestamp, ); + logger?.debug("Creating donation record", { + className: "DirectAllocatedHandler", + methodName: "handle", + donationId, + roundId: round.id, + projectId: project.id, + amountInUsd, + }); + const donation: Donation = { id: donationId, chainId: this.chainId, @@ -81,13 +130,13 @@ export class DirectAllocatedHandler implements IEventHandler<"Strategy", "Direct timestamp: new Date(this.event.blockTimestamp), }; - return [ + const changes = [ { - type: "InsertDonation", + type: "InsertDonation" as const, args: { donation }, }, { - type: "IncrementRoundDonationStats", + type: "IncrementRoundDonationStats" as const, args: { chainId: this.chainId, roundId: round.id, @@ -95,5 +144,17 @@ export class DirectAllocatedHandler implements IEventHandler<"Strategy", "Direct }, }, ]; + + logger?.info("Direct allocation processing completed", { + className: "DirectAllocatedHandler", + methodName: "handle", + donationId, + roundId: round.id, + projectId: project.id, + amountInUsd, + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/strategy/directGrantsLite/directGrantsLite.handler.ts b/packages/processors/src/processors/strategy/directGrantsLite/directGrantsLite.handler.ts index 1ed788bb..78242aad 100644 --- a/packages/processors/src/processors/strategy/directGrantsLite/directGrantsLite.handler.ts +++ b/packages/processors/src/processors/strategy/directGrantsLite/directGrantsLite.handler.ts @@ -9,7 +9,7 @@ import { StrategyTimings, UnsupportedEventException, } from "../../../internal.js"; -import { BaseStrategyHandler } from "../index.js"; +import { BaseStrategyHandler, getHandler } from "../index.js"; import { DGLiteAllocatedHandler, DGLiteRegisteredHandler, @@ -36,51 +36,129 @@ export class DirectGrantsLiteStrategyHandler extends BaseStrategyHandler { private readonly dependencies: ProcessorDependencies, ) { super(STRATEGY_NAME); + this.dependencies.logger?.debug("Initializing DirectGrantsLiteStrategyHandler", { + className: "DirectGrantsLiteStrategyHandler", + chainId: this.chainId, + strategyName: STRATEGY_NAME, + }); } /** @inheritdoc */ async handle(event: ProcessorEvent<"Strategy", StrategyEvent>): Promise { - switch (event.eventName) { - case "RecipientStatusUpdatedWithFullRow": - return new BaseRecipientStatusUpdatedHandler( - event as ProcessorEvent<"Strategy", "RecipientStatusUpdatedWithFullRow">, - this.chainId, - this.dependencies, - ).handle(); - case "RegisteredWithSender": - return new DGLiteRegisteredHandler( - event as ProcessorEvent<"Strategy", "RegisteredWithSender">, - this.chainId, - this.dependencies, - ).handle(); - case "UpdatedRegistrationWithStatus": - return new DGLiteUpdatedRegistrationHandler( - event as ProcessorEvent<"Strategy", "UpdatedRegistrationWithStatus">, - this.chainId, - this.dependencies, - ).handle(); - case "TimestampsUpdated": - return new DGLiteTimestampsUpdatedHandler( - event as ProcessorEvent<"Strategy", "TimestampsUpdated">, - this.chainId, - this.dependencies, - ).handle(); - case "AllocatedWithToken": - return new DGLiteAllocatedHandler( - event as ProcessorEvent<"Strategy", "AllocatedWithToken">, - this.chainId, - this.dependencies, - ).handle(); - default: - throw new UnsupportedEventException("Strategy", event.eventName, this.name); + const { logger } = this.dependencies; + + logger?.debug("Processing strategy event", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "handle", + eventName: event.eventName, + strategyAddress: event.srcAddress, + blockNumber: event.blockNumber, + }); + + try { + let result: Changeset[]; + switch (event.eventName) { + case "RecipientStatusUpdatedWithFullRow": + logger?.debug("Delegating to BaseRecipientStatusUpdatedHandler", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new BaseRecipientStatusUpdatedHandler( + event as ProcessorEvent<"Strategy", "RecipientStatusUpdatedWithFullRow">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "RegisteredWithSender": + logger?.debug("Delegating to DGLiteRegisteredHandler", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new DGLiteRegisteredHandler( + event as ProcessorEvent<"Strategy", "RegisteredWithSender">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "UpdatedRegistrationWithStatus": + logger?.debug("Delegating to DGLiteUpdatedRegistrationHandler", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new DGLiteUpdatedRegistrationHandler( + event as ProcessorEvent<"Strategy", "UpdatedRegistrationWithStatus">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "TimestampsUpdated": + logger?.debug("Delegating to DGLiteTimestampsUpdatedHandler", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new DGLiteTimestampsUpdatedHandler( + event as ProcessorEvent<"Strategy", "TimestampsUpdated">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "AllocatedWithToken": + logger?.debug("Delegating to DGLiteAllocatedHandler", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new DGLiteAllocatedHandler( + event as ProcessorEvent<"Strategy", "AllocatedWithToken">, + this.chainId, + this.dependencies, + ).handle(); + break; + default: + logger?.warn("Unsupported event received", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "handle", + eventName: event.eventName, + strategyName: this.name, + }); + throw new UnsupportedEventException("Strategy", event.eventName, this.name); + } + + logger?.debug("Event processing completed", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "handle", + eventName: event.eventName, + changeCount: result.length, + }); + + return result; + } catch (error) { + logger?.error("Error processing event", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "handle", + eventName: event.eventName, + error: error instanceof Error ? error.message : String(error), + }); + throw error; } } /** @inheritdoc */ override async fetchStrategyTimings(strategyId: Address): Promise { - const { evmProvider } = this.dependencies; - let results: [bigint, bigint] = [0n, 0n]; + const { evmProvider, logger } = this.dependencies; + + logger?.debug("Fetching strategy timings", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "fetchStrategyTimings", + strategyId: getHandler(strategyId), + chainId: this.chainId, + }); + let results: [bigint, bigint] = [0n, 0n]; const contractCalls = [ { abi: DirectGrantsLiteStrategy, @@ -94,25 +172,55 @@ export class DirectGrantsLiteStrategyHandler extends BaseStrategyHandler { }, ] as const; - // TODO: refactor when evmProvider implements this natively - if (evmProvider.getMulticall3Address()) { - results = await evmProvider.multicall({ - contracts: contractCalls, - allowFailure: false, + try { + if (evmProvider.getMulticall3Address()) { + logger?.debug("Using multicall for fetching timings", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "fetchStrategyTimings", + multicallAddress: evmProvider.getMulticall3Address(), + }); + + results = await evmProvider.multicall({ + contracts: contractCalls, + allowFailure: false, + }); + } else { + logger?.debug("Using individual calls for fetching timings", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "fetchStrategyTimings", + }); + + results = (await Promise.all( + contractCalls.map((call) => + evmProvider.readContract(call.address, call.abi, call.functionName), + ), + )) as [bigint, bigint]; + } + + const timings = { + applicationsStartTime: getDateFromTimestamp(results[0]), + applicationsEndTime: getDateFromTimestamp(results[1]), + donationsStartTime: null, + donationsEndTime: null, + }; + + logger?.debug("Strategy timings fetched", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "fetchStrategyTimings", + strategyId: getHandler(strategyId), + startTime: timings.applicationsStartTime?.toISOString(), + endTime: timings.applicationsEndTime?.toISOString(), }); - } else { - results = (await Promise.all( - contractCalls.map((call) => - evmProvider.readContract(call.address, call.abi, call.functionName), - ), - )) as [bigint, bigint]; - } - return { - applicationsStartTime: getDateFromTimestamp(results[0]), - applicationsEndTime: getDateFromTimestamp(results[1]), - donationsStartTime: null, - donationsEndTime: null, - }; + return timings; + } catch (error) { + logger?.error("Error fetching strategy timings", { + className: "DirectGrantsLiteStrategyHandler", + methodName: "fetchStrategyTimings", + strategyId: getHandler(strategyId), + error: error instanceof Error ? error.message : String(error), + }); + throw error; + } } } diff --git a/packages/processors/src/processors/strategy/directGrantsLite/handlers/allocated.handler.ts b/packages/processors/src/processors/strategy/directGrantsLite/handlers/allocated.handler.ts index 18034301..8c35fee3 100644 --- a/packages/processors/src/processors/strategy/directGrantsLite/handlers/allocated.handler.ts +++ b/packages/processors/src/processors/strategy/directGrantsLite/handlers/allocated.handler.ts @@ -8,7 +8,7 @@ import { IEventHandler, ProcessorDependencies } from "../../../../internal.js"; type Dependencies = Pick< ProcessorDependencies, - "roundRepository" | "applicationRepository" | "pricingProvider" + "roundRepository" | "applicationRepository" | "pricingProvider" | "logger" >; /** @@ -26,7 +26,15 @@ export class DGLiteAllocatedHandler implements IEventHandler<"Strategy", "Alloca readonly event: ProcessorEvent<"Strategy", "AllocatedWithToken">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DGLiteAllocatedHandler", { + className: "DGLiteAllocatedHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** * Handles the AllocatedWithToken event for the Direct Grants Lite strategy. @@ -37,12 +45,29 @@ export class DGLiteAllocatedHandler implements IEventHandler<"Strategy", "Alloca * @throws TokenPriceNotFound if the token price is not found. */ async handle(): Promise { - const { roundRepository, applicationRepository } = this.dependencies; + const { roundRepository, applicationRepository, pricingProvider, logger } = + this.dependencies; const { srcAddress } = this.event; const { recipientId: _recipientId, amount: strAmount, token: _token } = this.event.params; + logger?.debug("Starting allocation handling", { + className: "DGLiteAllocatedHandler", + methodName: "handle", + strategyAddress: srcAddress, + recipientId: _recipientId, + amount: strAmount, + token: _token, + }); + const amount = BigInt(strAmount); + logger?.debug("Fetching round by strategy address", { + className: "DGLiteAllocatedHandler", + methodName: "handle", + strategyAddress: srcAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, getAddress(srcAddress), @@ -50,6 +75,15 @@ export class DGLiteAllocatedHandler implements IEventHandler<"Strategy", "Alloca const recipientId = getAddress(_recipientId); const tokenAddress = getAddress(_token); + + logger?.debug("Fetching application details", { + className: "DGLiteAllocatedHandler", + methodName: "handle", + roundId: round.id, + recipientId, + chainId: this.chainId, + }); + const application = await applicationRepository.getApplicationByAnchorAddressOrThrow( this.chainId, round.id, @@ -59,24 +93,47 @@ export class DGLiteAllocatedHandler implements IEventHandler<"Strategy", "Alloca const token = getTokenOrThrow(this.chainId, tokenAddress); const matchToken = getTokenOrThrow(this.chainId, round.matchTokenAddress); + logger?.debug("Processing token amounts", { + className: "DGLiteAllocatedHandler", + methodName: "handle", + token: token, + amount: amount.toString(), + }); + let amountInUsd = "0"; let amountInRoundMatchToken = 0n; if (amount > 0) { + logger?.debug("Converting amount to USD", { + className: "DGLiteAllocatedHandler", + methodName: "handle", + amount: amount.toString(), + token: token, + timestamp: this.event.blockTimestamp, + }); + const { amountInUsd: amountInUsdString } = await getTokenAmountInUsd( - this.dependencies.pricingProvider, + pricingProvider, token, amount, this.event.blockTimestamp, ); amountInUsd = amountInUsdString; + logger?.debug("Calculating match token amount", { + className: "DGLiteAllocatedHandler", + methodName: "handle", + amountInUsd, + matchToken: matchToken, + usingDirectConversion: matchToken.address === token.address, + }); + amountInRoundMatchToken = matchToken.address === token.address ? amount : ( await getUsdInTokenAmount( - this.dependencies.pricingProvider, + pricingProvider, matchToken, amountInUsd, this.event.blockTimestamp, @@ -84,11 +141,9 @@ export class DGLiteAllocatedHandler implements IEventHandler<"Strategy", "Alloca ).amount; } - const timestamp = this.event.blockTimestamp; - - return [ + const changes = [ { - type: "InsertApplicationPayout", + type: "InsertApplicationPayout" as const, args: { applicationPayout: { amount, @@ -100,12 +155,12 @@ export class DGLiteAllocatedHandler implements IEventHandler<"Strategy", "Alloca amountInUsd, transactionHash: this.event.transactionFields.hash, sender: getAddress(this.event.params.sender), - timestamp: new Date(timestamp), + timestamp: new Date(this.event.blockTimestamp), }, }, }, { - type: "IncrementRoundTotalDistributed", + type: "IncrementRoundTotalDistributed" as const, args: { chainId: this.chainId, roundId: round.id, @@ -113,5 +168,18 @@ export class DGLiteAllocatedHandler implements IEventHandler<"Strategy", "Alloca }, }, ]; + + logger?.info("Allocation processing completed", { + className: "DGLiteAllocatedHandler", + methodName: "handle", + applicationId: application.id, + roundId: round.id, + amount: amount.toString(), + amountInUsd, + amountInRoundMatchToken: amountInRoundMatchToken.toString(), + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/strategy/directGrantsLite/handlers/registered.handler.ts b/packages/processors/src/processors/strategy/directGrantsLite/handlers/registered.handler.ts index 5b2da3cb..03688212 100644 --- a/packages/processors/src/processors/strategy/directGrantsLite/handlers/registered.handler.ts +++ b/packages/processors/src/processors/strategy/directGrantsLite/handlers/registered.handler.ts @@ -8,7 +8,7 @@ import { decodeDVMDExtendedApplicationData } from "../../helpers/index.js"; type Dependencies = Pick< ProcessorDependencies, - "roundRepository" | "projectRepository" | "metadataProvider" + "roundRepository" | "projectRepository" | "metadataProvider" | "logger" >; /** @@ -27,7 +27,15 @@ export class DGLiteRegisteredHandler implements IEventHandler<"Strategy", "Regis readonly event: ProcessorEvent<"Strategy", "RegisteredWithSender">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DGLiteRegisteredHandler", { + className: "DGLiteRegisteredHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** * Handles the RegisteredWithSender event for the Direct Grants Lite strategy. @@ -36,28 +44,71 @@ export class DGLiteRegisteredHandler implements IEventHandler<"Strategy", "Regis * @throws RoundNotFound if the round is not found. */ async handle(): Promise { - const { projectRepository, roundRepository, metadataProvider } = this.dependencies; + const { projectRepository, roundRepository, metadataProvider, logger } = this.dependencies; const { data: encodedData, recipientId, sender } = this.event.params; const { blockNumber, blockTimestamp } = this.event; + logger?.debug("Starting registration handling", { + className: "DGLiteRegisteredHandler", + methodName: "handle", + recipientId, + sender, + blockNumber, + }); + const anchorAddress = getAddress(recipientId); + logger?.debug("Fetching project by anchor", { + className: "DGLiteRegisteredHandler", + methodName: "handle", + anchorAddress, + chainId: this.chainId, + }); + const project = await projectRepository.getProjectByAnchorOrThrow( this.chainId, anchorAddress, ); const strategyAddress = getAddress(this.event.srcAddress); + logger?.debug("Fetching round by strategy address", { + className: "DGLiteRegisteredHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); + logger?.debug("Decoding application data", { + className: "DGLiteRegisteredHandler", + methodName: "handle", + encodedDataLength: encodedData.length, + }); + const values = decodeDVMDExtendedApplicationData(encodedData); - // ID is defined as recipientsCounter - 1, which is a value emitted by the strategy const id = (Number(values.recipientsCounter) - 1).toString(); + logger?.debug("Fetching application metadata", { + className: "DGLiteRegisteredHandler", + methodName: "handle", + metadataPointer: values.metadata.pointer, + applicationId: id, + }); + const metadata = await metadataProvider.getMetadata(values.metadata.pointer); + logger?.debug("Creating application record", { + className: "DGLiteRegisteredHandler", + methodName: "handle", + applicationId: id, + projectId: project.id, + roundId: round.id, + metadataFound: metadata !== null, + }); + const application: NewApplication = { chainId: this.chainId, id: id, @@ -85,9 +136,19 @@ export class DGLiteRegisteredHandler implements IEventHandler<"Strategy", "Regis tags: ["allo-v2"], }; + logger?.info("Registration processing completed", { + className: "DGLiteRegisteredHandler", + methodName: "handle", + applicationId: id, + roundId: round.id, + projectId: project.id, + status: "PENDING", + blockNumber, + }); + return [ { - type: "InsertApplication", + type: "InsertApplication" as const, args: application, }, ]; diff --git a/packages/processors/src/processors/strategy/directGrantsLite/handlers/timestampsUpdated.handler.ts b/packages/processors/src/processors/strategy/directGrantsLite/handlers/timestampsUpdated.handler.ts index 0e69546a..8c259206 100644 --- a/packages/processors/src/processors/strategy/directGrantsLite/handlers/timestampsUpdated.handler.ts +++ b/packages/processors/src/processors/strategy/directGrantsLite/handlers/timestampsUpdated.handler.ts @@ -6,7 +6,7 @@ import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; import { getDateFromTimestamp } from "../../../../helpers/index.js"; import { IEventHandler, ProcessorDependencies } from "../../../../internal.js"; -type Dependencies = Pick; +type Dependencies = Pick; /** * Handles the TimestampsUpdated event for the Direct Grants Lite strategy. @@ -23,7 +23,15 @@ export class DGLiteTimestampsUpdatedHandler readonly event: ProcessorEvent<"Strategy", "TimestampsUpdated">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DGLiteTimestampsUpdatedHandler", { + className: "DGLiteTimestampsUpdatedHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** * Handles the TimestampsUpdated event for the Direct Grants Lite strategy. @@ -31,20 +39,52 @@ export class DGLiteTimestampsUpdatedHandler * @throws RoundNotFound if the round is not found. */ async handle(): Promise { + const { roundRepository, logger } = this.dependencies; const strategyAddress = getAddress(this.event.srcAddress); - const round = await this.dependencies.roundRepository.getRoundByStrategyAddressOrThrow( + + logger?.debug("Starting timestamps update handling", { + className: "DGLiteTimestampsUpdatedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + + logger?.debug("Fetching round by strategy address", { + className: "DGLiteTimestampsUpdatedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); const { startTime: strStartTime, endTime: strEndTime } = this.event.params; + logger?.debug("Processing timestamp updates", { + className: "DGLiteTimestampsUpdatedHandler", + methodName: "handle", + roundId: round.id, + startTime: strStartTime, + endTime: strEndTime, + }); + const applicationsStartTime = getDateFromTimestamp(BigInt(strStartTime)); const applicationsEndTime = getDateFromTimestamp(BigInt(strEndTime)); - return [ + logger?.debug("Creating round update changeset", { + className: "DGLiteTimestampsUpdatedHandler", + methodName: "handle", + roundId: round.id, + applicationsStartTime: applicationsStartTime?.toISOString(), + applicationsEndTime: applicationsEndTime?.toISOString(), + }); + + const changes = [ { - type: "UpdateRound", + type: "UpdateRound" as const, args: { chainId: this.chainId, roundId: round.id, @@ -55,5 +95,16 @@ export class DGLiteTimestampsUpdatedHandler }, }, ]; + + logger?.info("Timestamps update completed", { + className: "DGLiteTimestampsUpdatedHandler", + methodName: "handle", + roundId: round.id, + startTime: applicationsStartTime?.toISOString(), + endTime: applicationsEndTime?.toISOString(), + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/strategy/directGrantsLite/handlers/updatedRegistration.handler.ts b/packages/processors/src/processors/strategy/directGrantsLite/handlers/updatedRegistration.handler.ts index 044af71f..e3f654bc 100644 --- a/packages/processors/src/processors/strategy/directGrantsLite/handlers/updatedRegistration.handler.ts +++ b/packages/processors/src/processors/strategy/directGrantsLite/handlers/updatedRegistration.handler.ts @@ -35,7 +35,15 @@ export class DGLiteUpdatedRegistrationHandler readonly event: ProcessorEvent<"Strategy", "UpdatedRegistrationWithStatus">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DGLiteUpdatedRegistrationHandler", { + className: "DGLiteUpdatedRegistrationHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** * Handles the UpdatedRegistrationWithStatus event for the Direct Grants Lite strategy. @@ -53,38 +61,92 @@ export class DGLiteUpdatedRegistrationHandler projectRepository, } = this.dependencies; - const { status: strStatus } = this.event.params; + const { status: strStatus, recipientId, data: encodedData } = this.event.params; const status = Number(strStatus); - if (!isValidApplicationStatus(status)) { - logger.warn( - `[DGLiteUpdatedRegistrationHandler] Invalid status: ${this.event.params.status}`, - ); + logger?.debug("Starting registration update handling", { + className: "DGLiteUpdatedRegistrationHandler", + methodName: "handle", + recipientId, + status, + encodedDataLength: encodedData.length, + }); + if (!isValidApplicationStatus(status)) { + logger?.warn("Invalid application status received", { + className: "DGLiteUpdatedRegistrationHandler", + methodName: "handle", + status: strStatus, + recipientId, + }); return []; } + logger?.debug("Fetching project by anchor", { + className: "DGLiteUpdatedRegistrationHandler", + methodName: "handle", + recipientId: getAddress(recipientId), + chainId: this.chainId, + }); + const project = await projectRepository.getProjectByAnchorOrThrow( this.chainId, - getAddress(this.event.params.recipientId), + getAddress(recipientId), ); + + logger?.debug("Fetching round by strategy address", { + className: "DGLiteUpdatedRegistrationHandler", + methodName: "handle", + strategyAddress: this.event.srcAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, getAddress(this.event.srcAddress), ); + + logger?.debug("Fetching application by anchor address", { + className: "DGLiteUpdatedRegistrationHandler", + methodName: "handle", + roundId: round.id, + anchorAddress: project.anchorAddress, + }); + const application = await applicationRepository.getApplicationByAnchorAddressOrThrow( this.chainId, round.id, project.anchorAddress!, ); - const encodedData = this.event.params.data; + logger?.debug("Decoding application data", { + className: "DGLiteUpdatedRegistrationHandler", + methodName: "handle", + applicationId: application.id, + encodedDataLength: encodedData.length, + }); + const values = decodeDVMDApplicationData(encodedData); - const metadata = await metadataProvider.getMetadata(values.metadata.pointer); + logger?.debug("Fetching updated metadata", { + className: "DGLiteUpdatedRegistrationHandler", + methodName: "handle", + applicationId: application.id, + metadataPointer: values.metadata.pointer, + }); + const metadata = await metadataProvider.getMetadata(values.metadata.pointer); const statusString = ApplicationStatus[status] as Application["status"]; + logger?.debug("Creating status update", { + className: "DGLiteUpdatedRegistrationHandler", + methodName: "handle", + applicationId: application.id, + currentStatus: application.status, + newStatus: statusString, + blockNumber: this.event.blockNumber, + }); + const statusUpdates = createStatusUpdate({ application, newStatus: statusString, @@ -92,9 +154,9 @@ export class DGLiteUpdatedRegistrationHandler blockTimestamp: this.event.blockTimestamp, }); - return [ + const changes = [ { - type: "UpdateApplication", + type: "UpdateApplication" as const, args: { chainId: this.chainId, roundId: round.id, @@ -108,5 +170,19 @@ export class DGLiteUpdatedRegistrationHandler }, }, ]; + + logger?.info("Registration update completed", { + className: "DGLiteUpdatedRegistrationHandler", + methodName: "handle", + applicationId: application.id, + roundId: round.id, + projectId: project.id, + oldStatus: application.status, + newStatus: statusString, + metadataUpdated: metadata !== null, + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/strategy/directGrantsSimple/directGrantsSimple.handler.ts b/packages/processors/src/processors/strategy/directGrantsSimple/directGrantsSimple.handler.ts index 5334f3c8..52e50768 100644 --- a/packages/processors/src/processors/strategy/directGrantsSimple/directGrantsSimple.handler.ts +++ b/packages/processors/src/processors/strategy/directGrantsSimple/directGrantsSimple.handler.ts @@ -22,31 +22,93 @@ export class DGSimpleStrategyHandler extends BaseStrategyHandler { private readonly dependencies: ProcessorDependencies, ) { super(STRATEGY_NAME); + this.dependencies.logger?.debug("Initializing DGSimpleStrategyHandler", { + className: "DGSimpleStrategyHandler", + chainId: this.chainId, + strategyName: STRATEGY_NAME, + }); } /** @inheritdoc */ async handle(event: ProcessorEvent<"Strategy", StrategyEvent>): Promise { - switch (event.eventName) { - case "TimestampsUpdated": - return new DGSimpleTimestampsUpdatedHandler( - event as ProcessorEvent<"Strategy", "TimestampsUpdated">, - this.chainId, - this.dependencies, - ).handle(); - case "RegisteredWithSender": - return new DGSimpleRegisteredHandler( - event as ProcessorEvent<"Strategy", "RegisteredWithSender">, - this.chainId, - this.dependencies, - ).handle(); - case "DistributedWithRecipientAddress": - return new BaseDistributedHandler( - event as ProcessorEvent<"Strategy", "DistributedWithRecipientAddress">, - this.chainId, - this.dependencies, - ).handle(); - default: - throw new UnsupportedEventException("Strategy", event.eventName, this.name); + const { logger } = this.dependencies; + + logger?.debug("Processing strategy event", { + className: "DGSimpleStrategyHandler", + methodName: "handle", + eventName: event.eventName, + strategyAddress: event.srcAddress, + blockNumber: event.blockNumber, + }); + + try { + let result: Changeset[]; + switch (event.eventName) { + case "TimestampsUpdated": + logger?.debug("Delegating to DGSimpleTimestampsUpdatedHandler", { + className: "DGSimpleStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new DGSimpleTimestampsUpdatedHandler( + event as ProcessorEvent<"Strategy", "TimestampsUpdated">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "RegisteredWithSender": + logger?.debug("Delegating to DGSimpleRegisteredHandler", { + className: "DGSimpleStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new DGSimpleRegisteredHandler( + event as ProcessorEvent<"Strategy", "RegisteredWithSender">, + this.chainId, + this.dependencies, + ).handle(); + break; + + case "DistributedWithRecipientAddress": + logger?.debug("Delegating to BaseDistributedHandler", { + className: "DGSimpleStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new BaseDistributedHandler( + event as ProcessorEvent<"Strategy", "DistributedWithRecipientAddress">, + this.chainId, + this.dependencies, + ).handle(); + break; + + default: + logger?.warn("Unsupported event received", { + className: "DGSimpleStrategyHandler", + methodName: "handle", + eventName: event.eventName, + strategyName: this.name, + }); + throw new UnsupportedEventException("Strategy", event.eventName, this.name); + } + + logger?.debug("Event processing completed", { + className: "DGSimpleStrategyHandler", + methodName: "handle", + eventName: event.eventName, + changeCount: result.length, + }); + + return result; + } catch (error) { + logger?.error("Error processing event", { + className: "DGSimpleStrategyHandler", + methodName: "handle", + eventName: event.eventName, + error: error instanceof Error ? error.message : String(error), + }); + throw error; } } } diff --git a/packages/processors/src/processors/strategy/directGrantsSimple/handlers/registered.handler.ts b/packages/processors/src/processors/strategy/directGrantsSimple/handlers/registered.handler.ts index 5afaea75..a7f98f7c 100644 --- a/packages/processors/src/processors/strategy/directGrantsSimple/handlers/registered.handler.ts +++ b/packages/processors/src/processors/strategy/directGrantsSimple/handlers/registered.handler.ts @@ -8,7 +8,7 @@ import { decodeDGApplicationData } from "../../helpers/index.js"; type Dependencies = Pick< ProcessorDependencies, - "roundRepository" | "projectRepository" | "metadataProvider" + "roundRepository" | "projectRepository" | "metadataProvider" | "logger" >; /** @@ -29,7 +29,15 @@ export class DGSimpleRegisteredHandler readonly event: ProcessorEvent<"Strategy", "RegisteredWithSender">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DGSimpleRegisteredHandler", { + className: "DGSimpleRegisteredHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** * Handles the RegisteredWithSender event for the Direct Grants Simple strategy. @@ -38,27 +46,71 @@ export class DGSimpleRegisteredHandler * @throws RoundNotFound if the round is not found. */ async handle(): Promise { - const { projectRepository, roundRepository, metadataProvider } = this.dependencies; + const { projectRepository, roundRepository, metadataProvider, logger } = this.dependencies; const { data: encodedData, recipientId, sender } = this.event.params; const { blockNumber, blockTimestamp } = this.event; + logger?.debug("Starting registration handling", { + className: "DGSimpleRegisteredHandler", + methodName: "handle", + recipientId, + sender, + blockNumber, + }); + const anchorAddress = getAddress(recipientId); + logger?.debug("Fetching project by anchor", { + className: "DGSimpleRegisteredHandler", + methodName: "handle", + anchorAddress, + chainId: this.chainId, + }); + const project = await projectRepository.getProjectByAnchorOrThrow( this.chainId, anchorAddress, ); const strategyAddress = getAddress(this.event.srcAddress); + logger?.debug("Fetching round by strategy address", { + className: "DGSimpleRegisteredHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); + logger?.debug("Decoding application data", { + className: "DGSimpleRegisteredHandler", + methodName: "handle", + encodedDataLength: encodedData.length, + }); + const values = decodeDGApplicationData(encodedData); const id = recipientId; + logger?.debug("Fetching application metadata", { + className: "DGSimpleRegisteredHandler", + methodName: "handle", + metadataPointer: values.metadata.pointer, + applicationId: id, + }); + const metadata = await metadataProvider.getMetadata(values.metadata.pointer); + logger?.debug("Creating application record", { + className: "DGSimpleRegisteredHandler", + methodName: "handle", + applicationId: id, + projectId: project.id, + roundId: round.id, + metadataFound: metadata !== null, + }); + const application: NewApplication = { chainId: this.chainId, id: id, @@ -86,9 +138,19 @@ export class DGSimpleRegisteredHandler tags: ["allo-v2"], }; + logger?.info("Registration processing completed", { + className: "DGSimpleRegisteredHandler", + methodName: "handle", + applicationId: id, + roundId: round.id, + projectId: project.id, + status: "PENDING", + blockNumber, + }); + return [ { - type: "InsertApplication", + type: "InsertApplication" as const, args: application, }, ]; diff --git a/packages/processors/src/processors/strategy/directGrantsSimple/handlers/timestampsUpdated.handler.ts b/packages/processors/src/processors/strategy/directGrantsSimple/handlers/timestampsUpdated.handler.ts index e5292b6a..3f86fa70 100644 --- a/packages/processors/src/processors/strategy/directGrantsSimple/handlers/timestampsUpdated.handler.ts +++ b/packages/processors/src/processors/strategy/directGrantsSimple/handlers/timestampsUpdated.handler.ts @@ -6,7 +6,7 @@ import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; import { getDateFromTimestamp } from "../../../../helpers/index.js"; import { IEventHandler, ProcessorDependencies } from "../../../../internal.js"; -type Dependencies = Pick; +type Dependencies = Pick; /** * Handles the TimestampsUpdated event for the Direct Grants Simple strategy. @@ -23,7 +23,15 @@ export class DGSimpleTimestampsUpdatedHandler readonly event: ProcessorEvent<"Strategy", "TimestampsUpdated">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DGSimpleTimestampsUpdatedHandler", { + className: "DGSimpleTimestampsUpdatedHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** * Handles the TimestampsUpdated event for the Direct Grants Simple strategy. @@ -31,20 +39,52 @@ export class DGSimpleTimestampsUpdatedHandler * @throws RoundNotFound if the round is not found. */ async handle(): Promise { + const { roundRepository, logger } = this.dependencies; const strategyAddress = getAddress(this.event.srcAddress); - const round = await this.dependencies.roundRepository.getRoundByStrategyAddressOrThrow( + + logger?.debug("Starting timestamps update handling", { + className: "DGSimpleTimestampsUpdatedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + + logger?.debug("Fetching round by strategy address", { + className: "DGSimpleTimestampsUpdatedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); const { startTime: strStartTime, endTime: strEndTime } = this.event.params; + logger?.debug("Processing timestamp updates", { + className: "DGSimpleTimestampsUpdatedHandler", + methodName: "handle", + roundId: round.id, + startTime: strStartTime, + endTime: strEndTime, + }); + const applicationsStartTime = getDateFromTimestamp(BigInt(strStartTime)); const applicationsEndTime = getDateFromTimestamp(BigInt(strEndTime)); - return [ + logger?.debug("Creating round update changeset", { + className: "DGSimpleTimestampsUpdatedHandler", + methodName: "handle", + roundId: round.id, + applicationsStartTime: applicationsStartTime?.toISOString(), + applicationsEndTime: applicationsEndTime?.toISOString(), + }); + + const changes = [ { - type: "UpdateRound", + type: "UpdateRound" as const, args: { chainId: this.chainId, roundId: round.id, @@ -55,5 +95,16 @@ export class DGSimpleTimestampsUpdatedHandler }, }, ]; + + logger?.info("Timestamps update completed", { + className: "DGSimpleTimestampsUpdatedHandler", + methodName: "handle", + roundId: round.id, + startTime: applicationsStartTime?.toISOString(), + endTime: applicationsEndTime?.toISOString(), + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/dvmdDirectTransfer.handler.ts b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/dvmdDirectTransfer.handler.ts index abca2cf4..12af0fc8 100644 --- a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/dvmdDirectTransfer.handler.ts +++ b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/dvmdDirectTransfer.handler.ts @@ -13,7 +13,11 @@ import { import type { ProcessorDependencies, StrategyTimings } from "../../../internal.js"; import DonationVotingMerkleDistributionDirectTransferStrategy from "../../../abis/allo-v2/v1/DonationVotingMerkleDistributionDirectTransferStrategy.js"; import { calculateAmountInUsd, getDateFromTimestamp } from "../../../helpers/index.js"; -import { TokenPriceNotFoundError, UnsupportedEventException } from "../../../internal.js"; +import { + getHandler, + TokenPriceNotFoundError, + UnsupportedEventException, +} from "../../../internal.js"; import { BaseDistributedHandler, BaseDistributionUpdatedHandler, @@ -61,64 +65,118 @@ export class DVMDDirectTransferStrategyHandler extends BaseStrategyHandler { private readonly dependencies: Dependencies, ) { super(STRATEGY_NAME); + this.dependencies.logger?.debug("Initializing DVMDDirectTransferStrategyHandler", { + className: "DVMDDirectTransferStrategyHandler", + chainId: this.chainId, + strategyName: STRATEGY_NAME, + }); } /** @inheritdoc */ async handle(event: ProcessorEvent<"Strategy", StrategyEvent>): Promise { - switch (event.eventName) { - case "RegisteredWithSender": - return new DVMDRegisteredHandler( - event as ProcessorEvent<"Strategy", "RegisteredWithSender">, - this.chainId, - this.dependencies, - ).handle(); - case "DistributedWithRecipientAddress": - return new BaseDistributedHandler( - event as ProcessorEvent<"Strategy", "DistributedWithRecipientAddress">, - this.chainId, - this.dependencies, - ).handle(); - case "AllocatedWithOrigin": - return new DVMDAllocatedHandler( - event as ProcessorEvent<"Strategy", "AllocatedWithOrigin">, - this.chainId, - this.dependencies, - ).handle(); - case "TimestampsUpdatedWithRegistrationAndAllocation": - return new DVMDTimestampsUpdatedHandler( - event as ProcessorEvent< - "Strategy", - "TimestampsUpdatedWithRegistrationAndAllocation" - >, - this.chainId, - this.dependencies, - ).handle(); - case "DistributionUpdatedWithMerkleRoot": - return new BaseDistributionUpdatedHandler( - event as ProcessorEvent<"Strategy", "DistributionUpdatedWithMerkleRoot">, - this.chainId, - this.dependencies, - ).handle(); - case "FundsDistributed": - return new BaseFundsDistributedHandler( - event as ProcessorEvent<"Strategy", "FundsDistributed">, - this.chainId, - this.dependencies, - ).handle(); - case "UpdatedRegistrationWithStatus": - return new DVMDUpdatedRegistrationHandler( - event as ProcessorEvent<"Strategy", "UpdatedRegistrationWithStatus">, - this.chainId, - this.dependencies, - ).handle(); - case "RecipientStatusUpdatedWithFullRow": - return new BaseRecipientStatusUpdatedHandler( - event as ProcessorEvent<"Strategy", "RecipientStatusUpdatedWithFullRow">, - this.chainId, - this.dependencies, - ).handle(); - default: - throw new UnsupportedEventException("Strategy", event.eventName, this.name); + const { logger } = this.dependencies; + + logger?.debug("Processing strategy event", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "handle", + eventName: event.eventName, + strategyAddress: event.srcAddress, + blockNumber: event.blockNumber, + }); + + try { + let result: Changeset[]; + switch (event.eventName) { + case "RegisteredWithSender": + logger?.debug("Delegating to DVMDRegisteredHandler", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new DVMDRegisteredHandler( + event as ProcessorEvent<"Strategy", "RegisteredWithSender">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "DistributedWithRecipientAddress": + result = await new BaseDistributedHandler( + event as ProcessorEvent<"Strategy", "DistributedWithRecipientAddress">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "AllocatedWithOrigin": + result = await new DVMDAllocatedHandler( + event as ProcessorEvent<"Strategy", "AllocatedWithOrigin">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "TimestampsUpdatedWithRegistrationAndAllocation": + result = await new DVMDTimestampsUpdatedHandler( + event as ProcessorEvent< + "Strategy", + "TimestampsUpdatedWithRegistrationAndAllocation" + >, + this.chainId, + this.dependencies, + ).handle(); + break; + case "DistributionUpdatedWithMerkleRoot": + result = await new BaseDistributionUpdatedHandler( + event as ProcessorEvent<"Strategy", "DistributionUpdatedWithMerkleRoot">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "FundsDistributed": + result = await new BaseFundsDistributedHandler( + event as ProcessorEvent<"Strategy", "FundsDistributed">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "UpdatedRegistrationWithStatus": + result = await new DVMDUpdatedRegistrationHandler( + event as ProcessorEvent<"Strategy", "UpdatedRegistrationWithStatus">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "RecipientStatusUpdatedWithFullRow": + result = await new BaseRecipientStatusUpdatedHandler( + event as ProcessorEvent<"Strategy", "RecipientStatusUpdatedWithFullRow">, + this.chainId, + this.dependencies, + ).handle(); + break; + default: + logger?.warn("Unsupported event received", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "handle", + eventName: event.eventName, + strategyName: this.name, + }); + throw new UnsupportedEventException("Strategy", event.eventName, this.name); + } + + logger?.debug("Event processing completed", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "handle", + eventName: event.eventName, + changeCount: result.length, + }); + + return result; + } catch (error) { + logger?.error("Error processing event", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "handle", + eventName: event.eventName, + error: error instanceof Error ? error.message : String(error), + }); + throw error; } } @@ -128,21 +186,49 @@ export class DVMDDirectTransferStrategyHandler extends BaseStrategyHandler { token: Token, blockTimestamp: TimestampMs, ): Promise<{ matchAmount: bigint; matchAmountInUsd: string }> { + const { logger } = this.dependencies; + + logger?.debug("Fetching match amount", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "fetchMatchAmount", + matchingFundsAvailable, + tokenSymbol: token.priceSourceCode, + blockTimestamp, + }); + const matchAmount = parseUnits(matchingFundsAvailable.toString(), token.decimals); + logger?.debug("Calculating match amount in USD", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "fetchMatchAmount", + matchAmount: matchAmount.toString(), + tokenDecimals: token.decimals, + }); + const matchAmountInUsd = await this.getTokenAmountInUsd(token, matchAmount, blockTimestamp); - return { - matchAmount, + logger?.debug("Match amount calculation completed", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "fetchMatchAmount", + matchAmount: matchAmount.toString(), matchAmountInUsd, - }; + }); + + return { matchAmount, matchAmountInUsd }; } /** @inheritdoc */ override async fetchStrategyTimings(strategyId: Address): Promise { - const { evmProvider } = this.dependencies; - let results: [bigint, bigint, bigint, bigint] = [0n, 0n, 0n, 0n]; + const { evmProvider, logger } = this.dependencies; + + logger?.debug("Fetching strategy timings", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "fetchStrategyTimings", + strategyId: getHandler(strategyId), + chainId: this.chainId, + }); + let results: [bigint, bigint, bigint, bigint] = [0n, 0n, 0n, 0n]; const contractCalls = [ { abi: DonationVotingMerkleDistributionDirectTransferStrategy, @@ -166,26 +252,60 @@ export class DVMDDirectTransferStrategyHandler extends BaseStrategyHandler { }, ] as const; - // TODO: refactor when evmProvider implements this natively - if (evmProvider.getMulticall3Address()) { - results = await evmProvider.multicall({ - contracts: contractCalls, - allowFailure: false, + try { + if (evmProvider.getMulticall3Address()) { + logger?.debug("Using multicall for fetching timings", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "fetchStrategyTimings", + multicallAddress: evmProvider.getMulticall3Address(), + }); + + results = await evmProvider.multicall({ + contracts: contractCalls, + allowFailure: false, + }); + } else { + logger?.debug("Using individual calls for fetching timings", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "fetchStrategyTimings", + }); + + results = (await Promise.all( + contractCalls.map((call) => + evmProvider.readContract(call.address, call.abi, call.functionName), + ), + )) as [bigint, bigint, bigint, bigint]; + } + + const timings = { + applicationsStartTime: getDateFromTimestamp(results[0]), + applicationsEndTime: getDateFromTimestamp(results[1]), + donationsStartTime: getDateFromTimestamp(results[2]), + donationsEndTime: getDateFromTimestamp(results[3]), + }; + + logger?.debug("Strategy timings fetched", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "fetchStrategyTimings", + strategyId: getHandler(strategyId), + timings: { + applicationsStartTime: timings.applicationsStartTime?.toISOString(), + applicationsEndTime: timings.applicationsEndTime?.toISOString(), + donationsStartTime: timings.donationsStartTime?.toISOString(), + donationsEndTime: timings.donationsEndTime?.toISOString(), + }, }); - } else { - results = (await Promise.all( - contractCalls.map((call) => - evmProvider.readContract(call.address, call.abi, call.functionName), - ), - )) as [bigint, bigint, bigint, bigint]; - } - return { - applicationsStartTime: getDateFromTimestamp(results[0]), - applicationsEndTime: getDateFromTimestamp(results[1]), - donationsStartTime: getDateFromTimestamp(results[2]), - donationsEndTime: getDateFromTimestamp(results[3]), - }; + return timings; + } catch (error) { + logger?.error("Error fetching strategy timings", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "fetchStrategyTimings", + strategyId: getHandler(strategyId), + error: error instanceof Error ? error.message : String(error), + }); + throw error; + } } /** @@ -201,13 +321,39 @@ export class DVMDDirectTransferStrategyHandler extends BaseStrategyHandler { amount: bigint, timestamp: TimestampMs, ): Promise { - const { pricingProvider } = this.dependencies; + const { pricingProvider, logger } = this.dependencies; + + logger?.debug("Getting token amount in USD", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "getTokenAmountInUsd", + tokenAddress: token.address, + amount: amount.toString(), + timestamp, + }); + const tokenPrice = await pricingProvider.getTokenPrice(token.priceSourceCode, timestamp); if (!tokenPrice) { + logger?.error("Token price not found", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "getTokenAmountInUsd", + tokenAddress: token.address, + timestamp, + }); throw new TokenPriceNotFoundError(token.address, timestamp); } - return calculateAmountInUsd(amount, tokenPrice.priceUsd, token.decimals); + const amountInUsd = calculateAmountInUsd(amount, tokenPrice.priceUsd, token.decimals); + + logger?.debug("Token amount in USD calculated", { + className: "DVMDDirectTransferStrategyHandler", + methodName: "getTokenAmountInUsd", + tokenAddress: token.address, + amount: amount.toString(), + priceUsd: tokenPrice.priceUsd, + amountInUsd, + }); + + return amountInUsd; } } diff --git a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.ts b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.ts index d916ecfe..58ebd83f 100644 --- a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.ts +++ b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.ts @@ -14,7 +14,7 @@ import { getDonationId } from "../../helpers/index.js"; type Dependencies = Pick< ProcessorDependencies, - "roundRepository" | "applicationRepository" | "pricingProvider" + "roundRepository" | "applicationRepository" | "pricingProvider" | "logger" >; /** @@ -31,7 +31,15 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate readonly event: ProcessorEvent<"Strategy", "AllocatedWithOrigin">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DVMDAllocatedHandler", { + className: "DVMDAllocatedHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** * Handles the AllocatedWithOrigin event for the Donation Voting Merkle Distribution Direct Transfer strategy. @@ -44,16 +52,40 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate * @throws {MetadataParsingFailed} if the metadata is invalid */ async handle(): Promise { - const { roundRepository, applicationRepository } = this.dependencies; + const { roundRepository, applicationRepository, pricingProvider, logger } = + this.dependencies; const { srcAddress } = this.event; const { recipientId: _recipientId, amount: strAmount, token: _token } = this.event.params; + logger?.debug("Starting allocation handling", { + className: "DVMDAllocatedHandler", + methodName: "handle", + recipientId: _recipientId, + amount: strAmount, + token: _token, + }); + const amount = BigInt(strAmount); + logger?.debug("Fetching round by strategy address", { + className: "DVMDAllocatedHandler", + methodName: "handle", + strategyAddress: srcAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, getAddress(srcAddress), ); + + logger?.debug("Fetching application details", { + className: "DVMDAllocatedHandler", + methodName: "handle", + roundId: round.id, + recipientId: _recipientId, + }); + const application = await applicationRepository.getApplicationByAnchorAddressOrThrow( this.chainId, round.id, @@ -61,31 +93,65 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate ); const donationId = getDonationId(this.event.blockNumber, this.event.logIndex); - const token = getTokenOrThrow(this.chainId, _token); const matchToken = getTokenOrThrow(this.chainId, round.matchTokenAddress); + logger?.debug("Calculating USD amount for donation", { + className: "DVMDAllocatedHandler", + methodName: "handle", + donationId, + amount: amount.toString(), + token: token, + matchToken: matchToken, + }); + const { amountInUsd } = await getTokenAmountInUsd( - this.dependencies.pricingProvider, + pricingProvider, token, amount, this.event.blockTimestamp, ); + + logger?.debug("Calculating match token amount", { + className: "DVMDAllocatedHandler", + methodName: "handle", + amountInUsd, + matchToken: matchToken, + usingDirectConversion: matchToken.address === token.address, + }); + let amountInRoundMatchToken: bigint | null = null; amountInRoundMatchToken = matchToken.address === token.address ? amount : ( await getUsdInTokenAmount( - this.dependencies.pricingProvider, + pricingProvider, matchToken, amountInUsd, this.event.blockTimestamp, ) ).amount; + logger?.debug("Parsing application metadata", { + className: "DVMDAllocatedHandler", + methodName: "handle", + applicationId: application.id, + metadataPresent: application.metadata !== null, + }); + const parsedMetadata = this.parseMetadataOrThrow(application.metadata); + logger?.debug("Creating donation record", { + className: "DVMDAllocatedHandler", + methodName: "handle", + donationId, + applicationId: application.id, + roundId: round.id, + amount: amount.toString(), + amountInUsd, + }); + const donation: Donation = { id: donationId, chainId: this.chainId, @@ -103,13 +169,13 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate timestamp: new Date(this.event.blockTimestamp), }; - return [ + const changes = [ { - type: "InsertDonation", + type: "InsertDonation" as const, args: { donation }, }, { - type: "IncrementRoundDonationStats", + type: "IncrementRoundDonationStats" as const, args: { chainId: this.chainId, roundId: round.id, @@ -117,7 +183,7 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate }, }, { - type: "IncrementApplicationDonationStats", + type: "IncrementApplicationDonationStats" as const, args: { chainId: this.chainId, roundId: round.id, @@ -126,6 +192,20 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate }, }, ]; + + logger?.info("Allocation processing completed", { + className: "DVMDAllocatedHandler", + methodName: "handle", + donationId, + roundId: round.id, + applicationId: application.id, + amount: amount.toString(), + amountInUsd, + amountInRoundMatchToken: amountInRoundMatchToken?.toString(), + changeCount: changes.length, + }); + + return changes; } /** @@ -135,8 +215,23 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate * @throws {MetadataParsingFailed} if the metadata is invalid. */ private parseMetadataOrThrow(metadata: unknown): ApplicationMetadata { + const { logger } = this.dependencies; + + logger?.debug("Parsing metadata", { + className: "DVMDAllocatedHandler", + methodName: "parseMetadataOrThrow", + metadataPresent: metadata !== null, + }); + const parsedMetadata = ApplicationMetadataSchema.safeParse(metadata); - if (!parsedMetadata.success) throw new MetadataParsingFailed(parsedMetadata.error.message); + if (!parsedMetadata.success) { + logger?.error("Failed to parse metadata", { + className: "DVMDAllocatedHandler", + methodName: "parseMetadataOrThrow", + error: parsedMetadata.error.message, + }); + throw new MetadataParsingFailed(parsedMetadata.error.message); + } return parsedMetadata.data; } diff --git a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.ts b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.ts index 49248fb7..94b3c97d 100644 --- a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.ts +++ b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.ts @@ -8,7 +8,7 @@ import { decodeDVMDExtendedApplicationData } from "../../helpers/index.js"; type Dependencies = Pick< ProcessorDependencies, - "roundRepository" | "projectRepository" | "metadataProvider" + "roundRepository" | "projectRepository" | "metadataProvider" | "logger" >; /** @@ -27,32 +27,83 @@ export class DVMDRegisteredHandler implements IEventHandler<"Strategy", "Registe readonly event: ProcessorEvent<"Strategy", "RegisteredWithSender">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DVMDRegisteredHandler", { + className: "DVMDRegisteredHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** @inheritdoc */ async handle(): Promise { - const { projectRepository, roundRepository, metadataProvider } = this.dependencies; + const { projectRepository, roundRepository, metadataProvider, logger } = this.dependencies; const { data: encodedData, recipientId, sender } = this.event.params; const { blockNumber, blockTimestamp } = this.event; + logger?.debug("Starting registration handling", { + className: "DVMDRegisteredHandler", + methodName: "handle", + recipientId, + sender, + blockNumber, + }); + const anchorAddress = getAddress(recipientId); + logger?.debug("Fetching project by anchor", { + className: "DVMDRegisteredHandler", + methodName: "handle", + anchorAddress, + chainId: this.chainId, + }); + const project = await projectRepository.getProjectByAnchorOrThrow( this.chainId, anchorAddress, ); const strategyAddress = getAddress(this.event.srcAddress); + logger?.debug("Fetching round by strategy address", { + className: "DVMDRegisteredHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); + logger?.debug("Decoding application data", { + className: "DVMDRegisteredHandler", + methodName: "handle", + encodedDataLength: encodedData.length, + }); + const values = decodeDVMDExtendedApplicationData(encodedData); - // ID is defined as recipientsCounter - 1, which is a value emitted by the strategy const id = (Number(values.recipientsCounter) - 1).toString(); + logger?.debug("Fetching application metadata", { + className: "DVMDRegisteredHandler", + methodName: "handle", + metadataPointer: values.metadata.pointer, + applicationId: id, + }); + const metadata = await metadataProvider.getMetadata(values.metadata.pointer); + logger?.debug("Creating application record", { + className: "DVMDRegisteredHandler", + methodName: "handle", + applicationId: id, + projectId: project.id, + roundId: round.id, + metadataFound: metadata !== null, + }); + const application: NewApplication = { chainId: this.chainId, id: id, @@ -80,9 +131,19 @@ export class DVMDRegisteredHandler implements IEventHandler<"Strategy", "Registe tags: ["allo-v2"], }; + logger?.info("Registration processing completed", { + className: "DVMDRegisteredHandler", + methodName: "handle", + applicationId: id, + roundId: round.id, + projectId: project.id, + status: "PENDING", + blockNumber, + }); + return [ { - type: "InsertApplication", + type: "InsertApplication" as const, args: application, }, ]; diff --git a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.ts b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.ts index 765339b0..dfba7e68 100644 --- a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.ts +++ b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.ts @@ -6,7 +6,7 @@ import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; import { getDateFromTimestamp } from "../../../../helpers/index.js"; import { IEventHandler, ProcessorDependencies } from "../../../../internal.js"; -type Dependencies = Pick; +type Dependencies = Pick; /** * Handles the TimestampsUpdated event for the Donation Voting Merkle Distribution Direct Transfer strategy. @@ -26,7 +26,15 @@ export class DVMDTimestampsUpdatedHandler >, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DVMDTimestampsUpdatedHandler", { + className: "DVMDTimestampsUpdatedHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /** * Handles the TimestampsUpdated event for the Donation Voting Merkle Distribution Direct Transfer strategy. @@ -34,8 +42,24 @@ export class DVMDTimestampsUpdatedHandler * @throws RoundNotFound if the round is not found. */ async handle(): Promise { + const { roundRepository, logger } = this.dependencies; const strategyAddress = getAddress(this.event.srcAddress); - const round = await this.dependencies.roundRepository.getRoundByStrategyAddressOrThrow( + + logger?.debug("Starting timestamps update handling", { + className: "DVMDTimestampsUpdatedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + + logger?.debug("Fetching round by strategy address", { + className: "DVMDTimestampsUpdatedHandler", + methodName: "handle", + strategyAddress, + chainId: this.chainId, + }); + + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); @@ -47,14 +71,34 @@ export class DVMDTimestampsUpdatedHandler allocationEndTime: strAllocationEndTime, } = this.event.params; + logger?.debug("Processing timestamp updates", { + className: "DVMDTimestampsUpdatedHandler", + methodName: "handle", + roundId: round.id, + registrationStartTime: strRegistrationStartTime, + registrationEndTime: strRegistrationEndTime, + allocationStartTime: strAllocationStartTime, + allocationEndTime: strAllocationEndTime, + }); + const applicationsStartTime = getDateFromTimestamp(BigInt(strRegistrationStartTime)); const applicationsEndTime = getDateFromTimestamp(BigInt(strRegistrationEndTime)); const donationsStartTime = getDateFromTimestamp(BigInt(strAllocationStartTime)); const donationsEndTime = getDateFromTimestamp(BigInt(strAllocationEndTime)); - return [ + logger?.debug("Creating round update changeset", { + className: "DVMDTimestampsUpdatedHandler", + methodName: "handle", + roundId: round.id, + applicationsStartTime: applicationsStartTime?.toISOString(), + applicationsEndTime: applicationsEndTime?.toISOString(), + donationsStartTime: donationsStartTime?.toISOString(), + donationsEndTime: donationsEndTime?.toISOString(), + }); + + const changes = [ { - type: "UpdateRound", + type: "UpdateRound" as const, args: { chainId: this.chainId, roundId: round.id, @@ -67,5 +111,16 @@ export class DVMDTimestampsUpdatedHandler }, }, ]; + + logger?.info("Timestamps update completed", { + className: "DVMDTimestampsUpdatedHandler", + methodName: "handle", + roundId: round.id, + registrationPeriod: `${applicationsStartTime?.toISOString()} - ${applicationsEndTime?.toISOString()}`, + allocationPeriod: `${donationsStartTime?.toISOString()} - ${donationsEndTime?.toISOString()}`, + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.ts b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.ts index e254c5d6..c7da05f7 100644 --- a/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.ts +++ b/packages/processors/src/processors/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.ts @@ -35,7 +35,15 @@ export class DVMDUpdatedRegistrationHandler readonly event: ProcessorEvent<"Strategy", "UpdatedRegistrationWithStatus">, private readonly chainId: ChainId, private readonly dependencies: Dependencies, - ) {} + ) { + this.dependencies.logger?.debug("Initializing DVMDUpdatedRegistrationHandler", { + className: "DVMDUpdatedRegistrationHandler", + chainId: this.chainId, + strategyAddress: this.event.srcAddress, + blockNumber: this.event.blockNumber, + transactionHash: this.event.transactionFields.hash, + }); + } /* @inheritdoc */ async handle(): Promise { @@ -47,38 +55,92 @@ export class DVMDUpdatedRegistrationHandler projectRepository, } = this.dependencies; - const { status: strStatus } = this.event.params; + const { status: strStatus, recipientId, data: encodedData } = this.event.params; const status = Number(strStatus); - if (!isValidApplicationStatus(status)) { - logger.warn( - `[DVMDUpdatedRegistrationHandler] Invalid status: ${this.event.params.status}`, - ); + logger?.debug("Starting registration update handling", { + className: "DVMDUpdatedRegistrationHandler", + methodName: "handle", + recipientId, + status, + encodedDataLength: encodedData.length, + }); + if (!isValidApplicationStatus(status)) { + logger?.warn("Invalid application status received", { + className: "DVMDUpdatedRegistrationHandler", + methodName: "handle", + status: strStatus, + recipientId, + }); return []; } + logger?.debug("Fetching project by anchor", { + className: "DVMDUpdatedRegistrationHandler", + methodName: "handle", + recipientId: getAddress(recipientId), + chainId: this.chainId, + }); + const project = await projectRepository.getProjectByAnchorOrThrow( this.chainId, - getAddress(this.event.params.recipientId), + getAddress(recipientId), ); + + logger?.debug("Fetching round by strategy address", { + className: "DVMDUpdatedRegistrationHandler", + methodName: "handle", + strategyAddress: this.event.srcAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, getAddress(this.event.srcAddress), ); + + logger?.debug("Fetching application by anchor address", { + className: "DVMDUpdatedRegistrationHandler", + methodName: "handle", + roundId: round.id, + anchorAddress: project.anchorAddress, + }); + const application = await applicationRepository.getApplicationByAnchorAddressOrThrow( this.chainId, round.id, project.anchorAddress!, ); - const encodedData = this.event.params.data; + logger?.debug("Decoding application data", { + className: "DVMDUpdatedRegistrationHandler", + methodName: "handle", + applicationId: application.id, + encodedDataLength: encodedData.length, + }); + const values = decodeDVMDApplicationData(encodedData); - const metadata = await metadataProvider.getMetadata(values.metadata.pointer); + logger?.debug("Fetching updated metadata", { + className: "DVMDUpdatedRegistrationHandler", + methodName: "handle", + applicationId: application.id, + metadataPointer: values.metadata.pointer, + }); + const metadata = await metadataProvider.getMetadata(values.metadata.pointer); const statusString = ApplicationStatus[status] as Application["status"]; + logger?.debug("Creating status update", { + className: "DVMDUpdatedRegistrationHandler", + methodName: "handle", + applicationId: application.id, + currentStatus: application.status, + newStatus: statusString, + blockNumber: this.event.blockNumber, + }); + const statusUpdates = createStatusUpdate({ application, newStatus: statusString, @@ -86,9 +148,9 @@ export class DVMDUpdatedRegistrationHandler blockTimestamp: this.event.blockTimestamp, }); - return [ + const changes = [ { - type: "UpdateApplication", + type: "UpdateApplication" as const, args: { chainId: this.chainId, roundId: round.id, @@ -102,5 +164,19 @@ export class DVMDUpdatedRegistrationHandler }, }, ]; + + logger?.info("Registration update completed", { + className: "DVMDUpdatedRegistrationHandler", + methodName: "handle", + applicationId: application.id, + roundId: round.id, + projectId: project.id, + oldStatus: application.status, + newStatus: statusString, + metadataUpdated: metadata !== null, + changeCount: changes.length, + }); + + return changes; } } diff --git a/packages/processors/src/processors/strategy/easyRetroFunding/easyRetroFunding.handler.ts b/packages/processors/src/processors/strategy/easyRetroFunding/easyRetroFunding.handler.ts index b602b8e5..fb078c0a 100644 --- a/packages/processors/src/processors/strategy/easyRetroFunding/easyRetroFunding.handler.ts +++ b/packages/processors/src/processors/strategy/easyRetroFunding/easyRetroFunding.handler.ts @@ -13,6 +13,7 @@ import { import EasyRetroFundingStrategy from "../../../abis/allo-v2/v1/EasyRetroFundingStrategy.js"; import { calculateAmountInUsd, getDateFromTimestamp } from "../../../helpers/index.js"; import { + getHandler, ProcessorDependencies, StrategyTimings, TokenPriceNotFoundError, @@ -49,53 +50,104 @@ export class EasyRetroFundingStrategyHandler extends BaseStrategyHandler { private readonly dependencies: ProcessorDependencies, ) { super(STRATEGY_NAME); + this.dependencies.logger?.debug("Initializing EasyRetroFundingStrategyHandler", { + className: "EasyRetroFundingStrategyHandler", + chainId: this.chainId, + strategyName: STRATEGY_NAME, + }); } /** @inheritdoc */ async handle(event: ProcessorEvent<"Strategy", StrategyEvent>): Promise { - switch (event.eventName) { - case "RegisteredWithSender": - return new ERFRegisteredHandler( - event as ProcessorEvent<"Strategy", "RegisteredWithSender">, - this.chainId, - this.dependencies, - ).handle(); - case "UpdatedRegistrationWithStatus": - return new ERFUpdatedRegistrationHandler( - event as ProcessorEvent<"Strategy", "UpdatedRegistrationWithStatus">, - this.chainId, - this.dependencies, - ).handle(); - case "RecipientStatusUpdatedWithFullRow": - return new BaseRecipientStatusUpdatedHandler( - event as ProcessorEvent<"Strategy", "RecipientStatusUpdatedWithFullRow">, - this.chainId, - this.dependencies, - ).handle(); - case "TimestampsUpdatedWithRegistrationAndAllocation": - return new ERFTimestampsUpdatedHandler( - event as ProcessorEvent< - "Strategy", - "TimestampsUpdatedWithRegistrationAndAllocation" - >, - this.chainId, - this.dependencies, - ).handle(); - case "DistributionUpdated": - return new BaseDistributionUpdatedHandler( - event as ProcessorEvent<"Strategy", "DistributionUpdated">, - this.chainId, - this.dependencies, - ).handle(); - case "FundsDistributed": - return new BaseFundsDistributedHandler( - event as ProcessorEvent<"Strategy", "FundsDistributed">, - this.chainId, - this.dependencies, - ).handle(); - - default: - throw new UnsupportedEventException("Strategy", event.eventName, this.name); + const { logger } = this.dependencies; + + logger?.debug("Processing strategy event", { + className: "EasyRetroFundingStrategyHandler", + methodName: "handle", + eventName: event.eventName, + strategyAddress: event.srcAddress, + blockNumber: event.blockNumber, + }); + + try { + let result: Changeset[]; + switch (event.eventName) { + case "RegisteredWithSender": + logger?.debug("Delegating to ERFRegisteredHandler", { + className: "EasyRetroFundingStrategyHandler", + methodName: "handle", + eventName: event.eventName, + }); + result = await new ERFRegisteredHandler( + event as ProcessorEvent<"Strategy", "RegisteredWithSender">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "UpdatedRegistrationWithStatus": + result = await new ERFUpdatedRegistrationHandler( + event as ProcessorEvent<"Strategy", "UpdatedRegistrationWithStatus">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "RecipientStatusUpdatedWithFullRow": + result = await new BaseRecipientStatusUpdatedHandler( + event as ProcessorEvent<"Strategy", "RecipientStatusUpdatedWithFullRow">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "TimestampsUpdatedWithRegistrationAndAllocation": + result = await new ERFTimestampsUpdatedHandler( + event as ProcessorEvent< + "Strategy", + "TimestampsUpdatedWithRegistrationAndAllocation" + >, + this.chainId, + this.dependencies, + ).handle(); + break; + case "DistributionUpdated": + result = await new BaseDistributionUpdatedHandler( + event as ProcessorEvent<"Strategy", "DistributionUpdated">, + this.chainId, + this.dependencies, + ).handle(); + break; + case "FundsDistributed": + result = await new BaseFundsDistributedHandler( + event as ProcessorEvent<"Strategy", "FundsDistributed">, + this.chainId, + this.dependencies, + ).handle(); + break; + default: + logger?.warn("Unsupported event received", { + className: "EasyRetroFundingStrategyHandler", + methodName: "handle", + eventName: event.eventName, + strategyName: this.name, + }); + throw new UnsupportedEventException("Strategy", event.eventName, this.name); + } + + logger?.debug("Event processing completed", { + className: "EasyRetroFundingStrategyHandler", + methodName: "handle", + eventName: event.eventName, + changeCount: result.length, + }); + + return result; + } catch (error) { + logger?.error("Error processing event", { + className: "EasyRetroFundingStrategyHandler", + methodName: "handle", + eventName: event.eventName, + error: error instanceof Error ? error.message : String(error), + }); + throw error; } } @@ -105,21 +157,41 @@ export class EasyRetroFundingStrategyHandler extends BaseStrategyHandler { token: Token, blockTimestamp: TimestampMs, ): Promise<{ matchAmount: bigint; matchAmountInUsd: string }> { - const matchAmount = parseUnits(matchingFundsAvailable.toString(), token.decimals); + const { logger } = this.dependencies; + + logger?.debug("Fetching match amount", { + className: "EasyRetroFundingStrategyHandler", + methodName: "fetchMatchAmount", + matchingFundsAvailable, + tokenAddress: token.address, + blockTimestamp, + }); + const matchAmount = parseUnits(matchingFundsAvailable.toString(), token.decimals); const matchAmountInUsd = await this.getTokenAmountInUsd(token, matchAmount, blockTimestamp); - return { - matchAmount, + logger?.debug("Match amount calculation completed", { + className: "EasyRetroFundingStrategyHandler", + methodName: "fetchMatchAmount", + matchAmount: matchAmount.toString(), matchAmountInUsd, - }; + }); + + return { matchAmount, matchAmountInUsd }; } /** @inheritdoc */ override async fetchStrategyTimings(strategyId: Address): Promise { - const { evmProvider } = this.dependencies; - let results: [bigint, bigint, bigint, bigint] = [0n, 0n, 0n, 0n]; + const { evmProvider, logger } = this.dependencies; + + logger?.debug("Fetching strategy timings", { + className: "EasyRetroFundingStrategyHandler", + methodName: "fetchStrategyTimings", + strategyId: getHandler(strategyId), + chainId: this.chainId, + }); + let results: [bigint, bigint, bigint, bigint] = [0n, 0n, 0n, 0n]; const contractCalls = [ { abi: EasyRetroFundingStrategy, @@ -143,26 +215,60 @@ export class EasyRetroFundingStrategyHandler extends BaseStrategyHandler { }, ] as const; - // TODO: refactor when evmProvider implements this natively - if (evmProvider.getMulticall3Address()) { - results = await evmProvider.multicall({ - contracts: contractCalls, - allowFailure: false, + try { + if (evmProvider.getMulticall3Address()) { + logger?.debug("Using multicall for fetching timings", { + className: "EasyRetroFundingStrategyHandler", + methodName: "fetchStrategyTimings", + multicallAddress: evmProvider.getMulticall3Address(), + }); + results = await evmProvider.multicall({ + contracts: contractCalls, + allowFailure: false, + }); + } else { + logger?.debug("Using individual calls for fetching timings", { + className: "EasyRetroFundingStrategyHandler", + methodName: "fetchStrategyTimings", + }); + results = (await Promise.all( + contractCalls.map((call) => + evmProvider.readContract(call.address, call.abi, call.functionName), + ), + )) as [bigint, bigint, bigint, bigint]; + } + + const timings = { + applicationsStartTime: getDateFromTimestamp(results[0]), + applicationsEndTime: getDateFromTimestamp(results[1]), + donationsStartTime: getDateFromTimestamp(results[2]), + donationsEndTime: getDateFromTimestamp(results[3]), + }; + + logger?.debug("Strategy timings fetched", { + className: "EasyRetroFundingStrategyHandler", + methodName: "fetchStrategyTimings", + strategyId: getHandler(strategyId), + chainId: this.chainId, + timings: { + applicationsStartTime: timings.applicationsStartTime?.toISOString(), + applicationsEndTime: timings.applicationsEndTime?.toISOString(), + donationsStartTime: timings.donationsStartTime?.toISOString(), + donationsEndTime: timings.donationsEndTime?.toISOString(), + }, }); - } else { - results = (await Promise.all( - contractCalls.map((call) => - evmProvider.readContract(call.address, call.abi, call.functionName), - ), - )) as [bigint, bigint, bigint, bigint]; - } - return { - applicationsStartTime: getDateFromTimestamp(results[0]), - applicationsEndTime: getDateFromTimestamp(results[1]), - donationsStartTime: getDateFromTimestamp(results[2]), - donationsEndTime: getDateFromTimestamp(results[3]), - }; + return timings; + } catch (error) { + logger?.error("Error fetching strategy timings", { + className: "EasyRetroFundingStrategyHandler", + methodName: "fetchStrategyTimings", + strategyId: getHandler(strategyId), + chainId: this.chainId, + error: error instanceof Error ? error.message : String(error), + }); + throw error; + } } /** @@ -178,13 +284,37 @@ export class EasyRetroFundingStrategyHandler extends BaseStrategyHandler { amount: bigint, timestamp: TimestampMs, ): Promise { - const { pricingProvider } = this.dependencies; + const { pricingProvider, logger } = this.dependencies; + + logger?.debug("Getting token amount in USD", { + className: "EasyRetroFundingStrategyHandler", + methodName: "getTokenAmountInUsd", + tokenAddress: token.address, + amount: amount.toString(), + timestamp, + }); + const tokenPrice = await pricingProvider.getTokenPrice(token.priceSourceCode, timestamp); if (!tokenPrice) { + logger?.error("Token price not found", { + className: "EasyRetroFundingStrategyHandler", + methodName: "getTokenAmountInUsd", + tokenAddress: token.address, + timestamp, + }); throw new TokenPriceNotFoundError(token.address, timestamp); } - return calculateAmountInUsd(amount, tokenPrice.priceUsd, token.decimals); + const amountInUsd = calculateAmountInUsd(amount, tokenPrice.priceUsd, token.decimals); + + logger?.debug("Token amount in USD calculated", { + className: "EasyRetroFundingStrategyHandler", + methodName: "getTokenAmountInUsd", + tokenAddress: token.address, + amountInUsd, + }); + + return amountInUsd; } } diff --git a/packages/processors/src/processors/strategy/strategy.processor.ts b/packages/processors/src/processors/strategy/strategy.processor.ts index 2981d0aa..edde9132 100644 --- a/packages/processors/src/processors/strategy/strategy.processor.ts +++ b/packages/processors/src/processors/strategy/strategy.processor.ts @@ -2,7 +2,7 @@ import { Changeset } from "@grants-stack-indexer/repository"; import { ChainId, ProcessorEvent, StrategyEvent } from "@grants-stack-indexer/shared"; import type { IProcessor, ProcessorDependencies } from "../../internal.js"; -import { UnsupportedStrategy } from "../../internal.js"; +import { getHandler, UnsupportedStrategy } from "../../internal.js"; import { StrategyHandlerFactory } from "./strategyHandler.factory.js"; export class StrategyProcessor implements IProcessor<"Strategy", StrategyEvent> { @@ -24,7 +24,7 @@ export class StrategyProcessor implements IProcessor<"Strategy", StrategyEvent> className: "StrategyProcessor", methodName: "process", eventName: event.eventName, - strategyId, + strategyId: getHandler(strategyId), chainId: this.chainId, blockNumber: event.blockNumber, }); @@ -33,7 +33,7 @@ export class StrategyProcessor implements IProcessor<"Strategy", StrategyEvent> logger?.debug("Creating strategy handler", { className: "StrategyProcessor", methodName: "process", - strategyId, + strategyId: getHandler(strategyId), chainId: this.chainId, }); @@ -47,7 +47,7 @@ export class StrategyProcessor implements IProcessor<"Strategy", StrategyEvent> logger?.error("Unsupported strategy encountered", { className: "StrategyProcessor", methodName: "process", - strategyId, + strategyId: getHandler(strategyId), chainId: this.chainId, }); throw new UnsupportedStrategy(strategyId); @@ -56,7 +56,7 @@ export class StrategyProcessor implements IProcessor<"Strategy", StrategyEvent> logger?.debug("Delegating to strategy handler", { className: "StrategyProcessor", methodName: "process", - strategyId, + strategyId: getHandler(strategyId), handlerType: strategyHandler.constructor.name, }); @@ -65,7 +65,7 @@ export class StrategyProcessor implements IProcessor<"Strategy", StrategyEvent> logger?.info("Strategy event processing completed", { className: "StrategyProcessor", methodName: "process", - strategyId, + strategyId: getHandler(strategyId), eventName: event.eventName, chainId: this.chainId, changesetCount: result.length, @@ -76,7 +76,7 @@ export class StrategyProcessor implements IProcessor<"Strategy", StrategyEvent> logger?.error("Error processing strategy event", { className: "StrategyProcessor", methodName: "process", - strategyId, + strategyId: getHandler(strategyId), eventName: event.eventName, chainId: this.chainId, error: error instanceof Error ? error.message : String(error), diff --git a/setup.sh b/setup.sh new file mode 100755 index 00000000..6c27f868 --- /dev/null +++ b/setup.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -e # Exit on any error + +echo "๐Ÿงน Cleaning up existing containers..." +if ! docker-compose down -v; then + echo "Error stopping containers" + exit 1 +fi + +sleep 10 + +echo "๐Ÿ—๏ธ Building and starting containers..." +if ! docker-compose up -d --build; then + echo "Error starting containers" + exit 1 +fi + +echo "โณ Waiting for database to be ready..." +sleep 60 + +echo "๐Ÿ”„ Running cache migrations..." +if ! pnpm db:cache:migrate; then + echo "Error running cache migrations" + exit 1 +fi + +echo "๐Ÿ“ฆ Bootstrapping metadata..." +if ! pnpm bootstrap:metadata; then + echo "Error bootstrapping metadata" + exit 1 +fi + +echo "๐Ÿ’ฐ Bootstrapping pricing..." +if ! pnpm bootstrap:pricing; then + echo "Error bootstrapping pricing" + exit 1 +fi + +echo "๐Ÿ—ƒ๏ธ Running database migrations..." +if ! pnpm db:migrate; then + echo "Error running migrations" + exit 1 +fi + +echo "โš™๏ธ Configuring API..." +if ! pnpm api:configure; then + echo "Error configuring API" + exit 1 +fi + +echo "โœ… Setup completed!" From e28f1c186322d7dc51ab4061c8fb1a2eeea6b024 Mon Sep 17 00:00:00 2001 From: 0xKurt Date: Wed, 2 Apr 2025 12:47:07 +0200 Subject: [PATCH 3/3] add logging --- packages/data-flow/src/eventsFetcher.ts | 2 +- packages/data-flow/src/orchestrator.ts | 215 ++++++++++++++++-- .../handlers/registered.handler.ts | 83 ++++++- .../handlers/timestampsUpdated.handler.ts | 71 +++++- .../handlers/updatedRegistration.handler.ts | 117 +++++++++- 5 files changed, 454 insertions(+), 34 deletions(-) diff --git a/packages/data-flow/src/eventsFetcher.ts b/packages/data-flow/src/eventsFetcher.ts index 9d25533c..3a83655e 100644 --- a/packages/data-flow/src/eventsFetcher.ts +++ b/packages/data-flow/src/eventsFetcher.ts @@ -14,7 +14,7 @@ export class EventsFetcher implements IEventsFetcher { chainId, blockNumber, logIndex, - limit = 100, + limit = 500, allowPartialLastBlock = true, }: GetEventsAfterBlockNumberAndLogIndexParams): Promise { return await this.indexerClient.getEventsAfterBlockNumberAndLogIndex({ diff --git a/packages/data-flow/src/orchestrator.ts b/packages/data-flow/src/orchestrator.ts index e1063ad9..6a4d7df4 100644 --- a/packages/data-flow/src/orchestrator.ts +++ b/packages/data-flow/src/orchestrator.ts @@ -137,24 +137,88 @@ export class Orchestrator { async run(signal: AbortSignal): Promise { let totalEvents = 0; let processedEvents = 0; + this.logger.debug("Starting run loop", { + className: Orchestrator.name, + chainId: this.chainId, + }); while (!signal.aborted) { let event: ProcessorEvent | undefined; try { if (this.eventsQueue.isEmpty()) { + this.logger.debug("Queue empty, fetching next batch", { + className: Orchestrator.name, + chainId: this.chainId, + }); const events = await this.getNextEventsBatch(); + this.logger.debug("Fetched events batch", { + className: Orchestrator.name, + chainId: this.chainId, + eventCount: events.length, + }); + await this.bulkFetchMetadataAndPricesForBatch(events); + this.logger.debug("Fetched metadata and prices", { + className: Orchestrator.name, + chainId: this.chainId, + }); + await this.enqueueEvents(events); + this.logger.debug("Enqueued events", { + className: Orchestrator.name, + chainId: this.chainId, + }); + totalEvents += events.length; + this.logger.debug("Updated total events count", { + className: Orchestrator.name, + chainId: this.chainId, + totalEvents, + }); } event = this.eventsQueue.pop(); + this.logger.debug("Popped event from queue", { + className: Orchestrator.name, + chainId: this.chainId, + hasEvent: !!event, + eventDetails: event ? `${event.blockNumber}:${event.logIndex}` : "none", + event: event + ? { + eventName: event.eventName, + contractName: event.contractName, + params: event.params, + srcAddress: event.srcAddress, + blockNumber: event.blockNumber, + logIndex: event.logIndex, + blockTimestamp: event.blockTimestamp, + } + : null, + queueState: { + remainingEvents: this.eventsQueue.length, + currentBatchProgress: `${processedEvents}/${totalEvents}`, + lastBlockNumber: event?.blockNumber, + lastLogIndex: event?.logIndex, + }, + blockContext: event + ? { + totalEventsInBlock: + this.eventsByBlockContext.get(event.blockNumber)?.length ?? 0, + eventsInBlockIndexes: this.eventsByBlockContext + .get(event.blockNumber) + ?.map((e) => e.logIndex), + isLastEventInBlock: this.isLastEventInBlock(event), + nextEventInBlock: this.getNextEventInBlock(event), + isLastEventInBatch: processedEvents + 1 >= totalEvents, + } + : null, + }); + if (!event) { this.logger.debug( `No event to process, sleeping for ${this.fetchDelayInMs}ms`, { className: Orchestrator.name, - chainId: this.chainId, }, ); await delay(this.fetchDelayInMs); @@ -167,26 +231,21 @@ export class Orchestrator { className: Orchestrator.name, chainId: this.chainId, eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, - blockNumber: event!.blockNumber, - logIndex: event!.logIndex, }); const changesets = await this.handleEvent(event!); - - this.logger.debug("Event handling completed", { + this.logger.debug("Event handled", { className: Orchestrator.name, chainId: this.chainId, hasChangesets: !!changesets, - changesetsCount: changesets?.length ?? 0, - eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + changesetCount: changesets?.length ?? 0, }); if (changesets) { - this.logger.debug("Applying changesets with processed event", { + this.logger.debug("Preparing to apply changesets", { className: Orchestrator.name, chainId: this.chainId, - totalChangesets: changesets.length + 1, - eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, + changesetCount: changesets.length, }); await this.dataLoader.applyChanges([ @@ -202,11 +261,15 @@ export class Orchestrator { }, }, ]); + this.logger.debug("Applied changesets successfully", { + className: Orchestrator.name, + chainId: this.chainId, + totalApplied: changesets.length + 1, + }); } else { - this.logger.debug("Applying only processed event record", { + this.logger.debug("No changesets, applying only processed event", { className: Orchestrator.name, chainId: this.chainId, - eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, }); await this.dataLoader.applyChanges([ @@ -221,17 +284,23 @@ export class Orchestrator { }, }, ]); + this.logger.debug("Applied processed event record", { + className: Orchestrator.name, + chainId: this.chainId, + }); } - - this.logger.debug("Changes applied successfully", { - className: Orchestrator.name, - chainId: this.chainId, - eventIdentifier: `${event!.blockNumber}:${event!.logIndex}`, - }); }, { abortSignal: signal }, ); + processedEvents++; + this.logger.debug("Updated processed events count", { + className: Orchestrator.name, + chainId: this.chainId, + processedEvents, + totalEvents, + }); + this.logger.info(`Processed events: ${processedEvents}/${totalEvents}`, { className: Orchestrator.name, chainId: this.chainId, @@ -240,11 +309,11 @@ export class Orchestrator { currentBlock: event!.blockNumber, }); } catch (error: unknown) { - this.logger.warn("Error encountered during event processing", { + this.logger.debug("Entered error handling block", { className: Orchestrator.name, chainId: this.chainId, - eventIdentifier: event ? `${event.blockNumber}:${event.logIndex}` : undefined, errorType: error instanceof Error ? error.constructor.name : typeof error, + errorMessage: error instanceof Error ? error.message : String(error), }); if (event) { @@ -446,6 +515,13 @@ export class Orchestrator { */ private async getNextEventsBatch(): Promise { const lastProcessedEvent = await this.eventsRegistry.getLastProcessedEvent(this.chainId); + this.logger.debug("Fetching next batch of events", { + className: Orchestrator.name, + chainId: this.chainId, + lastProcessedBlockNumber: lastProcessedEvent?.blockNumber ?? 0, + lastProcessedLogIndex: lastProcessedEvent?.logIndex ?? 0, + fetchLimit: this.fetchLimit, + }); const blockNumber = lastProcessedEvent?.blockNumber ?? 0; const logIndex = lastProcessedEvent?.logIndex ?? 0; @@ -457,6 +533,25 @@ export class Orchestrator { limit: this.fetchLimit, }); + this.logger.debug("Fetched events details", { + className: Orchestrator.name, + chainId: this.chainId, + eventCount: events.length, + firstEvent: events[0] + ? { + blockNumber: events[0].blockNumber, + logIndex: events[0].logIndex, + } + : null, + lastEvent: events[events.length - 1] + ? { + blockNumber: events[events.length - 1]!.blockNumber, + logIndex: events[events.length - 1]!.logIndex, + } + : null, + uniqueBlocks: [...new Set(events.map((e) => e.blockNumber))].length, + }); + return events; } @@ -491,7 +586,18 @@ export class Orchestrator { * @param events - The events batch */ private async enqueueEvents(events: AnyIndexerFetchedEvent[]): Promise { - // Clear previous context + this.logger.debug("Starting to enqueue events", { + className: Orchestrator.name, + chainId: this.chainId, + totalEvents: events.length, + eventsByBlock: Object.fromEntries( + Array.from(new Set(events.map((e) => e.blockNumber))).map((block) => [ + block, + events.filter((e) => e.blockNumber === block).length, + ]), + ), + }); + this.eventsByBlockContext.clear(); for (const event of events) { if (!this.eventsByBlockContext.has(event.blockNumber)) { @@ -501,6 +607,16 @@ export class Orchestrator { } this.eventsQueue.push(...events); + + this.logger.debug("Events enqueued with block context", { + className: Orchestrator.name, + chainId: this.chainId, + blockContextSize: this.eventsByBlockContext.size, + blocksWithEvents: Array.from(this.eventsByBlockContext.keys()), + eventsPerBlock: Array.from(this.eventsByBlockContext.entries()).map( + ([block, events]) => ({ block, count: events.length }), + ), + }); } /** @@ -618,9 +734,30 @@ export class Orchestrator { private async handleEvent( event: ProcessorEvent, ): Promise { + this.logger.debug("Starting event handling", { + className: Orchestrator.name, + chainId: this.chainId, + eventDetails: { + blockNumber: event.blockNumber, + logIndex: event.logIndex, + eventName: event.eventName, + contractName: event.contractName, + hasStrategyId: "strategyId" in event, + }, + }); + event = await this.enhanceStrategyId(event); + if (this.isPoolCreated(event)) { const handleable = existsHandler(event.strategyId); + this.logger.debug("Processing PoolCreated event", { + className: Orchestrator.name, + chainId: this.chainId, + strategyId: event.strategyId, + strategyAddress: event.params.strategy, + isHandleable: handleable, + }); + await this.strategyRegistry.saveStrategyId( this.chainId, event.params.strategy, @@ -629,17 +766,28 @@ export class Orchestrator { ); } else if (event.contractName === "Strategy" && "strategyId" in event) { if (!existsHandler(event.strategyId)) { - this.logger.debug("Skipping event", { - event, + this.logger.debug("Skipping unhandled strategy event", { className: Orchestrator.name, chainId: this.chainId, + eventName: event.eventName, + strategyId: event.strategyId, + blockNumber: event.blockNumber, + logIndex: event.logIndex, }); - // we skip the event if the strategy id is not handled yet return undefined; } } - return this.eventsProcessor.processEvent(event); + const result = await this.eventsProcessor.processEvent(event); + this.logger.debug("Event handling completed", { + className: Orchestrator.name, + chainId: this.chainId, + eventIdentifier: `${event.blockNumber}:${event.logIndex}`, + hasResult: !!result, + changesetCount: result?.length ?? 0, + }); + + return result; } /** @@ -722,4 +870,21 @@ export class Orchestrator { ): event is ProcessorEvent<"Allo", "PoolCreated"> | ProcessorEvent<"Strategy", StrategyEvent> { return this.isPoolCreated(event) || isStrategyEvent(event); } + + private isLastEventInBlock(event: AnyIndexerFetchedEvent): boolean { + const eventsInBlock = this.eventsByBlockContext.get(event.blockNumber); + if (!eventsInBlock || eventsInBlock.length === 0) return false; + const lastEvent = eventsInBlock[eventsInBlock.length - 1]!; + return lastEvent.logIndex === event.logIndex; + } + + private getNextEventInBlock(event: AnyIndexerFetchedEvent): AnyIndexerFetchedEvent | undefined { + const eventsInBlock = this.eventsByBlockContext.get(event.blockNumber); + if (!eventsInBlock) return undefined; + + const currentIndex = eventsInBlock.findIndex((e) => e.logIndex === event.logIndex); + if (currentIndex === -1 || currentIndex === eventsInBlock.length - 1) return undefined; + + return eventsInBlock[currentIndex + 1]; + } } diff --git a/packages/processors/src/processors/strategy/easyRetroFunding/handlers/registered.handler.ts b/packages/processors/src/processors/strategy/easyRetroFunding/handlers/registered.handler.ts index d56bf6b8..cc9149ae 100644 --- a/packages/processors/src/processors/strategy/easyRetroFunding/handlers/registered.handler.ts +++ b/packages/processors/src/processors/strategy/easyRetroFunding/handlers/registered.handler.ts @@ -8,7 +8,7 @@ import { decodeDVMDExtendedApplicationData } from "../../helpers/index.js"; type Dependencies = Pick< ProcessorDependencies, - "roundRepository" | "projectRepository" | "metadataProvider" + "roundRepository" | "projectRepository" | "metadataProvider" | "logger" >; /** @@ -35,28 +35,90 @@ export class ERFRegisteredHandler implements IEventHandler<"Strategy", "Register * @throws RoundNotFound if the round is not found. */ async handle(): Promise { - const { projectRepository, roundRepository, metadataProvider } = this.dependencies; + const { projectRepository, roundRepository, metadataProvider, logger } = this.dependencies; const { data: encodedData, recipientId, sender } = this.event.params; const { blockNumber, blockTimestamp } = this.event; + logger.debug("Starting registration handling", { + className: ERFRegisteredHandler.name, + methodName: "handle", + chainId: this.chainId, + eventDetails: { + blockNumber, + logIndex: this.event.logIndex, + recipientId, + sender, + encodedDataLength: encodedData.length, + }, + }); + const anchorAddress = getAddress(recipientId); + logger.debug("Fetching project by anchor", { + className: ERFRegisteredHandler.name, + methodName: "handle", + chainId: this.chainId, + anchorAddress, + }); + const project = await projectRepository.getProjectByAnchorOrThrow( this.chainId, anchorAddress, ); + logger.debug("Project found", { + className: ERFRegisteredHandler.name, + methodName: "handle", + projectId: project.id, + }); + const strategyAddress = getAddress(this.event.srcAddress); + logger.debug("Fetching round by strategy", { + className: ERFRegisteredHandler.name, + methodName: "handle", + strategyAddress, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); + logger.debug("Round found", { + className: ERFRegisteredHandler.name, + methodName: "handle", + roundId: round.id, + }); + + logger.debug("Decoding application data", { + className: ERFRegisteredHandler.name, + methodName: "handle", + encodedDataLength: encodedData.length, + }); + const values = decodeDVMDExtendedApplicationData(encodedData); - // ID is defined as recipientsCounter - 1, which is a value emitted by the strategy const id = (Number(values.recipientsCounter) - 1).toString(); + logger.debug("Application data decoded", { + className: ERFRegisteredHandler.name, + methodName: "handle", + applicationId: id, + metadataPointer: values.metadata.pointer, + }); + + logger.debug("Fetching metadata", { + className: ERFRegisteredHandler.name, + methodName: "handle", + metadataPointer: values.metadata.pointer, + }); + const metadata = await metadataProvider.getMetadata(values.metadata.pointer); + logger.debug("Metadata retrieved", { + className: ERFRegisteredHandler.name, + methodName: "handle", + metadataFound: metadata !== null, + }); + const application: NewApplication = { chainId: this.chainId, id: id, @@ -84,6 +146,21 @@ export class ERFRegisteredHandler implements IEventHandler<"Strategy", "Register tags: ["allo-v2"], }; + logger.info("Registration handling completed", { + className: ERFRegisteredHandler.name, + methodName: "handle", + applicationDetails: { + id, + projectId: project.id, + roundId: round.id, + anchorAddress, + createdByAddress: getAddress(sender), + metadataCid: values.metadata.pointer, + blockNumber: blockNumber.toString(), + timestamp: new Date(blockTimestamp).toISOString(), + }, + }); + return [ { type: "InsertApplication", diff --git a/packages/processors/src/processors/strategy/easyRetroFunding/handlers/timestampsUpdated.handler.ts b/packages/processors/src/processors/strategy/easyRetroFunding/handlers/timestampsUpdated.handler.ts index 7c700ed3..ee2e957d 100644 --- a/packages/processors/src/processors/strategy/easyRetroFunding/handlers/timestampsUpdated.handler.ts +++ b/packages/processors/src/processors/strategy/easyRetroFunding/handlers/timestampsUpdated.handler.ts @@ -6,7 +6,7 @@ import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; import { getDateFromTimestamp } from "../../../../helpers/index.js"; import { IEventHandler, ProcessorDependencies } from "../../../../internal.js"; -type Dependencies = Pick; +type Dependencies = Pick; /** * Handles the TimestampsUpdated event for the Easy Retro Funding strategy. @@ -34,12 +34,39 @@ export class ERFTimestampsUpdatedHandler * @throws RoundNotFound if the round is not found. */ async handle(): Promise { + const { roundRepository } = this.dependencies; + + this.dependencies.logger.debug("Starting timestamps update handling", { + className: ERFTimestampsUpdatedHandler.name, + methodName: "handle", + chainId: this.chainId, + eventDetails: { + blockNumber: this.event.blockNumber, + logIndex: this.event.logIndex, + strategyAddress: this.event.srcAddress, + }, + }); + const strategyAddress = getAddress(this.event.srcAddress); - const round = await this.dependencies.roundRepository.getRoundByStrategyAddressOrThrow( + this.dependencies.logger.debug("Fetching round by strategy address", { + className: ERFTimestampsUpdatedHandler.name, + methodName: "handle", + chainId: this.chainId, + strategyAddress, + }); + + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); + this.dependencies.logger.debug("Round found", { + className: ERFTimestampsUpdatedHandler.name, + methodName: "handle", + roundId: round.id, + roundAddress: round.strategyAddress, + }); + const { registrationStartTime: strRegistrationStartTime, registrationEndTime: strRegistrationEndTime, @@ -47,11 +74,51 @@ export class ERFTimestampsUpdatedHandler allocationEndTime: strAllocationEndTime, } = this.event.params; + this.dependencies.logger.debug("Processing timestamp parameters", { + className: ERFTimestampsUpdatedHandler.name, + methodName: "handle", + rawTimestamps: { + registrationStart: strRegistrationStartTime, + registrationEnd: strRegistrationEndTime, + allocationStart: strAllocationStartTime, + allocationEnd: strAllocationEndTime, + }, + }); + const applicationsStartTime = getDateFromTimestamp(BigInt(strRegistrationStartTime)); const applicationsEndTime = getDateFromTimestamp(BigInt(strRegistrationEndTime)); const donationsStartTime = getDateFromTimestamp(BigInt(strAllocationStartTime)); const donationsEndTime = getDateFromTimestamp(BigInt(strAllocationEndTime)); + this.dependencies.logger.debug("Converted timestamps to dates", { + className: ERFTimestampsUpdatedHandler.name, + methodName: "handle", + convertedDates: { + applicationsStartTime: applicationsStartTime?.toISOString(), + applicationsEndTime: applicationsEndTime?.toISOString(), + donationsStartTime: donationsStartTime?.toISOString(), + donationsEndTime: donationsEndTime?.toISOString(), + }, + }); + + this.dependencies.logger.info("Timestamps update handling completed", { + className: ERFTimestampsUpdatedHandler.name, + methodName: "handle", + roundId: round.id, + updates: { + applicationsStartTime: applicationsStartTime?.toISOString(), + applicationsEndTime: applicationsEndTime?.toISOString(), + donationsStartTime: donationsStartTime?.toISOString(), + donationsEndTime: donationsEndTime?.toISOString(), + }, + previousValues: { + applicationsStartTime: round.applicationsStartTime?.toISOString(), + applicationsEndTime: round.applicationsEndTime?.toISOString(), + donationsStartTime: round.donationsStartTime?.toISOString(), + donationsEndTime: round.donationsEndTime?.toISOString(), + }, + }); + return [ { type: "UpdateRound", diff --git a/packages/processors/src/processors/strategy/easyRetroFunding/handlers/updatedRegistration.handler.ts b/packages/processors/src/processors/strategy/easyRetroFunding/handlers/updatedRegistration.handler.ts index 58a3d689..afa8b2b4 100644 --- a/packages/processors/src/processors/strategy/easyRetroFunding/handlers/updatedRegistration.handler.ts +++ b/packages/processors/src/processors/strategy/easyRetroFunding/handlers/updatedRegistration.handler.ts @@ -53,38 +53,138 @@ export class ERFUpdatedRegistrationHandler projectRepository, } = this.dependencies; + logger.debug("Starting ERF registration update handling", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + chainId: this.chainId, + eventDetails: { + blockNumber: this.event.blockNumber, + logIndex: this.event.logIndex, + recipientId: this.event.params.recipientId, + strategyAddress: this.event.srcAddress, + encodedDataLength: this.event.params.data.length, + }, + }); + const { status: strStatus } = this.event.params; const status = Number(strStatus); + logger.debug("Validating application status", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + rawStatus: strStatus, + parsedStatus: status, + isValid: isValidApplicationStatus(status), + }); + if (!isValidApplicationStatus(status)) { - logger.warn( - `[ERFUpdatedRegistrationHandler] Invalid status: ${this.event.params.status}`, - ); + logger.warn("Invalid application status", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + status: strStatus, + recipientId: this.event.params.recipientId, + }); return []; } + logger.debug("Fetching project details", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + recipientId: this.event.params.recipientId, + chainId: this.chainId, + }); + const project = await projectRepository.getProjectByAnchorOrThrow( this.chainId, getAddress(this.event.params.recipientId), ); + + logger.debug("Project found", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + projectId: project.id, + anchorAddress: project.anchorAddress, + }); + + logger.debug("Fetching round details", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + strategyAddress: this.event.srcAddress, + chainId: this.chainId, + }); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, getAddress(this.event.srcAddress), ); + + logger.debug("Round found", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + roundId: round.id, + roundAddress: round.strategyAddress, + }); + + logger.debug("Fetching application details", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + roundId: round.id, + anchorAddress: project.anchorAddress, + }); + const application = await applicationRepository.getApplicationByAnchorAddressOrThrow( this.chainId, round.id, project.anchorAddress!, ); + logger.debug("Application found", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + applicationId: application.id, + currentStatus: application.status, + }); + + logger.debug("Decoding application data", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + encodedDataLength: this.event.params.data.length, + }); + const encodedData = this.event.params.data; const values = decodeDVMDApplicationData(encodedData); + logger.debug("Application data decoded", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + metadataPointer: values.metadata.pointer, + }); + + logger.debug("Fetching metadata", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + metadataPointer: values.metadata.pointer, + }); + const metadata = await metadataProvider.getMetadata(values.metadata.pointer); + logger.debug("Metadata fetched", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + hasMetadata: !!metadata, + }); + const statusString = ApplicationStatus[status] as Application["status"]; + logger.debug("Creating status update", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + currentStatus: application.status, + newStatus: statusString, + blockNumber: this.event.blockNumber, + }); + const statusUpdates = createStatusUpdate({ application, newStatus: statusString, @@ -92,6 +192,17 @@ export class ERFUpdatedRegistrationHandler blockTimestamp: this.event.blockTimestamp, }); + logger.info("Registration update handling completed", { + className: ERFUpdatedRegistrationHandler.name, + methodName: "handle", + applicationId: application.id, + roundId: round.id, + projectId: project.id, + oldStatus: application.status, + newStatus: statusString, + metadataUpdated: !!metadata, + }); + return [ { type: "UpdateApplication",