Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions packages/indexer-agent/src/__tests__/agent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Agent,
addIndexingPaymentsSubgraphToTarget,
convertSubgraphBasedRulesToDeploymentBased,
consolidateAllocationDecisions,
resolveTargetDeployments,
Expand Down Expand Up @@ -472,3 +473,107 @@ describe('reconcileDeploymentAllocationAction', () => {
expect(operator.presentPOIForAllocations).not.toHaveBeenCalled()
})
})

describe('addIndexingPaymentsSubgraphToTarget function', () => {
const paymentsDeployment = new SubgraphDeploymentID(
'QmddSbatCN1XmufoBm1bBwPx4L3FtuMAMHUNNBSPzrgL2a',
)

it('pushes the deployment when enableDips is true and deployment is defined', () => {
const target: SubgraphDeploymentID[] = []
addIndexingPaymentsSubgraphToTarget(true, paymentsDeployment, target)
expect(target.map(d => d.bytes32)).toContain(paymentsDeployment.bytes32)
})

it('does nothing when enableDips is false', () => {
const target: SubgraphDeploymentID[] = []
addIndexingPaymentsSubgraphToTarget(false, paymentsDeployment, target)
expect(target).toHaveLength(0)
})

it('does nothing when deployment is undefined', () => {
const target: SubgraphDeploymentID[] = []
addIndexingPaymentsSubgraphToTarget(true, undefined, target)
expect(target).toHaveLength(0)
})

it('does not duplicate an already-present deployment', () => {
const target: SubgraphDeploymentID[] = [paymentsDeployment]
addIndexingPaymentsSubgraphToTarget(true, paymentsDeployment, target)
expect(target).toHaveLength(1)
})
})

describe('reconcileDeployments indexing-payments carve-out wiring', () => {
const paymentsDeployment = new SubgraphDeploymentID(
'QmddSbatCN1XmufoBm1bBwPx4L3FtuMAMHUNNBSPzrgL2a',
)

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockLogger: any = {
child: jest.fn().mockReturnThis(),
info: jest.fn(),
debug: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
trace: jest.fn(),
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function createAgentUnderTest(network: any) {
const agent = Object.create(Agent.prototype)
agent.logger = mockLogger
agent.offchainSubgraphs = []
agent.multiNetworks = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
map: async (fn: any) => Promise.all([fn({ network })]),
}
agent.graphNode = {
subgraphDeploymentsAssignments: jest.fn().mockResolvedValue([]),
ensure: jest.fn().mockResolvedValue(undefined),
pause: jest.fn().mockResolvedValue(undefined),
}
return agent
}

it('schedules the indexing-payments deployment for indexing when DIPS is enabled', async () => {
const agent = createAgentUnderTest({
networkSubgraph: { deployment: undefined },
specification: { indexerOptions: { enableDips: true } },
indexingPaymentsSubgraph: { deployment: { id: paymentsDeployment } },
})

await agent.reconcileDeployments([], [], [])

const ensureCalls = agent.graphNode.ensure.mock.calls
const ensuredDeployments = ensureCalls.map(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(call: any[]) => (call[1] as SubgraphDeploymentID).bytes32,
)
expect(ensuredDeployments).toContain(paymentsDeployment.bytes32)
})

it('does not schedule the indexing-payments deployment when DIPS is disabled', async () => {
const agent = createAgentUnderTest({
networkSubgraph: { deployment: undefined },
specification: { indexerOptions: { enableDips: false } },
indexingPaymentsSubgraph: { deployment: { id: paymentsDeployment } },
})

await agent.reconcileDeployments([], [], [])

expect(agent.graphNode.ensure).not.toHaveBeenCalled()
})

it('does not schedule the indexing-payments deployment when deployment is undefined', async () => {
const agent = createAgentUnderTest({
networkSubgraph: { deployment: undefined },
specification: { indexerOptions: { enableDips: true } },
indexingPaymentsSubgraph: { deployment: undefined },
})

await agent.reconcileDeployments([], [], [])

expect(agent.graphNode.ensure).not.toHaveBeenCalled()
})
})
32 changes: 32 additions & 0 deletions packages/indexer-agent/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ const deploymentInList = (
): boolean =>
list.find(item => item.bytes32 === deployment.bytes32) !== undefined

export function addIndexingPaymentsSubgraphToTarget(
enableDips: boolean,
deployment: SubgraphDeploymentID | undefined,
targetDeployments: SubgraphDeploymentID[],
): void {
if (!enableDips || !deployment) return
if (deploymentInList(targetDeployments, deployment)) return
targetDeployments.push(deployment)
}

