Skip to content

Commit 9aeeb58

Browse files
committed
feat: migrate to new indexing payments subgraph
1 parent 861cddf commit 9aeeb58

10 files changed

Lines changed: 229 additions & 24 deletions

File tree

packages/indexer-agent/src/__tests__/agent.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
Agent,
3+
addIndexingPaymentsSubgraphToTarget,
34
convertSubgraphBasedRulesToDeploymentBased,
45
consolidateAllocationDecisions,
56
resolveTargetDeployments,
@@ -472,3 +473,107 @@ describe('reconcileDeploymentAllocationAction', () => {
472473
expect(operator.presentPOIForAllocations).not.toHaveBeenCalled()
473474
})
474475
})
476+
477+
describe('addIndexingPaymentsSubgraphToTarget function', () => {
478+
const paymentsDeployment = new SubgraphDeploymentID(
479+
'QmddSbatCN1XmufoBm1bBwPx4L3FtuMAMHUNNBSPzrgL2a',
480+
)
481+
482+
it('pushes the deployment when enableDips is true and deployment is defined', () => {
483+
const target: SubgraphDeploymentID[] = []
484+
addIndexingPaymentsSubgraphToTarget(true, paymentsDeployment, target)
485+
expect(target.map(d => d.bytes32)).toContain(paymentsDeployment.bytes32)
486+
})
487+
488+
it('does nothing when enableDips is false', () => {
489+
const target: SubgraphDeploymentID[] = []
490+
addIndexingPaymentsSubgraphToTarget(false, paymentsDeployment, target)
491+
expect(target).toHaveLength(0)
492+
})
493+
494+
it('does nothing when deployment is undefined', () => {
495+
const target: SubgraphDeploymentID[] = []
496+
addIndexingPaymentsSubgraphToTarget(true, undefined, target)
497+
expect(target).toHaveLength(0)
498+
})
499+
500+
it('does not duplicate an already-present deployment', () => {
501+
const target: SubgraphDeploymentID[] = [paymentsDeployment]
502+
addIndexingPaymentsSubgraphToTarget(true, paymentsDeployment, target)
503+
expect(target).toHaveLength(1)
504+
})
505+
})
506+
507+
describe('reconcileDeployments indexing-payments carve-out wiring', () => {
508+
const paymentsDeployment = new SubgraphDeploymentID(
509+
'QmddSbatCN1XmufoBm1bBwPx4L3FtuMAMHUNNBSPzrgL2a',
510+
)
511+
512+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
513+
const mockLogger: any = {
514+
child: jest.fn().mockReturnThis(),
515+
info: jest.fn(),
516+
debug: jest.fn(),
517+
warn: jest.fn(),
518+
error: jest.fn(),
519+
trace: jest.fn(),
520+
}
521+
522+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
523+
function createAgentUnderTest(network: any) {
524+
const agent = Object.create(Agent.prototype)
525+
agent.logger = mockLogger
526+
agent.offchainSubgraphs = []
527+
agent.multiNetworks = {
528+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
529+
map: async (fn: any) => Promise.all([fn({ network })]),
530+
}
531+
agent.graphNode = {
532+
subgraphDeploymentsAssignments: jest.fn().mockResolvedValue([]),
533+
ensure: jest.fn().mockResolvedValue(undefined),
534+
pause: jest.fn().mockResolvedValue(undefined),
535+
}
536+
return agent
537+
}
538+
539+
it('schedules the indexing-payments deployment for indexing when DIPS is enabled', async () => {
540+
const agent = createAgentUnderTest({
541+
networkSubgraph: { deployment: undefined },
542+
specification: { indexerOptions: { enableDips: true } },
543+
indexingPaymentsSubgraph: { deployment: { id: paymentsDeployment } },
544+
})
545+
546+
await agent.reconcileDeployments([], [], [])
547+
548+
const ensureCalls = agent.graphNode.ensure.mock.calls
549+
const ensuredDeployments = ensureCalls.map(
550+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
551+
(call: any[]) => (call[1] as SubgraphDeploymentID).bytes32,
552+
)
553+
expect(ensuredDeployments).toContain(paymentsDeployment.bytes32)
554+
})
555+
556+
it('does not schedule the indexing-payments deployment when DIPS is disabled', async () => {
557+
const agent = createAgentUnderTest({
558+
networkSubgraph: { deployment: undefined },
559+
specification: { indexerOptions: { enableDips: false } },
560+
indexingPaymentsSubgraph: { deployment: { id: paymentsDeployment } },
561+
})
562+
563+
await agent.reconcileDeployments([], [], [])
564+
565+
expect(agent.graphNode.ensure).not.toHaveBeenCalled()
566+
})
567+
568+
it('does not schedule the indexing-payments deployment when deployment is undefined', async () => {
569+
const agent = createAgentUnderTest({
570+
networkSubgraph: { deployment: undefined },
571+
specification: { indexerOptions: { enableDips: true } },
572+
indexingPaymentsSubgraph: { deployment: undefined },
573+
})
574+
575+
await agent.reconcileDeployments([], [], [])
576+
577+
expect(agent.graphNode.ensure).not.toHaveBeenCalled()
578+
})
579+
})

