From 5349d7b44367b9121f876de779103c88ce603fb5 Mon Sep 17 00:00:00 2001 From: Eduard Dumea Date: Wed, 8 Oct 2025 17:23:57 +0300 Subject: [PATCH] fix: handle multi event transaction ordering --- src/lib/contract/shared.ts | 65 ++++++++++++++++++- src/lib/contract/shared/index.ts | 3 + src/lib/contract/v3/data-registry.ts | 8 +-- src/lib/contract/v4/data-refiner-registry.ts | 6 +- src/lib/contract/v4/query-engine.ts | 13 +--- src/lib/contract/v5/dlp-performance.ts | 17 ++--- .../contract/v6/data-portability-servers.ts | 31 +++------ src/lib/contract/v6/data-refiner-registry.ts | 6 +- 8 files changed, 93 insertions(+), 56 deletions(-) diff --git a/src/lib/contract/shared.ts b/src/lib/contract/shared.ts index 7ba59b9..5d11a00 100644 --- a/src/lib/contract/shared.ts +++ b/src/lib/contract/shared.ts @@ -1,6 +1,6 @@ import { Address, BigDecimal, BigInt, Bytes } from "@graphprotocol/graph-ts"; import { REFERENCE_TOKEN } from "../../uniswap/common/chain"; -import { Dlp, User, Token, Grantee } from "../../../generated/schema"; +import { Dlp, User, Token, Grantee, Server, Epoch, Refiner } from "../../../generated/schema"; import { getOrCreateTotals, getTotalsDlpId } from "../entity/totals"; export function getOrCreateUser(userId: string): User { @@ -47,6 +47,69 @@ export function getOrCreateGrantee(granteeId: string): Grantee { return grantee; } +export function getOrCreateServer(serverId: string): Server { + let server = Server.load(serverId); + if (server == null) { + // Create a placeholder server that will be populated by ServerRegistered event + server = new Server(serverId); + + // Initialize with placeholder values + // These will be overwritten when the actual ServerRegistered event is processed + server.owner = ""; + server.serverAddress = Bytes.empty(); + server.publicKey = Bytes.empty(); + server.url = ""; + server.registeredAtBlock = BigInt.zero(); + server.registeredAtTimestamp = BigInt.zero(); + server.transactionHash = Bytes.empty(); + + server.save(); + } + return server; +} + +export function getOrCreateEpoch(epochId: string): Epoch { + let epoch = Epoch.load(epochId); + if (epoch == null) { + // Create a placeholder epoch that will be populated by EpochCreated event + epoch = new Epoch(epochId); + + // Initialize with placeholder values + // These will be overwritten when the actual EpochCreated event is processed + epoch.startBlock = BigInt.zero(); + epoch.endBlock = BigInt.zero(); + epoch.reward = BigInt.zero(); + epoch.createdAt = BigInt.zero(); + epoch.createdTxHash = Bytes.empty(); + epoch.createdAtBlock = BigInt.zero(); + epoch.logIndex = BigInt.zero(); + epoch.isFinalized = false; + epoch.dlpIds = []; + + epoch.save(); + } + return epoch; +} + +export function getOrCreateRefiner(refinerId: string): Refiner { + let refiner = Refiner.load(refinerId); + if (refiner == null) { + // Create a placeholder refiner that will be populated by RefinerRegistered event + refiner = new Refiner(refinerId); + + // Initialize with placeholder values + // These will be overwritten when the actual RefinerRegistered event is processed + refiner.dlp = ""; + refiner.owner = Bytes.empty(); + refiner.name = ""; + refiner.schemaDefinitionUrl = ""; + refiner.refinementInstructionUrl = ""; + + refiner.save(); + } + return refiner; +} + export function getTokenAmountInVana( tokenAddress: Bytes, amount: BigInt, diff --git a/src/lib/contract/shared/index.ts b/src/lib/contract/shared/index.ts index 584a59b..45141f4 100644 --- a/src/lib/contract/shared/index.ts +++ b/src/lib/contract/shared/index.ts @@ -12,5 +12,8 @@ export { getOrCreateUser, getOrCreateDlp, getOrCreateGrantee, + getOrCreateServer, + getOrCreateEpoch, + getOrCreateRefiner, getTokenAmountInVana, } from "../shared"; diff --git a/src/lib/contract/v3/data-registry.ts b/src/lib/contract/v3/data-registry.ts index 132e6eb..fad48bd 100644 --- a/src/lib/contract/v3/data-registry.ts +++ b/src/lib/contract/v3/data-registry.ts @@ -13,6 +13,7 @@ import { getDlpEpochUserId } from "../../entity/dlpEpochUser"; import { getOrCreateDlp, getOrCreateUser, + getOrCreateEpoch, createFileFromEvent, logDataRegistryEvent, createDataRegistryProof, @@ -96,12 +97,9 @@ function updateDlpEpochUser( userId: string, dlpId: string, ): void { - const epoch = Epoch.load(epochId); + // Get or create epoch and dlp (handles race condition when events are processed out of order) + const epoch = getOrCreateEpoch(epochId); const dlp = getOrCreateDlp(dlpId); - if (!epoch) { - log.error("No epoch found for ID {}", [epochId]); - return; - } if (!dlp.verificationBlockNumber) { return; diff --git a/src/lib/contract/v4/data-refiner-registry.ts b/src/lib/contract/v4/data-refiner-registry.ts index f2a1311..b77c33f 100644 --- a/src/lib/contract/v4/data-refiner-registry.ts +++ b/src/lib/contract/v4/data-refiner-registry.ts @@ -1,7 +1,7 @@ import { log } from "@graphprotocol/graph-ts"; import { Refiner } from "../../../../generated/schema"; import { RefinerAdded } from "../../../../generated/DataRefinerRegistryImplementation/DataRefinerRegistryImplementation"; -import { getOrCreateUser } from "../shared"; +import { getOrCreateUser, getOrCreateRefiner } from "../shared"; export function handleRefinerAddedV4(event: RefinerAdded): void { log.info("Handling RefinerAdded with transaction hash: {}", [ @@ -11,8 +11,8 @@ export function handleRefinerAddedV4(event: RefinerAdded): void { const ownerAddress = event.transaction.from; getOrCreateUser(ownerAddress.toHex()); - // Create new Refiner entity - const refiner = new Refiner(event.params.refinerId.toString()); + // Get or create Refiner entity (may already exist if PaymentReceived was processed first) + const refiner = getOrCreateRefiner(event.params.refinerId.toString()); refiner.dlp = event.params.dlpId.toString(); refiner.owner = ownerAddress; refiner.name = event.params.name; diff --git a/src/lib/contract/v4/query-engine.ts b/src/lib/contract/v4/query-engine.ts index bbc9e17..d3b5b4e 100644 --- a/src/lib/contract/v4/query-engine.ts +++ b/src/lib/contract/v4/query-engine.ts @@ -6,22 +6,15 @@ import { getOrCreateTotalsGlobal, getTotalsDlpId, } from "../../entity/totals"; -import { getTokenAmountInVana } from "../shared"; +import { getTokenAmountInVana, getOrCreateRefiner } from "../shared"; export function handlePaymentReceived(event: PaymentReceivedEvent): void { // Create unique ID from transaction hash and log index const id = `${event.transaction.hash.toHexString()}-${event.logIndex.toString()}`; - // Check if refiner exists + // Get or create refiner (handles race condition when events are processed out of order) const refinerId = event.params.refinerId.toString(); - const refiner = Refiner.load(refinerId); - if (!refiner) { - log.error( - "Payment received for unknown refiner: {}. TX: {}. This payment will not be tracked!", - [refinerId, event.transaction.hash.toHexString()], - ); - return; - } + const refiner = getOrCreateRefiner(refinerId); // Create new PaymentReceived entity const payment = new PaymentReceived(id); diff --git a/src/lib/contract/v5/dlp-performance.ts b/src/lib/contract/v5/dlp-performance.ts index 6dd40b3..edff7a9 100644 --- a/src/lib/contract/v5/dlp-performance.ts +++ b/src/lib/contract/v5/dlp-performance.ts @@ -2,6 +2,7 @@ import { BigInt as GraphBigInt, log } from "@graphprotocol/graph-ts"; import { EpochDlpPerformancesSaved as EpochDlpPerformancesSavedEvent } from "../../../../generated/DLPPerformanceImplementationV5/DLPPerformanceImplementationV5"; import { Dlp, Epoch, DlpPerformance } from "../../../../generated/schema"; +import { getOrCreateDlp, getOrCreateEpoch } from "../shared"; export function handleEpochDlpPerformancesSavedV5( event: EpochDlpPerformancesSavedEvent, @@ -13,19 +14,9 @@ export function handleEpochDlpPerformancesSavedV5( const epochId = event.params.epochId.toString(); const dlpId = event.params.dlpId.toString(); - // Load the epoch and dlp - const epoch = Epoch.load(epochId); - const dlp = Dlp.load(dlpId); - - if (epoch == null) { - log.error("Epoch not found for performance metrics: {}", [epochId]); - return; - } - - if (dlp == null) { - log.error("DLP not found for performance metrics: {}", [dlpId]); - return; - } + // Get or create epoch and dlp (handles race condition when events are processed out of order) + const epoch = getOrCreateEpoch(epochId); + const dlp = getOrCreateDlp(dlpId); // Create unique performance ID const performanceId = `${epochId}-${dlpId}`; diff --git a/src/lib/contract/v6/data-portability-servers.ts b/src/lib/contract/v6/data-portability-servers.ts index 6d41b57..9cf3d53 100644 --- a/src/lib/contract/v6/data-portability-servers.ts +++ b/src/lib/contract/v6/data-portability-servers.ts @@ -6,7 +6,7 @@ import { ServerUntrusted, } from "../../../../generated/DataPortabilityServersImplementation/DataPortabilityServersImplementation"; import { Server, UserServer } from "../../../../generated/schema"; -import { getOrCreateUser } from "../shared"; +import { getOrCreateUser, getOrCreateServer } from "../shared"; export function handleServerRegistered(event: ServerRegistered): void { log.info( @@ -20,11 +20,9 @@ export function handleServerRegistered(event: ServerRegistered): void { ); const serverId = event.params.serverId.toString(); - let server = Server.load(serverId); - if (server == null) { - server = new Server(serverId); - } + // Get or create server (may already exist if ServerTrusted/ServerUpdated was processed first) + const server = getOrCreateServer(serverId); // Get or create the owner user const owner = getOrCreateUser(event.params.owner.toHex()); @@ -47,17 +45,12 @@ export function handleServerUpdated(event: ServerUpdated): void { ]); const serverId = event.params.serverId.toString(); - const server = Server.load(serverId); - if (server) { - server.url = event.params.url; - server.save(); - } else { - log.warning( - "Received update event for a server not found in subgraph: {}", - [serverId], - ); - } + // Get or create server (handles race condition when ServerUpdated is processed before ServerRegistered) + const server = getOrCreateServer(serverId); + + server.url = event.params.url; + server.save(); } export function handleServerTrusted(event: ServerTrusted): void { @@ -70,12 +63,8 @@ export function handleServerTrusted(event: ServerTrusted): void { const serverId = event.params.serverId.toString(); const compositeId = `${user.id}-${serverId}`; - // Ensure server exists - const server = Server.load(serverId); - if (server == null) { - log.error("Server with id {} not found for trust relationship", [serverId]); - return; - } + // Get or create server (handles race condition when ServerTrusted is processed before ServerRegistered) + const server = getOrCreateServer(serverId); let userServer = UserServer.load(compositeId); if (userServer == null) { diff --git a/src/lib/contract/v6/data-refiner-registry.ts b/src/lib/contract/v6/data-refiner-registry.ts index e88710c..be84148 100644 --- a/src/lib/contract/v6/data-refiner-registry.ts +++ b/src/lib/contract/v6/data-refiner-registry.ts @@ -4,7 +4,7 @@ import { RefinerAdded, SchemaAdded, } from "../../../../generated/DataRefinerRegistryImplementationV6/DataRefinerRegistryImplementationV6"; -import { getOrCreateUser } from "../shared"; +import { getOrCreateUser, getOrCreateRefiner } from "../shared"; export function handleSchemaAdded(event: SchemaAdded): void { log.info("Handling SchemaAdded with transaction hash: {} and schemaId: {}", [ @@ -36,8 +36,8 @@ export function handleRefinerAddedV6(event: RefinerAdded): void { const ownerAddress = event.transaction.from; getOrCreateUser(ownerAddress.toHex()); - // Create new Refiner entity - const refiner = new Refiner(event.params.refinerId.toString()); + // Get or create Refiner entity (may already exist if PaymentReceived was processed first) + const refiner = getOrCreateRefiner(event.params.refinerId.toString()); refiner.dlp = event.params.dlpId.toString(); refiner.owner = ownerAddress; refiner.name = event.params.name;