const deploymentRuleInList = (
list: IndexingRuleAttributes[],
deployment: SubgraphDeploymentID,
Expand Down Expand Up @@ -906,6 +916,17 @@ export class Agent {
}
})

// ----------------------------------------------------------------------------------------
// Ensure the indexing-payments subgraph is always indexed when DIPS is enabled
// ----------------------------------------------------------------------------------------
await this.multiNetworks.map(async ({ network }) => {
addIndexingPaymentsSubgraphToTarget(
network.specification.indexerOptions.enableDips,
network.indexingPaymentsSubgraph?.deployment?.id,
targetDeployments,
)
})

// ----------------------------------------------------------------------------------------
// Inspect Deployments and Networks
// ----------------------------------------------------------------------------------------
Expand Down Expand Up @@ -1328,6 +1349,17 @@ export class Agent {
network.specification.networkIdentifier,
)
}
// Indexing payments subgraph (DIPS)
if (
network.specification.indexerOptions.enableDips &&
network.specification.subgraphs.indexingPaymentsSubgraph?.deployment !==
undefined
) {
await this.ensureSubgraphIndexing(
network.specification.subgraphs.indexingPaymentsSubgraph.deployment,
network.specification.networkIdentifier,
)
}
}
}

Expand Down
24 changes: 24 additions & 0 deletions packages/indexer-agent/src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,19 @@ export const start = {
type: 'string',
group: 'TAP Subgraph',
})
.option('indexing-payments-subgraph-deployment', {
description:
'Indexing payments subgraph deployment (for local hosting)',
array: false,
type: 'string',
group: 'Indexing Fees ("DIPs")',
})
.option('indexing-payments-subgraph-endpoint', {
description: 'Endpoint to query the indexing payments subgraph from',
array: false,
type: 'string',
group: 'Indexing Fees ("DIPs")',
})
.option('allocate-on-network-subgraph', {
description: 'Whether to allocate to the network subgraph',
type: 'boolean',
Expand Down Expand Up @@ -434,6 +447,13 @@ export const start = {
if (argv['enable-dips'] && !argv['dipper-endpoint']) {
return 'Invalid --dipper-endpoint provided. Must be provided when --enable-dips is true.'
}
if (
argv['enable-dips'] &&
!argv['indexing-payments-subgraph-endpoint'] &&
!argv['indexing-payments-subgraph-deployment']
) {
return 'At least one of --indexing-payments-subgraph-endpoint and --indexing-payments-subgraph-deployment must be provided when --enable-dips is true.'
}
return true
})
},
Expand Down Expand Up @@ -507,6 +527,10 @@ export async function createNetworkSpecification(
deployment: argv.tapSubgraphDeployment,
url: argv.tapSubgraphEndpoint,
},
indexingPaymentsSubgraph: {
deployment: argv.indexingPaymentsSubgraphDeployment,
url: argv.indexingPaymentsSubgraphEndpoint,
},
}

