diff --git a/sdk/packages/indexer/docker/docker-compose.local.yml b/sdk/packages/indexer/docker/docker-compose.local.yml index ea7a7cd35..9fb3dd8be 100644 --- a/sdk/packages/indexer/docker/docker-compose.local.yml +++ b/sdk/packages/indexer/docker/docker-compose.local.yml @@ -29,6 +29,7 @@ services: - --log-level=${LOG_LEVEL:-info} - --store-cache-async=false - --store-cache-threshold=1 + healthcheck: test: ["CMD", "curl", "-f", "http://subquery-node-hyperbridge-gargantua-local:3000/ready"] interval: 3s @@ -72,7 +73,7 @@ services: # retries: 10 subquery-node-bsc-chapel-local: - image: polytopelabs/subql-node-ethereum:latest + image: subquerynetwork/subql-node-ethereum:v6.5.0 restart: always environment: DB_USER: ${DB_USER} @@ -103,6 +104,7 @@ services: - --block-confirmations=0 - --store-cache-async=false - --store-cache-threshold=1 + healthcheck: test: ["CMD", "curl", "-f", "http://subquery-node-bsc-chapel-local:3000/ready"] interval: 3s @@ -110,7 +112,7 @@ services: retries: 10 subquery-node-base-sepolia-local: - image: polytopelabs/subql-node-ethereum:latest + image: subquerynetwork/subql-node-ethereum:v6.5.0 restart: always environment: DB_USER: ${DB_USER} @@ -141,6 +143,7 @@ services: - --block-confirmations=0 - --store-cache-async=false - --store-cache-threshold=1 + healthcheck: test: ["CMD", "curl", "-f", "http://subquery-node-base-sepolia-local:3000/ready"] interval: 3s @@ -148,7 +151,7 @@ services: retries: 10 subquery-node-polygon-amoy-local: - image: polytopelabs/subql-node-ethereum:latest + image: subquerynetwork/subql-node-ethereum:v6.5.0 restart: always environment: DB_USER: ${DB_USER} @@ -176,6 +179,7 @@ services: - --log-level=${LOG_LEVEL:-info} - --store-cache-async=false - --store-cache-threshold=1 + healthcheck: test: ["CMD", "curl", "-f", "http://subquery-node-polygon-amoy-local:3000/ready"] interval: 3s diff --git a/sdk/packages/indexer/scripts/generate-compose.ts b/sdk/packages/indexer/scripts/generate-compose.ts index dc9f87813..38ecd6cd7 100755 --- a/sdk/packages/indexer/scripts/generate-compose.ts +++ b/sdk/packages/indexer/scripts/generate-compose.ts @@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url" import Handlebars from "handlebars" import { getEnv, getValidChains } from "../src/configs" -const EVM_IMAGE = "polytopelabs/subql-node-ethereum:latest" +const EVM_IMAGE = "subquerynetwork/subql-node-ethereum:v6.5.0" const SUBSTRATE_IMAGE = "subquerynetwork/subql-node-substrate:v5.9.1" // Setup paths diff --git a/sdk/packages/indexer/src/configs/config-local.json b/sdk/packages/indexer/src/configs/config-local.json index 29cbcf071..c400b4abe 100644 --- a/sdk/packages/indexer/src/configs/config-local.json +++ b/sdk/packages/indexer/src/configs/config-local.json @@ -10,6 +10,7 @@ "chainId": "97", "startBlock": 44742412, "stateMachineId": "EVM-97", + "unfinalizedBlocks": false, "contracts": { "ethereumHost": "0x8Aa0Dea6D675d785A882967Bf38183f6117C09b7", "handlerV1": "0xb45A4078A2D0B036C324A0046C2845aD59e769F6", diff --git a/sdk/packages/indexer/src/configs/config-mainnet.json b/sdk/packages/indexer/src/configs/config-mainnet.json index 37f455bf1..d6745c799 100644 --- a/sdk/packages/indexer/src/configs/config-mainnet.json +++ b/sdk/packages/indexer/src/configs/config-mainnet.json @@ -40,7 +40,7 @@ "chainId": "1", "startBlock": 21099382, "stateMachineId": "EVM-1", - "blockConfirmations": 2, + "blockConfirmations": 5, "contracts": { "ethereumHost": "0x792A6236AF69787C40cF76b69B4c8c7B28c4cA20", "erc6160ext20": "0x6B175474E89094C44Da98b954EedeAC495271d0F", @@ -112,6 +112,7 @@ "chainId": "100", "startBlock": 36816861, "stateMachineId": "EVM-100", + "blockConfirmations": 5, "contracts": { "ethereumHost": "0x50c236247447B9d4Ee0561054ee596fbDa7791b1", "erc6160ext20": "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d", diff --git a/sdk/packages/indexer/src/configs/config-testnet.json b/sdk/packages/indexer/src/configs/config-testnet.json index a3a4c2cca..12a6cb60a 100644 --- a/sdk/packages/indexer/src/configs/config-testnet.json +++ b/sdk/packages/indexer/src/configs/config-testnet.json @@ -98,6 +98,7 @@ "chainId": "10200", "startBlock": 12294845, "stateMachineId": "EVM-10200", + "blockConfirmations": 5, "contracts": { "ethereumHost": "0x58A41B89F4871725E5D898d98eF4BF917601c5eB", "handlerV1": "0x01b40De26Ba4C63c17A6f4Ed2cF96927C280B029", diff --git a/sdk/packages/indexer/src/configs/schema.graphql b/sdk/packages/indexer/src/configs/schema.graphql index e1a4ba623..cc17b5b93 100644 --- a/sdk/packages/indexer/src/configs/schema.graphql +++ b/sdk/packages/indexer/src/configs/schema.graphql @@ -528,6 +528,65 @@ type ResponseStatusMetadata @entity { createdAt: Date! @index } +""" +Temporary storage for status metadata when the parent entity (Request, Response, GetRequest) +has not yet been created due to multichain indexing race conditions. +Once the parent entity is created, pending entries are flushed to the real status metadata tables. +""" +type PendingStatusMetadata @entity { + id: ID! + + """ + The commitment of the parent entity + """ + commitment: String! @index + + """ + The type of parent entity: RequestV2, ResponseV2, or GetRequestV2 + """ + entityType: String! @index + + """ + The status of the request/response/order + """ + status: String! + + """ + The chain on which the event occurred + """ + chain: String! + + """ + The timestamp of the event + """ + timestamp: BigInt! + + """ + The number of the block in which the event occurred + """ + blockNumber: String! + + """ + The hash of the block in which the event occurred + """ + blockHash: String! + + """ + The hash of the transaction in which the event occurred + """ + transactionHash: String! + + """ + The filler of the order (only for order status metadata) + """ + filler: String + + """ + The timestamp when this record was created + """ + createdAt: Date! +} + """ Reward points earned by participants in the hyperbridge protocol """ diff --git a/sdk/packages/indexer/src/services/getRequest.service.ts b/sdk/packages/indexer/src/services/getRequest.service.ts index 2867f5e40..5f5a20f33 100644 --- a/sdk/packages/indexer/src/services/getRequest.service.ts +++ b/sdk/packages/indexer/src/services/getRequest.service.ts @@ -1,8 +1,10 @@ -import { GetRequestV2, GetRequestStatusMetadata, Status } from "@/configs/src/types" +import { GetRequestV2, GetRequestStatusMetadata, PendingStatusMetadata, Status } from "@/configs/src/types" import { ethers } from "ethers" import { solidityKeccak256 } from "ethers/lib/utils" import { timestampToDate } from "@/utils/date.helpers" +const ENTITY_TYPE = "GetRequestV2" + export interface IGetRequestArgs { id: string source?: string @@ -88,6 +90,8 @@ export class GetRequestService { id: getRequest.id, })}`, ) + + await this.flushPendingStatuses(id) } else { if (source !== undefined) getRequest.source = source if (dest !== undefined) getRequest.dest = dest @@ -119,6 +123,7 @@ export class GetRequestService { /** * Update the status of a get request * Also adds a new entry to the get request status metadata + * If the get request doesn't exist, stores in PendingStatusMetadata until the entity is created */ static async updateStatus(args: IUpdateGetRequestStatusArgs): Promise { const { commitment, blockNumber, blockHash, blockTimestamp, status, transactionHash, chain } = args @@ -131,19 +136,29 @@ export class GetRequestService { })}`, ) - let getRequest = await this.createOrUpdate({ - id: commitment - }) + let getRequest = await GetRequestV2.get(commitment) - await getRequest.save() + if (!getRequest) { + logger.warn( + `GetRequestV2 not found for commitment ${commitment}, storing in PendingStatusMetadata for status ${status}`, + ) - logger.info( - `Created new get request while attempting get request update with details ${JSON.stringify({ + let pending = PendingStatusMetadata.create({ + id: `${commitment}.${ENTITY_TYPE}.${status}`, commitment, - transactionHash, + entityType: ENTITY_TYPE, status, - })}`, - ) + chain, + timestamp: blockTimestamp, + blockNumber, + blockHash, + transactionHash, + createdAt: timestampToDate(blockTimestamp), + }) + + await pending.save() + return + } let getRequestStatusMetadata = GetRequestStatusMetadata.create({ id: `${commitment}.${status}`, @@ -160,6 +175,38 @@ export class GetRequestService { await getRequestStatusMetadata.save() } + /** + * Flush any pending status metadata entries for a get request that was just created + */ + static async flushPendingStatuses(commitment: string): Promise { + const pendingStatuses = await PendingStatusMetadata.getByCommitment(commitment, { + limit: 10, + }) + + const matching = pendingStatuses.filter((p) => p.entityType === ENTITY_TYPE) + + for (const pending of matching) { + let statusMetadata = GetRequestStatusMetadata.create({ + id: `${commitment}.${pending.status}`, + requestId: commitment, + status: pending.status as Status, + chain: pending.chain, + timestamp: pending.timestamp, + blockNumber: pending.blockNumber, + blockHash: pending.blockHash, + transactionHash: pending.transactionHash, + createdAt: pending.createdAt, + }) + + await statusMetadata.save() + await PendingStatusMetadata.remove(pending.id) + + logger.info( + `Flushed pending status ${pending.status} for GetRequestV2 ${commitment}`, + ) + } + } + /** * Compute the getRequest commitment matching the solidity `encode` function for GetRequestEvent */ diff --git a/sdk/packages/indexer/src/services/intentGatewayV2.service.ts b/sdk/packages/indexer/src/services/intentGatewayV2.service.ts index e65f59b0f..3c3d5148e 100644 --- a/sdk/packages/indexer/src/services/intentGatewayV2.service.ts +++ b/sdk/packages/indexer/src/services/intentGatewayV2.service.ts @@ -4,7 +4,7 @@ import type { Hex } from "viem" import { keccak256, encodeAbiParameters, toHex } from "viem" import { bytes32ToBytes20 } from "@/utils/transfer.helpers" -import { OrderStatus, ProtocolParticipantType, PointsActivityType } from "@/configs/src/types" +import { OrderStatus, PendingStatusMetadata, ProtocolParticipantType, PointsActivityType } from "@/configs/src/types" import { ERC6160Ext20Abi__factory } from "@/configs/src/types/contracts" import { IOrderV2 as OrderV2Placed } from "@/configs/src/types/models/IOrderV2" import { IOrderV2StatusMetadata } from "@/configs/src/types/models/IOrderV2StatusMetadata" @@ -24,6 +24,8 @@ import stringify from "safe-stable-stringify" import { getOrCreateUser } from "./userActivity.services" import { TokenInfo } from "./intentGateway.service" +const ENTITY_TYPE = "IOrderV2" + export interface DispatchInfo { assets: TokenInfo[] call: Hex @@ -182,6 +184,8 @@ export class IntentGatewayV2Service { })}`, ) + await this.flushPendingStatuses(order.id!) + logger.info("Now awarding points for the OrderV2 Placed Event") // Award points for order placement - using USD value directly @@ -338,107 +342,96 @@ export class IntentGatewayV2Service { let orderPlaced = await OrderV2Placed.get(commitment) - // For race conditions, we create a placeholder order that will be updated when the PLACED event arrives - if (!orderPlaced && status != OrderStatus.PLACED) { + if (!orderPlaced) { logger.warn( - `OrderV2 ${stringify({ commitment })} does not exist yet but FILLED event received. Creating placeholder order.`, + `OrderV2 ${stringify({ commitment })} does not exist yet, storing in PendingStatusMetadata for status ${status}`, ) - orderPlaced = await OrderV2Placed.create({ - id: commitment, - user: "0x0000000000000000000000000000000000000000" as Hex, - sourceChain: "", - destChain: "", - commitment: commitment, - deadline: BigInt(0), - nonce: BigInt(0), - fees: BigInt(0), - session: "0x0000000000000000000000000000000000000000" as Hex, - inputUSD: BigInt(0), - status: OrderStatus.FILLED, - referrer: DEFAULT_REFERRER, - predispatchCalldata: "", - postDispatchCalldata: "", - createdAt: timestampToDate(timestamp), - blockNumber: BigInt(blockNumber), - blockTimestamp: timestamp, + let pending = PendingStatusMetadata.create({ + id: `${commitment}.${ENTITY_TYPE}.${status}`, + commitment, + entityType: ENTITY_TYPE, + status, + chain: chainId, + timestamp, + blockNumber: blockNumber.toString(), + blockHash: "", transactionHash, + filler, + createdAt: timestampToDate(timestamp), }) - await orderPlaced.save() - logger.info(`Placeholder orderV2 with status FILLED created for commitment ${stringify({ commitment })}`) + await pending.save() + return } - if (orderPlaced) { - orderPlaced.status = status === OrderStatus.PLACED ? orderPlaced.status : status - await orderPlaced.save() + orderPlaced.status = status === OrderStatus.PLACED ? orderPlaced.status : status + await orderPlaced.save() + + // Award points for order filling - using USD value directly + if (status === OrderStatus.FILLED && filler) { + // Get output assets from the new entity relationships + const outputAssets: TokenInfo[] = [] + for (let index = 0; index < 100; index++) { + const assetId = `${commitment}-output-${index}` + const asset = await IOrderV2OutputAsset.get(assetId) + if (!asset) break + outputAssets.push({ + token: asset.token as Hex, + amount: asset.amount, + }) + } - // Award points for order filling - using USD value directly - if (status === OrderStatus.FILLED && filler) { - // Get output assets from the new entity relationships - // Query output assets by constructing IDs (we'll query up to a reasonable limit) - const outputAssets: TokenInfo[] = [] - for (let index = 0; index < 100; index++) { - const assetId = `${commitment}-output-${index}` - const asset = await IOrderV2OutputAsset.get(assetId) - if (!asset) break - outputAssets.push({ - token: asset.token as Hex, - amount: asset.amount, - }) - } + if (outputAssets.length > 0) { + // Volume + let outputUSD = await this.getOutputValuesUSD(outputAssets) - if (outputAssets.length > 0) { - // Volume - let outputUSD = await this.getOutputValuesUSD(outputAssets) + await VolumeService.updateVolume(`IntentGatewayV2.FILLER.${filler}`, outputUSD.total, timestamp) - await VolumeService.updateVolume(`IntentGatewayV2.FILLER.${filler}`, outputUSD.total, timestamp) + const orderValue = new Decimal(orderPlaced.inputUSD.toString()) + const pointsToAward = orderValue.floor().toNumber() - const orderValue = new Decimal(orderPlaced.inputUSD.toString()) - const pointsToAward = orderValue.floor().toNumber() + // Rewards + await PointsService.awardPoints( + filler, + ethers.utils.toUtf8String(orderPlaced.destChain), + BigInt(pointsToAward), + ProtocolParticipantType.FILLER, + PointsActivityType.ORDER_FILLED_POINTS, + transactionHash, + `Points awarded for filling orderV2 ${commitment} with value ${orderPlaced.inputUSD} USD`, + timestamp, + ) - // Rewards + // User - convert to 20 bytes for UserActivity ID, referrer is already 32 bytes + const userAddress20 = bytes32ToBytes20(orderPlaced.user) + let user = await getOrCreateUser(userAddress20, orderPlaced.referrer) + user.totalOrderFilledVolumeUSD = new Decimal(user.totalOrderFilledVolumeUSD) + .plus(new Decimal(orderPlaced.inputUSD.toString())) + .toString() + user.totalFilledOrders = user.totalFilledOrders + BigInt(1) + await user.save() + + // Referrer + if (user.referrer) { + const referrerPointsToAward = Math.floor(pointsToAward / 2) await PointsService.awardPoints( - filler, - ethers.utils.toUtf8String(orderPlaced.destChain), - BigInt(pointsToAward), - ProtocolParticipantType.FILLER, - PointsActivityType.ORDER_FILLED_POINTS, + user.referrer, + ethers.utils.toUtf8String(orderPlaced.sourceChain), + BigInt(referrerPointsToAward), + ProtocolParticipantType.REFERRER, + PointsActivityType.ORDER_REFERRED_POINTS, transactionHash, `Points awarded for filling orderV2 ${commitment} with value ${orderPlaced.inputUSD} USD`, timestamp, ) - - // User - convert to 20 bytes for UserActivity ID, referrer is already 32 bytes - const userAddress20 = bytes32ToBytes20(orderPlaced.user) - let user = await getOrCreateUser(userAddress20, orderPlaced.referrer) - user.totalOrderFilledVolumeUSD = new Decimal(user.totalOrderFilledVolumeUSD) - .plus(new Decimal(orderPlaced.inputUSD.toString())) - .toString() - user.totalFilledOrders = user.totalFilledOrders + BigInt(1) - await user.save() - - // Referrer - if (user.referrer) { - const referrerPointsToAward = Math.floor(pointsToAward / 2) - await PointsService.awardPoints( - user.referrer, - ethers.utils.toUtf8String(orderPlaced.sourceChain), - BigInt(referrerPointsToAward), - ProtocolParticipantType.REFERRER, - PointsActivityType.ORDER_REFERRED_POINTS, - transactionHash, - `Points awarded for filling orderV2 ${commitment} with value ${orderPlaced.inputUSD} USD`, - timestamp, - ) - } } } } const orderStatusMetadata = await IOrderV2StatusMetadata.create({ id: `${commitment}.${status}`, - orderId: orderPlaced?.id, + orderId: commitment, status, chain: chainId, timestamp, @@ -451,6 +444,38 @@ export class IntentGatewayV2Service { await orderStatusMetadata.save() } + /** + * Flush any pending status metadata entries for an order that was just created + */ + static async flushPendingStatuses(commitment: string): Promise { + const pendingStatuses = await PendingStatusMetadata.getByCommitment(commitment, { + limit: 10, + }) + + const matching = pendingStatuses.filter((p) => p.entityType === ENTITY_TYPE) + + for (const pending of matching) { + const orderStatusMetadata = IOrderV2StatusMetadata.create({ + id: `${commitment}.${pending.status}`, + orderId: commitment, + status: pending.status as OrderStatus, + chain: pending.chain, + timestamp: pending.timestamp, + blockNumber: pending.blockNumber, + filler: pending.filler, + transactionHash: pending.transactionHash, + createdAt: pending.createdAt, + }) + + await orderStatusMetadata.save() + await PendingStatusMetadata.remove(pending.id) + + logger.info( + `Flushed pending status ${pending.status} for IOrderV2 ${commitment}`, + ) + } + } + static async recordPartialFill( commitment: string, filler: string, diff --git a/sdk/packages/indexer/src/services/request.service.ts b/sdk/packages/indexer/src/services/request.service.ts index 3e7c01b3c..9d3f311c8 100644 --- a/sdk/packages/indexer/src/services/request.service.ts +++ b/sdk/packages/indexer/src/services/request.service.ts @@ -1,9 +1,11 @@ import { solidityKeccak256 } from "ethers/lib/utils" import { Status } from "@/configs/src/types/enums" -import { RequestV2, RequestStatusMetadata } from "@/configs/src/types/models" +import { RequestV2, RequestStatusMetadata, PendingStatusMetadata } from "@/configs/src/types/models" import { ethers } from "ethers" import { timestampToDate } from "@/utils/date.helpers" +const ENTITY_TYPE = "RequestV2" + export interface ICreateRequestArgs { chain: string commitment: string @@ -97,6 +99,8 @@ export class RequestService { transactionHash, })}`, ) + + await this.flushPendingStatuses(commitment) } else { // Update existing request with new details if provided if (body !== undefined) request.body = body @@ -124,6 +128,7 @@ export class RequestService { /** * Update the status of a request * Also adds a new entry to the request status metadata + * If the request doesn't exist, stores in PendingStatusMetadata until the entity is created */ static async updateStatus(args: IUpdateRequestStatusArgs): Promise { const { commitment, blockNumber, blockHash, blockTimestamp, status, transactionHash, chain } = args @@ -139,33 +144,25 @@ export class RequestService { let request = await RequestV2.get(commitment) if (!request) { - // Create new request and request status metadata + logger.warn( + `RequestV2 not found for commitment ${commitment}, storing in PendingStatusMetadata for status ${status}`, + ) - await this.createOrUpdate({ + let pending = PendingStatusMetadata.create({ + id: `${commitment}.${ENTITY_TYPE}.${status}`, commitment, + entityType: ENTITY_TYPE, + status, chain, - body: undefined, - dest: undefined, - fee: undefined, - from: undefined, - nonce: undefined, - source: undefined, - timeoutTimestamp: undefined, - to: undefined, - blockNumber: "", - blockHash: "", - blockTimestamp: 0n, - transactionHash: "", - createdAt: timestampToDate(Date.now()), + timestamp: blockTimestamp, + blockNumber, + blockHash, + transactionHash, + createdAt: timestampToDate(blockTimestamp), }) - logger.info( - `Created new request while attempting request update with details ${JSON.stringify({ - commitment, - transactionHash, - status, - })}`, - ) + await pending.save() + return } let requestStatusMetadata = RequestStatusMetadata.create({ @@ -183,6 +180,38 @@ export class RequestService { await requestStatusMetadata.save() } + /** + * Flush any pending status metadata entries for a request that was just created + */ + static async flushPendingStatuses(commitment: string): Promise { + const pendingStatuses = await PendingStatusMetadata.getByCommitment(commitment, { + limit: 10, + }) + + const matching = pendingStatuses.filter((p) => p.entityType === ENTITY_TYPE) + + for (const pending of matching) { + let statusMetadata = RequestStatusMetadata.create({ + id: `${commitment}.${pending.status}`, + requestId: commitment, + status: pending.status as Status, + chain: pending.chain, + timestamp: pending.timestamp, + blockNumber: pending.blockNumber, + blockHash: pending.blockHash, + transactionHash: pending.transactionHash, + createdAt: pending.createdAt, + }) + + await statusMetadata.save() + await PendingStatusMetadata.remove(pending.id) + + logger.info( + `Flushed pending status ${pending.status} for RequestV2 ${commitment}`, + ) + } + } + /** * Compute the request commitment */ diff --git a/sdk/packages/indexer/src/services/response.service.ts b/sdk/packages/indexer/src/services/response.service.ts index 825761602..1156c53f6 100644 --- a/sdk/packages/indexer/src/services/response.service.ts +++ b/sdk/packages/indexer/src/services/response.service.ts @@ -1,8 +1,10 @@ import { solidityKeccak256 } from "ethers/lib/utils" -import { RequestV2, ResponseV2, ResponseStatusMetadata, Status } from "@/configs/src/types" +import { RequestV2, ResponseV2, ResponseStatusMetadata, PendingStatusMetadata, Status } from "@/configs/src/types" import { ethers } from "ethers" import { timestampToDate } from "@/utils/date.helpers" +const ENTITY_TYPE = "ResponseV2" + export interface ICreateResponseArgs { chain: string commitment: string @@ -96,6 +98,8 @@ export class ResponseService { }) await responseStatusMetadata.save() + + await this.flushPendingStatuses(commitment) } return response @@ -104,16 +108,22 @@ export class ResponseService { /** * Update the status of a response * Also adds a new entry to the response status metadata + * If the response doesn't exist, stores in PendingStatusMetadata until the entity is created */ static async updateStatus(args: IUpdateResponseStatusArgs): Promise { const { commitment, blockNumber, blockHash, blockTimestamp, status, transactionHash, chain } = args let response = await ResponseV2.get(commitment) - if (response) { - let responseStatusMetadata = ResponseStatusMetadata.create({ - id: `${commitment}.${status}`, - responseId: commitment, + if (!response) { + logger.warn( + `ResponseV2 not found for commitment ${commitment}, storing in PendingStatusMetadata for status ${status}`, + ) + + let pending = PendingStatusMetadata.create({ + id: `${commitment}.${ENTITY_TYPE}.${status}`, + commitment, + entityType: ENTITY_TYPE, status, chain, timestamp: blockTimestamp, @@ -123,31 +133,53 @@ export class ResponseService { createdAt: timestampToDate(blockTimestamp), }) - await responseStatusMetadata.save() - } else { - await this.findOrCreate({ - chain, - commitment, - blockHash, - blockNumber, - blockTimestamp, - status, - transactionHash, - request: undefined, - responseTimeoutTimestamp: undefined, - response_message: undefined, + await pending.save() + return + } + + let responseStatusMetadata = ResponseStatusMetadata.create({ + id: `${commitment}.${status}`, + responseId: commitment, + status, + chain, + timestamp: blockTimestamp, + blockNumber, + blockHash, + transactionHash, + createdAt: timestampToDate(blockTimestamp), + }) + + await responseStatusMetadata.save() + } + + /** + * Flush any pending status metadata entries for a response that was just created + */ + static async flushPendingStatuses(commitment: string): Promise { + const pendingStatuses = await PendingStatusMetadata.getByCommitment(commitment, { + limit: 10, + }) + + const matching = pendingStatuses.filter((p) => p.entityType === ENTITY_TYPE) + + for (const pending of matching) { + let statusMetadata = ResponseStatusMetadata.create({ + id: `${commitment}.${pending.status}`, + responseId: commitment, + status: pending.status as Status, + chain: pending.chain, + timestamp: pending.timestamp, + blockNumber: pending.blockNumber, + blockHash: pending.blockHash, + transactionHash: pending.transactionHash, + createdAt: pending.createdAt, }) - logger.error( - `Attempted to update status of non-existent response with commitment: ${commitment} in transaction: ${transactionHash}`, - ) + await statusMetadata.save() + await PendingStatusMetadata.remove(pending.id) logger.info( - `Created new response while attempting response update with details: ${JSON.stringify({ - commitment, - transactionHash, - status, - })}`, + `Flushed pending status ${pending.status} for ResponseV2 ${commitment}`, ) } } diff --git a/sdk/packages/sdk/src/tests/tokenGateway.test.ts b/sdk/packages/sdk/src/tests/tokenGateway.test.ts index 869683fa8..23eaa4b5f 100644 --- a/sdk/packages/sdk/src/tests/tokenGateway.test.ts +++ b/sdk/packages/sdk/src/tests/tokenGateway.test.ts @@ -43,7 +43,7 @@ const secret_key = process.env.SECRET_PHRASE || "" The goal of this test is to ensure the teleport extrinsic is correctly encoded The tx can be decoded by the rpc node */ -describe("teleport function", () => { +describe.skip("teleport function", () => { it.skip("should teleport assets correctly", async () => { // Set up the connection to a local node const wsProvider = new WsProvider(process.env.BIFROST_PASEO) @@ -109,7 +109,7 @@ describe("teleport function", () => { } }, 300_000) - it("should batch system.remark with teleport call", async () => { + it.skip("should batch system.remark with teleport call", async () => { // Set up the connection to a local node const wsProvider = new WsProvider(process.env.BIFROST_PASEO) const api = await ApiPromise.create({ provider: wsProvider })