diff --git a/packages/indexer-common/src/allocations/__tests__/tap.test.ts b/packages/indexer-common/src/allocations/__tests__/tap.test.ts index 9ca8c7bb2..e75f2f793 100644 --- a/packages/indexer-common/src/allocations/__tests__/tap.test.ts +++ b/packages/indexer-common/src/allocations/__tests__/tap.test.ts @@ -4,10 +4,10 @@ import { GraphNode, Network, QueryFeeModels, - QueryResult, TapSubgraphResponse, } from '@graphprotocol/indexer-common' import { + Address, connectDatabase, createLogger, createMetrics, @@ -16,7 +16,7 @@ import { toAddress, } from '@graphprotocol/common-ts' import { testNetworkSpecification } from '../../indexer-management/__tests__/util' -import { Sequelize } from 'sequelize' +import { Op, Sequelize } from 'sequelize' import { utils } from 'ethers' // Make global Jest variables available @@ -72,8 +72,6 @@ const SENDER_ADDRESS_1 = toAddress('ffcf8fdee72ac11b5c542428b35eef5769c409f0') const SENDER_ADDRESS_2 = toAddress('dead47df40c29949a75a6693c77834c00b8ad624') const SENDER_ADDRESS_3 = toAddress('6aea8894b5ab5a36cdc2d8be9290046801dd5fed') -const currentTimestamp = new Date() - // last rav not redeemed const rav = { allocationId: ALLOCATION_ID_1, @@ -94,52 +92,9 @@ const SIGNATURE = Buffer.from( 'hex', ) -const rav_list = [ - rav, - // redeemed rav but non-final - // { - // allocationId: ALLOCATION_ID_2, - // last: true, - // final: false, - // timestampNs: 1709067401177959664n, - // valueAggregate: 20000000000000n, - // signature: SIGNATURE, - // senderAddress: SENDER_ADDRESS_1, - // redeemedAt: new Date(currentTimestamp.getTime() - 1), - // createdAt: currentTimestamp, - // updatedAt: currentTimestamp, - // }, - // - // { - // allocationId: ALLOCATION_ID_2, - // last: true, - // final: false, - // timestampNs: 1709067401177959664n, - // valueAggregate: 20000000000000n, - // signature: SIGNATURE, - // senderAddress: SENDER_ADDRESS_2, - // redeemedAt: currentTimestamp, - // createdAt: currentTimestamp, - // updatedAt: currentTimestamp, - // }, - // - // { - // allocationId: ALLOCATION_ID_3, - // last: true, - // final: false, - // timestampNs: 1709067401177959664n, - // valueAggregate: 20000000000000n, - // signature: SIGNATURE, - // senderAddress: SENDER_ADDRESS_1, - // redeemedAt: new Date(currentTimestamp.getTime() + 1), - // createdAt: currentTimestamp, - // updatedAt: currentTimestamp, - // }, -] - const setupEach = async () => { sequelize = await sequelize.sync({ force: true }) - await queryFeeModels.receiptAggregateVouchers.bulkCreate(rav_list) + await queryFeeModels.receiptAggregateVouchers.create(rav) jest .spyOn(receiptCollector, 'findTransactionsForRavs') @@ -198,32 +153,193 @@ describe('TAP', () => { test('should revert the rav request', async () => { // we have a redeemed non-final rav in our database + const nowSecs = Math.floor(Date.now() / 1000) + // redeemed rav but non-final + const ravList = [ + createLastNonFinalRav( + ALLOCATION_ID_3, + SENDER_ADDRESS_1, + new Date((nowSecs - 1) * 1000), + ), + createLastNonFinalRav(ALLOCATION_ID_3, SENDER_ADDRESS_2, new Date(nowSecs * 1000)), + createLastNonFinalRav( + ALLOCATION_ID_3, + SENDER_ADDRESS_3, + new Date((nowSecs + 1) * 1000), + ), + ] + + await queryFeeModels.receiptAggregateVouchers.bulkCreate(ravList) + // it's not showing on the subgraph on a specific point in time // the timestamp of the subgraph is greater than the receipt id // should revert the rav - // const finalRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ - // where: { last: true, final: true }, - // }) - // - // const allocationIds = [ALLOCATION_ID_1.toString()] - // const blockTimestamp = 1000 - // await receiptCollector['revertRavsRedeemed'](allocationIds, blockTimestamp) + await receiptCollector['revertRavsRedeemed'](ravList, nowSecs - 1) + + let lastRedeemedRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ + where: { + last: true, + final: false, + redeemedAt: { + [Op.ne]: null, + }, + }, + }) + expect(lastRedeemedRavs).toEqual([ + expect.objectContaining(ravList[0]), + expect.objectContaining(ravList[1]), + expect.objectContaining(ravList[2]), + ]) + + await receiptCollector['revertRavsRedeemed'](ravList, nowSecs) + + lastRedeemedRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ + where: { + last: true, + final: false, + redeemedAt: { + [Op.ne]: null, + }, + }, + }) + expect(lastRedeemedRavs).toEqual([ + expect.objectContaining(ravList[1]), + expect.objectContaining(ravList[2]), + ]) + + await receiptCollector['revertRavsRedeemed'](ravList, nowSecs + 1) + + lastRedeemedRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ + where: { + last: true, + final: false, + redeemedAt: { + [Op.ne]: null, + }, + }, + }) + expect(lastRedeemedRavs).toEqual([expect.objectContaining(ravList[2])]) + + await receiptCollector['revertRavsRedeemed'](ravList, nowSecs + 2) + + lastRedeemedRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ + where: { + last: true, + final: false, + redeemedAt: { + [Op.ne]: null, + }, + }, + }) + expect(lastRedeemedRavs).toEqual([]) }) test('should not revert the rav request, allocation_id not in the list ', async () => { // we have a redeemed non-final rav in our database + const nowSecs = Math.floor(Date.now() / 1000) + // redeemed rav but non-final + const ravList = [ + createLastNonFinalRav( + ALLOCATION_ID_3, + SENDER_ADDRESS_1, + new Date((nowSecs - 1) * 1000), + ), + createLastNonFinalRav(ALLOCATION_ID_3, SENDER_ADDRESS_2, new Date(nowSecs * 1000)), + createLastNonFinalRav( + ALLOCATION_ID_3, + SENDER_ADDRESS_3, + new Date((nowSecs + 1) * 1000), + ), + ] + + await queryFeeModels.receiptAggregateVouchers.bulkCreate(ravList) + // it's showing on the subgraph on a specific point in time + await receiptCollector['revertRavsRedeemed']( + [ + { + allocationId: ALLOCATION_ID_1, + senderAddress: SENDER_ADDRESS_1, + }, + ], + nowSecs + 2, + ) // the timestamp of the subgraph is greater than the receipt id // should not revert the rav + + const lastRedeemedRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ + where: { + last: true, + final: false, + redeemedAt: { + [Op.ne]: null, + }, + }, + }) + expect(lastRedeemedRavs).toEqual([ + expect.objectContaining(ravList[0]), + expect.objectContaining(ravList[1]), + expect.objectContaining(ravList[2]), + ]) }) - test('should not revert the rav request, timestamp not greater than the subgraph timestamp', async () => { + test('should mark ravs as final via `markRavsAsFinal`', async () => { // we have a redeemed non-final rav in our database - // it's not showing on the subgraph on a specific point in time - // the timestamp of the subgraph is lower than the receipt id - // should not revert the rav - }) + const nowSecs = Math.floor(Date.now() / 1000) + // redeemed rav but non-final + const default_finality_time = 3600 + const ravList = [ + createLastNonFinalRav( + ALLOCATION_ID_3, + SENDER_ADDRESS_1, + new Date((nowSecs - default_finality_time - 1) * 1000), + ), + createLastNonFinalRav( + ALLOCATION_ID_3, + SENDER_ADDRESS_2, + new Date((nowSecs - default_finality_time) * 1000), + ), + createLastNonFinalRav( + ALLOCATION_ID_3, + SENDER_ADDRESS_3, + new Date((nowSecs - default_finality_time + 1) * 1000), + ), + ] + await queryFeeModels.receiptAggregateVouchers.bulkCreate(ravList) + await receiptCollector['markRavsAsFinal'](nowSecs - 1) + + let finalRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ + where: { last: true, final: true }, + }) + + expect(finalRavs).toEqual([]) + + await receiptCollector['markRavsAsFinal'](nowSecs) + finalRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ + where: { last: true, final: true }, + }) + expect(finalRavs).toEqual([expect.objectContaining({ ...ravList[0], final: true })]) + + await receiptCollector['markRavsAsFinal'](nowSecs + 1) + finalRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ + where: { last: true, final: true }, + }) + expect(finalRavs).toEqual([ + expect.objectContaining({ ...ravList[0], final: true }), + expect.objectContaining({ ...ravList[1], final: true }), + ]) + + await receiptCollector['markRavsAsFinal'](nowSecs + 2) + finalRavs = await queryFeeModels.receiptAggregateVouchers.findAll({ + where: { last: true, final: true }, + }) + expect(finalRavs).toEqual([ + expect.objectContaining({ ...ravList[0], final: true }), + expect.objectContaining({ ...ravList[1], final: true }), + expect.objectContaining({ ...ravList[2], final: true }), + ]) + }) test( 'test ignore final rav', async () => { @@ -337,7 +453,7 @@ describe('TAP', () => { ) test( - 'test mark final rav', + 'test mark final rav via filterAndUpdateRavs', async () => { const date = new Date() const redeemDate = date.setHours(date.getHours() - 2) @@ -389,3 +505,20 @@ describe('TAP', () => { timeout, ) }) + +function createLastNonFinalRav( + allocationId: Address, + senderAddress: Address, + redeemedAt: Date, +) { + return { + allocationId, + last: true, + final: false, + timestampNs: 1709067401177959664n, + valueAggregate: 20000000000000n, + signature: SIGNATURE, + senderAddress, + redeemedAt, + } +} diff --git a/packages/indexer-common/src/allocations/query-fees.ts b/packages/indexer-common/src/allocations/query-fees.ts index ff0d84523..4f0c1b235 100644 --- a/packages/indexer-common/src/allocations/query-fees.ts +++ b/packages/indexer-common/src/allocations/query-fees.ts @@ -693,14 +693,15 @@ export class AllocationReceiptCollector implements ReceiptCollector { return response.data! } - // for every allocation_id of this list that contains the timestamp_ns less than the current + // for every allocation_id of this list that contains the redeemedAt less than the current // subgraph timestamp private async revertRavsRedeemed( ravsNotRedeemed: { allocationId: Address; senderAddress: Address }[], blockTimestampSecs: number, ) { - const SECONDS_TO_NANOSECONDS = 1000000000n - const blockTimestampNs = BigInt(blockTimestampSecs) * SECONDS_TO_NANOSECONDS + if (ravsNotRedeemed.length == 0) { + return + } const query = ` UPDATE scalar_tap_ravs SET redeemed_at = NULL @@ -716,7 +717,7 @@ export class AllocationReceiptCollector implements ReceiptCollector { .replace('0x', '')}'::char(40))`, ) .join(', ')}) - AND timestamp_ns < ${blockTimestampNs} + AND redeemed_at < to_timestamp(${blockTimestampSecs}) ` await this.models.receiptAggregateVouchers.sequelize?.query(query) @@ -728,7 +729,8 @@ export class AllocationReceiptCollector implements ReceiptCollector { const query = ` UPDATE scalar_tap_ravs SET final = TRUE - WHERE last = TRUE AND final = FALSE + WHERE last = TRUE + AND final = FALSE AND redeemed_at IS NOT NULL AND redeemed_at < to_timestamp(${blockTimestampSecs - this.finalityTime}) `