packages/indexer-agent/src/agent.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ const deploymentInList = (
5555
): boolean =>
5656
list.find(item => item.bytes32 === deployment.bytes32) !== undefined
5757

58+
export function addIndexingPaymentsSubgraphToTarget(
59+
enableDips: boolean,
60+
deployment: SubgraphDeploymentID | undefined,
61+
targetDeployments: SubgraphDeploymentID[],
62+
): void {
63+
if (!enableDips || !deployment) return
64+
if (deploymentInList(targetDeployments, deployment)) return
65+
targetDeployments.push(deployment)
66+
}
67+
5868
const deploymentRuleInList = (
5969
list: IndexingRuleAttributes[],
6070
deployment: SubgraphDeploymentID,
@@ -906,6 +916,17 @@ export class Agent {
906916
}
907917
})
908918

919+
// ----------------------------------------------------------------------------------------
920+
// Ensure the indexing-payments subgraph is always indexed when DIPS is enabled
921+
// ----------------------------------------------------------------------------------------
922+
await this.multiNetworks.map(async ({ network }) => {
923+
addIndexingPaymentsSubgraphToTarget(
924+
network.specification.indexerOptions.enableDips,
925+
network.indexingPaymentsSubgraph?.deployment?.id,
926+
targetDeployments,
927+
)
928+
})
929+
909930
// ----------------------------------------------------------------------------------------
910931
// Inspect Deployments and Networks
911932
// ----------------------------------------------------------------------------------------

packages/indexer-agent/src/commands/start.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,20 @@ export const start = {
198198
type: 'string',
199199
group: 'TAP Subgraph',
200200
})
201+
.option('indexing-payments-subgraph-deployment', {
202+
description:
203+
'Indexing payments subgraph deployment (for local hosting)',
204+
array: false,
205+
type: 'string',
206+
group: 'Indexing Fees ("DIPs")',
207+
})
208+
.option('indexing-payments-subgraph-endpoint', {
209+
description:
210+
'Endpoint to query the indexing payments subgraph from',
211+
array: false,
212+
type: 'string',
213+
group: 'Indexing Fees ("DIPs")',
214+
})
201215
.option('allocate-on-network-subgraph', {
202216
description: 'Whether to allocate to the network subgraph',
203217
type: 'boolean',
@@ -434,6 +448,13 @@ export const start = {
434448
if (argv['enable-dips'] && !argv['dipper-endpoint']) {
435449
return 'Invalid --dipper-endpoint provided. Must be provided when --enable-dips is true.'
436450
}
451+
if (
452+
argv['enable-dips'] &&
453+
!argv['indexing-payments-subgraph-endpoint'] &&
454+
!argv['indexing-payments-subgraph-deployment']
455+
) {
456+
return 'At least one of --indexing-payments-subgraph-endpoint and --indexing-payments-subgraph-deployment must be provided when --enable-dips is true.'
457+
}
437458
return true
438459
})
439460
},
@@ -507,6 +528,10 @@ export async function createNetworkSpecification(
507528
deployment: argv.tapSubgraphDeployment,
508529
url: argv.tapSubgraphEndpoint,
509530
},
531+
indexingPaymentsSubgraph: {
532+
deployment: argv.indexingPaymentsSubgraphDeployment,
533+
url: argv.indexingPaymentsSubgraphEndpoint,
534+
},
510535
}
511536

