Skip to content

Commit 26b08ba

Browse files
committed
chore: collect all dips receipts periodically
1 parent 28ea27e commit 26b08ba

File tree

9 files changed

+153
-49
lines changed

9 files changed

+153
-49
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ const setup = async () => {
146146
const network = await Network.create(
147147
logger,
148148
networkSpecification,
149+
models,
149150
queryFeeModels,
150151
graphNode,
151152
metrics,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,7 @@ export async function run(
612612
const network = await Network.create(
613613
logger,
614614
networkSpecification,
615+
managementModels,
615616
queryFeeModels,
616617
graphNode,
617618
metrics,

packages/indexer-cli/src/__tests__/util.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export const setup = async () => {
8787
const network = await Network.create(
8888
logger,
8989
testNetworkSpecification,
90+
models,
9091
queryFeeModels,
9192
graphNode,
9293
metrics,

packages/indexer-common/src/allocations/__tests__/tap.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
TapSubgraphResponse,
88
TapCollector,
99
Allocation,
10+
defineIndexerManagementModels,
1011
} from '@graphprotocol/indexer-common'
1112
import {
1213
Address,
@@ -43,6 +44,7 @@ const setup = async () => {
4344
// Clearing the registry prevents duplicate metric registration in the default registry.
4445
metrics.registry.clear()
4546
sequelize = await connectDatabase(__DATABASE__)
47+
const models = defineIndexerManagementModels(sequelize)
4648
queryFeeModels = defineQueryFeeModels(sequelize)
4749
sequelize = await sequelize.sync({ force: true })
4850

@@ -56,6 +58,7 @@ const setup = async () => {
5658
const network = await Network.create(
5759
logger,
5860
testNetworkSpecification,
61+
models,
5962
queryFeeModels,
6063
graphNode,
6164
metrics,

packages/indexer-common/src/allocations/__tests__/validate-queries.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
defineIndexerManagementModels,
23
defineQueryFeeModels,
34
GraphNode,
45
Network,
@@ -36,6 +37,7 @@ const setup = async () => {
3637
// Clearing the registry prevents duplicate metric registration in the default registry.
3738
metrics.registry.clear()
3839
sequelize = await connectDatabase(__DATABASE__)
40+
const models = defineIndexerManagementModels(sequelize)
3941
queryFeeModels = defineQueryFeeModels(sequelize)
4042
sequelize = await sequelize.sync({ force: true })
4143

@@ -49,6 +51,7 @@ const setup = async () => {
4951
const network = await Network.create(
5052
logger,
5153
testNetworkSpecification,
54+
models,
5255
queryFeeModels,
5356
graphNode,
5457
metrics,

packages/indexer-common/src/indexer-management/__tests__/allocations.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const setup = async () => {
6161
const network = await Network.create(
6262
logger,
6363
testNetworkSpecification,
64+
managementModels,
6465
queryFeeModels,
6566
graphNode,
6667
metrics,

packages/indexer-common/src/indexer-management/__tests__/util.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const createTestManagementClient = async (
5656
const network = await Network.create(
5757
logger,
5858
networkSpecification,
59+
managementModels,
5960
queryFeeModels,
6061
graphNode,
6162
metrics,

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

Lines changed: 114 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import {
77
IndexingDecisionBasis,
88
IndexingRuleAttributes,
99
Network,
10+
QueryFeeModels,
11+
sequentialTimerMap,
1012
SubgraphIdentifierType,
13+
TapCollector,
1114
upsertIndexingRule,
1215
} from '@graphprotocol/indexer-common'
1316
import { Op } from 'sequelize'
@@ -23,6 +26,10 @@ import {
2326
GatewayDipsServiceClientImpl,
2427
} from '@graphprotocol/dips-proto/generated/gateway'
2528
import { IndexingAgreement } from '../indexer-management/models/indexing-agreement'
29+
import { NetworkSpecification } from '../network-specification'
30+
import { Wallet } from 'ethers'
31+
32+
const DIPS_COLLECTION_INTERVAL = 60_000
2633

2734
export class DipsManager {
2835
private gatewayDipsServiceClient: GatewayDipsServiceClientImpl
@@ -86,9 +93,109 @@ export class DipsManager {
8693
await agreement.save()
8794
}
8895
}
96+
async ensureAgreementRules() {
97+
if (!this.parent) {
98+
this.logger.error(
99+
'DipsManager has no parent AllocationManager, cannot ensure agreement rules',
100+
)
101+
return
102+
}
103+
// Get all the indexing agreements that are not cancelled
104+
const indexingAgreements = await this.models.IndexingAgreement.findAll({
105+
where: {
106+
cancelled_at: null,
107+
},
108+
})
109+
// For each agreement, check that there is an indexing rule to always
110+
// allocate to the agreement's subgraphDeploymentId, and if not, create one
111+
for (const agreement of indexingAgreements) {
112+
const subgraphDeploymentID = new SubgraphDeploymentID(
113+
agreement.subgraph_deployment_id,
114+
)
115+
// If there is not yet an indexingRule that deems this deployment worth allocating to, make one
116+
if (!(await this.parent.matchingRuleExists(this.logger, subgraphDeploymentID))) {
117+
this.logger.debug(`Creating indexing rule for agreement ${agreement.id}`)
118+
const indexingRule = {
119+
identifier: agreement.subgraph_deployment_id,
120+
allocationAmount: formatGRT(
121+
this.network.specification.indexerOptions.dipsAllocationAmount,
122+
),
123+
identifierType: SubgraphIdentifierType.DEPLOYMENT,
124+
decisionBasis: IndexingDecisionBasis.ALWAYS,
125+
protocolNetwork: this.network.specification.networkIdentifier,
126+
autoRenewal: true,
127+
allocationLifetime: Math.max(
128+
Number(agreement.min_epochs_per_collection),
129+
Number(agreement.max_epochs_per_collection) -
130+
this.network.specification.indexerOptions.dipsEpochsMargin,
131+
),
132+
} as Partial<IndexingRuleAttributes>
133+
134+
await upsertIndexingRule(this.logger, this.models, indexingRule)
135+
}
136+
}
137+
}
138+
}
139+
140+
export class DipsCollector {
141+
private gatewayDipsServiceClient: GatewayDipsServiceClientImpl
142+
constructor(
143+
private logger: Logger,
144+
private managementModels: IndexerManagementModels,
145+
private queryFeeModels: QueryFeeModels,
146+
private specification: NetworkSpecification,
147+
private tapCollector: TapCollector,
148+
private wallet: Wallet,
149+
) {
150+
if (!this.specification.indexerOptions.dipperEndpoint) {
151+
throw new Error('dipperEndpoint is not set')
152+
}
153+
this.gatewayDipsServiceClient = createGatewayDipsServiceClient(
154+
this.specification.indexerOptions.dipperEndpoint,
155+
)
156+
}
157+
158+
static create(
159+
logger: Logger,
160+
managementModels: IndexerManagementModels,
161+
queryFeeModels: QueryFeeModels,
162+
specification: NetworkSpecification,
163+
tapCollector: TapCollector,
164+
wallet: Wallet,
165+
) {
166+
const collector = new DipsCollector(
167+
logger,
168+
managementModels,
169+
queryFeeModels,
170+
specification,
171+
tapCollector,
172+
wallet,
173+
)
174+
collector.startCollectionLoop()
175+
return collector
176+
}
177+
178+
startCollectionLoop() {
179+
sequentialTimerMap(
180+
{
181+
logger: this.logger,
182+
milliseconds: DIPS_COLLECTION_INTERVAL,
183+
},
184+
async () => {
185+
this.logger.debug('Running DIPS payment collection loop')
186+
await this.collectAllPayments()
187+
},
188+
{
189+
onError: (err) => {
190+
this.logger.error('Failed to collect DIPS payments', { err })
191+
},
192+
},
193+
)
194+
}
195+
89196
// Collect payments for all outstanding agreements
90197
async collectAllPayments() {
91-
const outstandingAgreements = await this.models.IndexingAgreement.findAll({
198+
const outstandingAgreements = await this.managementModels.IndexingAgreement.findAll({
92199
where: {
93200
last_payment_collected_at: null,
94201
last_allocation_id: {
@@ -110,27 +217,27 @@ export class DipsManager {
110217
agreement.id,
111218
agreement.last_allocation_id,
112219
entityCount,
113-
this.network.wallet,
220+
this.wallet,
114221
)
115222
try {
116223
const response = await this.gatewayDipsServiceClient.CollectPayment({
117224
version: 1,
118225
signedCollection: collection,
119226
})
120227
if (response.status === CollectPaymentStatus.ACCEPT) {
121-
if (!this.network.tapCollector) {
228+
if (!this.tapCollector) {
122229
throw new Error('TapCollector not initialized')
123230
}
124231
// Store the tap receipt in the database
125232
const tapReceipt = decodeTapReceipt(
126233
response.tapReceipt,
127-
this.network.tapCollector?.tapContracts.tapVerifier.address,
234+
this.tapCollector?.tapContracts.tapVerifier.address,
128235
)
129236
// TODO: check that the signer of the TAP receipt is a signer
130237
// on the corresponding escrow account for the payer (sender) of the
131238
// indexing agreement
132239
const escrowSender = await getEscrowSenderForSigner(
133-
this.network.tapCollector?.tapSubgraph,
240+
this.tapCollector?.tapSubgraph,
134241
tapReceipt.signer_address,
135242
)
136243
if (escrowSender !== agreement.payer) {
@@ -139,9 +246,9 @@ export class DipsManager {
139246
'Signer of TAP receipt is not a signer on the indexing agreement',
140247
)
141248
}
142-
await this.network.queryFeeModels.scalarTapReceipts.create(tapReceipt)
249+
await this.queryFeeModels.scalarTapReceipts.create(tapReceipt)
143250
// Mark the agreement as having had a payment collected
144-
await this.models.IndexingAgreement.update(
251+
await this.managementModels.IndexingAgreement.update(
145252
{
146253
last_payment_collected_at: new Date(),
147254
},
@@ -160,46 +267,4 @@ export class DipsManager {
160267
})
161268
}
162269
}
163-
async ensureAgreementRules() {
164-
if (!this.parent) {
165-
this.logger.error(
166-
'DipsManager has no parent AllocationManager, cannot ensure agreement rules',
167-
)
168-
return
169-
}
170-
// Get all the indexing agreements that are not cancelled
171-
const indexingAgreements = await this.models.IndexingAgreement.findAll({
172-
where: {
173-
cancelled_at: null,
174-
},
175-
})
176-
// For each agreement, check that there is an indexing rule to always
177-
// allocate to the agreement's subgraphDeploymentId, and if not, create one
178-
for (const agreement of indexingAgreements) {
179-
const subgraphDeploymentID = new SubgraphDeploymentID(
180-
agreement.subgraph_deployment_id,
181-
)
182-
// If there is not yet an indexingRule that deems this deployment worth allocating to, make one
183-
if (!(await this.parent.matchingRuleExists(this.logger, subgraphDeploymentID))) {
184-
this.logger.debug(`Creating indexing rule for agreement ${agreement.id}`)
185-
const indexingRule = {
186-
identifier: agreement.subgraph_deployment_id,
187-
allocationAmount: formatGRT(
188-
this.network.specification.indexerOptions.dipsAllocationAmount,
189-
),
190-
identifierType: SubgraphIdentifierType.DEPLOYMENT,
191-
decisionBasis: IndexingDecisionBasis.ALWAYS,
192-
protocolNetwork: this.network.specification.networkIdentifier,
193-
autoRenewal: true,
194-
allocationLifetime: Math.max(
195-
Number(agreement.min_epochs_per_collection),
196-
Number(agreement.max_epochs_per_collection) -
197-
this.network.specification.indexerOptions.dipsEpochsMargin,
198-
),
199-
} as Partial<IndexingRuleAttributes>
200-
201-
await upsertIndexingRule(this.logger, this.models, indexingRule)
202-
}
203-
}
204-
}
205270
}

packages/indexer-common/src/network.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ import {
2929
AllocationReceiptCollector,
3030
SubgraphFreshnessChecker,
3131
monitorEligibleAllocations,
32+
IndexerManagementModels,
3233
} from '.'
3334
import { resolveChainId } from './indexer-management'
3435
import { monitorEthBalance } from './utils'
3536
import { QueryFeeModels } from './query-fees'
3637
import { readFileSync } from 'fs'
3738
import { TapCollector } from './allocations/tap-collector'
39+
import { DipsCollector } from './indexing-fees/dips'
3840

3941
export class Network {
4042
logger: Logger
@@ -49,10 +51,12 @@ export class Network {
4951
receiptCollector: AllocationReceiptCollector | undefined
5052

5153
tapCollector: TapCollector | undefined
54+
dipsCollector: DipsCollector | undefined
5255
specification: spec.NetworkSpecification
5356
paused: Eventual<boolean>
5457
isOperator: Eventual<boolean>
5558
queryFeeModels: QueryFeeModels
59+
managementModels: IndexerManagementModels
5660
private constructor(
5761
logger: Logger,
5862
contracts: NetworkContracts,
@@ -67,6 +71,8 @@ export class Network {
6771
paused: Eventual<boolean>,
6872
isOperator: Eventual<boolean>,
6973
queryFeeModels: QueryFeeModels,
74+
managementModels: IndexerManagementModels,
75+
dipsCollector: DipsCollector | undefined,
7076
) {
7177
this.logger = logger
7278
this.contracts = contracts
@@ -81,11 +87,14 @@ export class Network {
8187
this.paused = paused
8288
this.isOperator = isOperator
8389
this.queryFeeModels = queryFeeModels
90+
this.managementModels = managementModels
91+
this.dipsCollector = dipsCollector
8492
}
8593

8694
static async create(
8795
parentLogger: Logger,
8896
specification: spec.NetworkSpecification,
97+
managementModels: IndexerManagementModels,
8998
queryFeeModels: QueryFeeModels,
9099
graphNode: GraphNode,
91100
metrics: Metrics,
@@ -331,6 +340,23 @@ export class Network {
331340
Tap Subgraph: ${!!tapSubgraph}.`)
332341
}
333342

343+
let dipsCollector: DipsCollector | undefined = undefined
344+
if (specification.indexerOptions.enableDips) {
345+
if (!tapCollector) {
346+
throw new Error(
347+
'TapCollector is not initialized, cannot initialize DipsCollector',
348+
)
349+
}
350+
dipsCollector = DipsCollector.create(
351+
logger,
352+
managementModels,
353+
queryFeeModels,
354+
specification,
355+
tapCollector,
356+
wallet,
357+
)
358+
}
359+
334360
// --------------------------------------------------------------------------------
335361
// * Network
336362
// --------------------------------------------------------------------------------
@@ -348,6 +374,8 @@ export class Network {
348374
paused,
349375
isOperator,
350376
queryFeeModels,
377+
managementModels,
378+
dipsCollector,
351379
)
352380
}
353381

0 commit comments

Comments
 (0)