const networkProvider = {
Expand Down
9 changes: 0 additions & 9 deletions packages/indexer-common/src/indexer-management/allocations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1214,15 +1214,6 @@ export class AllocationManager {

await upsertIndexingRule(logger, this.models, neverIndexingRule)

if (this.dipsManager) {
await this.dipsManager.tryCancelAgreement(allocationID)
await this.dipsManager.tryUpdateAgreementAllocation(
allocation.subgraphDeployment.id.toString(),
toAddress(allocationID),
null,
)
}

return {
actionID,
type: 'unallocate',
Expand Down
33 changes: 17 additions & 16 deletions packages/indexer-common/src/indexer-management/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,27 +55,28 @@ export class NetworkMonitor {
private networkSubgraph: SubgraphClient,
private ethereum: Provider,
private epochSubgraph: SubgraphClient,
private indexingPaymentsSubgraph?: SubgraphClient,
) {}

async hasActiveDipsAgreement(allocationId: string): Promise<boolean> {
try {
const result = await this.networkSubgraph.checkedQuery(
gql`
query indexingAgreements($allocationId: Bytes!) {
indexingAgreements(
where: { allocationId: $allocationId, state_not: 0 }
first: 1
) {
id
}
}
`,
{ allocationId: allocationId.toLowerCase() },
)
return (result.data?.indexingAgreements?.length ?? 0) > 0
} catch {
// No DIPS subgraph configured → no agreement can exist
if (!this.indexingPaymentsSubgraph) {
return false
}
const result = await this.indexingPaymentsSubgraph.checkedQuery(
gql`
query indexingAgreements($allocationId: Bytes!) {
indexingAgreements(
where: { allocationId: $allocationId, state_in: [Accepted, CanceledByPayer] }
first: 1
) {
id
}
}
`,
{ allocationId: allocationId.toLowerCase() },
)
return (result.data?.indexingAgreements?.length ?? 0) > 0
}

poiDisputeMonitoringEnabled(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1686,7 +1686,7 @@ export default {
force: boolean
protocolNetwork: string
},
{ logger, models, multiNetworks, actionManager }: IndexerManagementResolverContext,
{ logger, models, multiNetworks }: IndexerManagementResolverContext,
): Promise<CloseAllocationResult> => {
logger.debug('Execute closeAllocation() mutation', {
allocationID: allocation,
Expand Down Expand Up @@ -1762,17 +1762,6 @@ export default {

await models.IndexingRule.upsert(offchainIndexingRule)

const allocationManager =
actionManager?.allocationManagers[network.specification.networkIdentifier]
if (allocationManager?.dipsManager) {
await allocationManager.dipsManager.tryCancelAgreement(allocation)
await allocationManager.dipsManager.tryUpdateAgreementAllocation(
allocationData.subgraphDeployment.id.toString(),
toAddress(allocation),
null,
)
}

// Since upsert succeeded, we _must_ have a rule
const updatedRule = await models.IndexingRule.findOne({
where: { identifier: offchainIndexingRule.identifier },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unused-vars */
import {
fetchCollectableAgreements,
SubgraphIndexingAgreement,
} from '../agreement-monitor'
/* eslint-disable @typescript-eslint/no-explicit-any */
import { fetchCollectableAgreements } from '../agreement-monitor'

const mockQuery = jest.fn()
const mockNetworkSubgraph = { query: mockQuery } as any
Expand All @@ -22,7 +19,7 @@ describe('fetchCollectableAgreements', () => {
id: '0x00000000000000000000000000000001',
allocationId: '0xaaaa',
subgraphDeploymentId: '0xbbbb',
state: 1,
state: 'Accepted',
lastCollectionAt: '1000',
endsAt: '9999999999',
maxInitialTokens: '1000000',
Expand All @@ -41,7 +38,7 @@ describe('fetchCollectableAgreements', () => {

expect(result).toHaveLength(1)
expect(result[0].id).toBe('0x00000000000000000000000000000001')
expect(result[0].state).toBe(1)
expect(result[0].state).toBe('Accepted')
expect(mockQuery).toHaveBeenCalledTimes(1)
})

Expand All @@ -61,7 +58,7 @@ describe('fetchCollectableAgreements', () => {
id: `0x${i.toString(16).padStart(32, '0')}`,
allocationId: '0xaaaa',
subgraphDeploymentId: '0xbbbb',
state: 1,
state: 'Accepted',
lastCollectionAt: '1000',
endsAt: '9999999999',
maxInitialTokens: '1000000',
Expand All @@ -78,7 +75,7 @@ describe('fetchCollectableAgreements', () => {
id: '0x' + 'f'.repeat(32),
allocationId: '0xaaaa',
subgraphDeploymentId: '0xbbbb',
state: 1,
state: 'Accepted',
lastCollectionAt: '1000',
endsAt: '9999999999',
maxInitialTokens: '1000000',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Logger } from '@graphprotocol/common-ts'
import { DipsManager } from '../dips'
import type { SubgraphIndexingAgreement } from '../agreement-monitor'

const logger = {
child: jest.fn().mockReturnThis(),
Expand Down Expand Up @@ -40,6 +41,7 @@ const mockGraphNode = {
const mockNetwork = {
contracts: mockContracts,
networkSubgraph: mockNetworkSubgraph,
indexingPaymentsSubgraph: mockNetworkSubgraph,
transactionManager: mockTransactionManager,
specification: {
indexerOptions: {
Expand All @@ -55,20 +57,26 @@ const mockNetwork = {
},
} as any

const mockModels = {} as any
const mockModels = {
IndexingRule: {
findAll: jest.fn().mockResolvedValue([]),
},
} as any

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

// Helper: agreement that was last collected long ago (ready to collect)
function makeReadyAgreement(id = '0x00000000000000000000000000000001') {
function makeReadyAgreement(
id = '0x00000000000000000000000000000001',
): SubgraphIndexingAgreement {
return {
id,
allocationId: '0xaaaa',
subgraphDeploymentId:
'0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
state: 1,
state: 'Accepted',
lastCollectionAt: '0', // never collected → always ready
endsAt: '9999999999',
maxInitialTokens: '1000000',
Expand Down
Loading
Loading