512537
const networkProvider = {

packages/indexer-common/src/indexing-fees/__tests__/agreement-monitor.test.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unused-vars */
2-
import {
3-
fetchCollectableAgreements,
4-
SubgraphIndexingAgreement,
5-
} from '../agreement-monitor'
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { fetchCollectableAgreements } from '../agreement-monitor'
63

74
const mockQuery = jest.fn()
85
const mockNetworkSubgraph = { query: mockQuery } as any
@@ -22,7 +19,7 @@ describe('fetchCollectableAgreements', () => {
2219
id: '0x00000000000000000000000000000001',
2320
allocationId: '0xaaaa',
2421
subgraphDeploymentId: '0xbbbb',
25-
state: 1,
22+
state: 'Accepted',
2623
lastCollectionAt: '1000',
2724
endsAt: '9999999999',
2825
maxInitialTokens: '1000000',
@@ -41,7 +38,7 @@ describe('fetchCollectableAgreements', () => {
4138

4239
expect(result).toHaveLength(1)
4340
expect(result[0].id).toBe('0x00000000000000000000000000000001')
44-
expect(result[0].state).toBe(1)
41+
expect(result[0].state).toBe('Accepted')
4542
expect(mockQuery).toHaveBeenCalledTimes(1)
4643
})
4744

@@ -61,7 +58,7 @@ describe('fetchCollectableAgreements', () => {
6158
id: `0x${i.toString(16).padStart(32, '0')}`,
6259
allocationId: '0xaaaa',
6360
subgraphDeploymentId: '0xbbbb',
64-
state: 1,
61+
state: 'Accepted',
6562
lastCollectionAt: '1000',
6663
endsAt: '9999999999',
6764
maxInitialTokens: '1000000',
@@ -78,7 +75,7 @@ describe('fetchCollectableAgreements', () => {
7875
id: '0x' + 'f'.repeat(32),
7976
allocationId: '0xaaaa',
8077
subgraphDeploymentId: '0xbbbb',
81-
state: 1,
78+
state: 'Accepted',
8279
lastCollectionAt: '1000',
8380
endsAt: '9999999999',
8481
maxInitialTokens: '1000000',

packages/indexer-common/src/indexing-fees/__tests__/collect-agreements.test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { Logger } from '@graphprotocol/common-ts'
33
import { DipsManager } from '../dips'
4+
import type { SubgraphIndexingAgreement } from '../agreement-monitor'
45

56
const logger = {
67
child: jest.fn().mockReturnThis(),
@@ -40,6 +41,7 @@ const mockGraphNode = {
4041
const mockNetwork = {
4142
contracts: mockContracts,
4243
networkSubgraph: mockNetworkSubgraph,
44+
indexingPaymentsSubgraph: mockNetworkSubgraph,
4345
transactionManager: mockTransactionManager,
4446
specification: {
4547
indexerOptions: {
@@ -55,20 +57,24 @@ const mockNetwork = {
5557
},
5658
} as any
5759

58-
const mockModels = {} as any
60+
const mockModels = {
61+
IndexingRule: {
62+
findAll: jest.fn().mockResolvedValue([]),
63+
},
64+
} as any
5965

6066
function createDipsManager(): DipsManager {
6167
return new DipsManager(logger, mockModels, mockNetwork, mockGraphNode, null)
6268
}
6369

6470
// Helper: agreement that was last collected long ago (ready to collect)
65-
function makeReadyAgreement(id = '0x00000000000000000000000000000001') {
71+
function makeReadyAgreement(id = '0x00000000000000000000000000000001'): SubgraphIndexingAgreement {
6672
return {
6773
id,
6874
allocationId: '0xaaaa',
6975
subgraphDeploymentId:
7076
'0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
71-
state: 1,
77+
state: 'Accepted',
7278
lastCollectionAt: '0', // never collected → always ready
7379
endsAt: '9999999999',
7480
maxInitialTokens: '1000000',

packages/indexer-common/src/indexing-fees/__tests__/dips.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ describe('DipsManager', () => {
465465
id: '0x123e4567e89b12d3a456426614174000',
466466
allocationId: '0xabcd47df40c29949a75a6693c77834c00b8ad626',
467467
subgraphDeploymentId: 'QmTZ8ejXJxRo7vDBS4uwqBeGoxLSWbhaA7oXa1RvxunLy7',
468-
state: 1, // Accepted
468+
state: 'Accepted',
469469
lastCollectionAt: '0',
470470
endsAt: '9999999999',
471471
maxInitialTokens: '1000',
@@ -583,7 +583,7 @@ describe('DipsManager', () => {
583583
id: '0x123e4567e89b12d3a456426614174000',
584584
allocationId: '0xabcd47df40c29949a75a6693c77834c00b8ad626',
585585
subgraphDeploymentId: 'QmTZ8ejXJxRo7vDBS4uwqBeGoxLSWbhaA7oXa1RvxunLy7',
586-
state: 1,
586+
state: 'Accepted',
587587
lastCollectionAt: '0',
588588
endsAt: '9999999999',
589589
maxInitialTokens: '1000',
@@ -605,7 +605,7 @@ describe('DipsManager', () => {
605605

606606
test('removes payer-cancelled agreement from tracker after collection', () => {
607607
const removeSpy = jest.spyOn(dipsManager.collectionTracker, 'remove')
608-
const agreement = { ...baseAgreement, state: 3 }
608+
const agreement = { ...baseAgreement, state: 'CanceledByPayer' as const }
609609

610610
const result = dipsManager.cleanupFinishedAgreement(agreement, 1000, logger)
611611

@@ -616,7 +616,7 @@ describe('DipsManager', () => {
616616
test('removes expired agreement from tracker after collection', () => {
617617
const removeSpy = jest.spyOn(dipsManager.collectionTracker, 'remove')
618618
const nowSeconds = 2000
619-
const agreement = { ...baseAgreement, state: 1, endsAt: '1000' }
619+
const agreement = { ...baseAgreement, state: 'Accepted' as const, endsAt: '1000' }
620620

621621
const result = dipsManager.cleanupFinishedAgreement(
622622
agreement,
@@ -631,7 +631,7 @@ describe('DipsManager', () => {
631631
test('does not remove active agreement from tracker', () => {
632632
const removeSpy = jest.spyOn(dipsManager.collectionTracker, 'remove')
633633
const nowSeconds = 1000
634-
const agreement = { ...baseAgreement, state: 1, endsAt: '9999999999' }
634+
const agreement = { ...baseAgreement, state: 'Accepted' as const, endsAt: '9999999999' }
635635

636636
const result = dipsManager.cleanupFinishedAgreement(
637637
agreement,
@@ -649,7 +649,7 @@ describe('DipsManager', () => {
649649
id: '0x123e4567e89b12d3a456426614174000',
650650
allocationId: '0xabcd47df40c29949a75a6693c77834c00b8ad626',
651651
subgraphDeploymentId: 'QmTZ8ejXJxRo7vDBS4uwqBeGoxLSWbhaA7oXa1RvxunLy7',
652-
state: 1,
652+
state: 'Accepted',
653653
lastCollectionAt: '0',
654654
endsAt: '9999999999',
655655
maxInitialTokens: '1000',

packages/indexer-common/src/indexing-fees/agreement-monitor.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import gql from 'graphql-tag'
22
import { SubgraphClient } from '../subgraph-client'
33

4+
export type AgreementState =
5+
| 'NotAccepted'
6+
| 'Accepted'
7+
| 'CanceledByServiceProvider'
8+
| 'CanceledByPayer'
9+
410
export interface SubgraphIndexingAgreement {
511
id: string
612
allocationId: string
713
subgraphDeploymentId: string
8-
state: number
14+
state: AgreementState
915
lastCollectionAt: string
1016
endsAt: string
1117
maxInitialTokens: string
@@ -20,7 +26,7 @@ export interface SubgraphIndexingAgreement {
2026
const INDEXING_AGREEMENTS_QUERY = gql`
2127
query indexingAgreements($indexer: String!, $lastId: String!) {
2228
indexingAgreements(
23-
where: { serviceProvider: $indexer, state_in: [1, 3], id_gt: $lastId }
29+
where: { indexer: $indexer, state_in: [Accepted, CanceledByPayer], id_gt: $lastId }
2430
orderBy: id
2531
orderDirection: asc
2632
first: 1000
@@ -43,14 +49,14 @@ const INDEXING_AGREEMENTS_QUERY = gql`
4349
`
4450

4551
export async function fetchCollectableAgreements(
46-
networkSubgraph: SubgraphClient,
52+
subgraphClient: SubgraphClient,
4753
indexerAddress: string,
4854
): Promise<SubgraphIndexingAgreement[]> {
4955
const all: SubgraphIndexingAgreement[] = []
5056
let lastId = ''
5157

5258
for (;;) {
53-
const result = await networkSubgraph.query(INDEXING_AGREEMENTS_QUERY, {
59+
const result = await subgraphClient.query(INDEXING_AGREEMENTS_QUERY, {
5460
indexer: indexerAddress.toLowerCase(),
5561
lastId,
5662
})

0 commit comments

Comments
 (0)