Skip to content

Commit 6d05b47

Browse files
committed
feat: indexing fees / dips (wip)
1 parent beed3a7 commit 6d05b47

File tree

27 files changed

+1712
-58
lines changed

27 files changed

+1712
-58
lines changed

docs/action-queue.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ The action execution worker will only grab items from the action queue to execut
88

99
## Allocation management modes:
1010
- `auto`: The indexer-agent will act similarly to the legacy paradigm. When it identifies allocation actions it will add them to the queue with ActionStatus = `approved`; the execution worker process will pick up the approved actions within 30 seconds and execute them.
11-
- `manual`: The indexer-agent will not add any items to the action queue in this mode. It will spin up an indexer-management server which can be interacted with manually or integrated with 3rd party tools to add actions to the action queue and execute them.
12-
- `oversight`: The indexer-agent will add run its reconciliation loop to make allocation decisions and when actions are identified it will queue them. These actions will then require approval before they can be executed.
11+
- `manual`: The indexer-agent will not add any items to the action queue in this mode. It will spin up an indexer-management server which can be interacted with manually or integrated with 3rd party tools to add actions to the action queue and execute them. An exception to this is indexing agreements (DIPs), for which actions will be queued and executed even in this mode.
12+
- `oversight`: The indexer-agent will add run its reconciliation loop to make allocation decisions and when actions are identified it will queue them. These actions will then require approval before they can be executed. An exception to this is indexing agreements (DIPs), for which actions will be queued as approved and executed even in this mode.
1313

1414
## Actions CLI
1515
The indexer-cli provides an `actions` module for manually working with the action queue. It uses the #Graphql API hosted by the indexer management server to interact with the actions queue.

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

+1
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/agent.ts

+79-15
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,16 @@ export class Agent {
220220
sequentialTimerMap(
221221
{ logger, milliseconds: requestIntervalSmall },
222222
async () => {
223+
if (network.specification.indexerOptions.enableDips) {
224+
// There should be a DipsManager in the operator
225+
if (!operator.dipsManager) {
226+
throw new Error('DipsManager is not available')
227+
}
228+
logger.trace('Ensuring indexing rules for DIPS', {
229+
protocolNetwork: network.specification.networkIdentifier,
230+
})
231+
await operator.dipsManager.ensureAgreementRules()
232+
}
223233
logger.trace('Fetching indexing rules', {
224234
protocolNetwork: network.specification.networkIdentifier,
225235
})
@@ -252,14 +262,15 @@ export class Agent {
252262
},
253263
)
254264

255-
// Skip fetching active deployments if the deployment management mode is manual and POI tracking is disabled
265+
// Skip fetching active deployments if the deployment management mode is manual, DIPs is disabled, and POI tracking is disabled
256266
const activeDeployments: Eventual<SubgraphDeploymentID[]> =
257267
sequentialTimerMap(
258268
{ logger, milliseconds: requestIntervalLarge },
259269
async () => {
260270
if (
261271
this.deploymentManagement === DeploymentManagementMode.AUTO ||
262-
network.networkMonitor.poiDisputeMonitoringEnabled()
272+
network.networkMonitor.poiDisputeMonitoringEnabled() ||
273+
network.specification.indexerOptions.enableDips
263274
) {
264275
logger.trace('Fetching active deployments')
265276
const assignments =
@@ -487,9 +498,40 @@ export class Agent {
487498
}
488499
break
489500
case DeploymentManagementMode.MANUAL:
490-
this.logger.debug(
491-
`Skipping subgraph deployment reconciliation since DeploymentManagementMode = 'manual'`,
492-
)
501+
if (network.specification.indexerOptions.enableDips) {
502+
// Reconcile DIPs deployments anyways
503+
this.logger.warn(
504+
`Deployment management is manual, but DIPs is enabled. Reconciling DIPs deployments anyways.`,
505+
)
506+
if (!operator.dipsManager) {
507+
throw new Error('DipsManager is not available')
508+
}
509+
const dipsDeployments =
510+
await operator.dipsManager.getActiveDipsDeployments()
511+
const newTargetDeployments = new Set([
512+
...activeDeployments,
513+
...dipsDeployments,
514+
])
515+
try {
516+
await this.reconcileDeployments(
517+
activeDeployments,
518+
Array.from(newTargetDeployments),
519+
eligibleAllocations,
520+
)
521+
} catch (err) {
522+
logger.warn(
523+
`Exited early while reconciling deployments. Skipped reconciling actions.`,
524+
{
525+
err: indexerError(IndexerErrorCode.IE005, err),
526+
},
527+
)
528+
return
529+
}
530+
} else {
531+
this.logger.debug(
532+
`Skipping subgraph deployment reconciliation since DeploymentManagementMode = 'manual'`,
533+
)
534+
}
493535
break
494536
default:
495537
throw new Error(
@@ -810,6 +852,7 @@ export class Agent {
810852
maxAllocationEpochs: number,
811853
network: Network,
812854
operator: Operator,
855+
forceAction: boolean = false,
813856
): Promise<void> {
814857
const logger = this.logger.child({
815858
deployment: deploymentAllocationDecision.deployment.ipfsHash,
@@ -831,6 +874,7 @@ export class Agent {
831874
logger,
832875
deploymentAllocationDecision,
833876
activeDeploymentAllocations,
877+
forceAction,
834878
)
835879
case true: {
836880
// If no active allocations and subgraph health passes safety check, create one
@@ -867,6 +911,7 @@ export class Agent {
867911
logger,
868912
deploymentAllocationDecision,
869913
mostRecentlyClosedAllocation,
914+
forceAction,
870915
)
871916
}
872917
} else if (activeDeploymentAllocations.length > 0) {
@@ -875,6 +920,7 @@ export class Agent {
875920
logger,
876921
deploymentAllocationDecision,
877922
activeDeploymentAllocations,
923+
forceAction,
878924
)
879925
} else {
880926
// Refresh any expiring allocations
@@ -891,6 +937,7 @@ export class Agent {
891937
logger,
892938
deploymentAllocationDecision,
893939
expiringAllocations,
940+
forceAction,
894941
)
895942
}
896943
}
@@ -910,21 +957,37 @@ export class Agent {
910957
// --------------------------------------------------------------------------------
911958
const { network, operator } = this.networkAndOperator
912959
let validatedAllocationDecisions = [...allocationDecisions]
960+
let dipsDeployments: SubgraphDeploymentID[] = []
961+
if (network.specification.indexerOptions.enableDips) {
962+
if (!operator.dipsManager) {
963+
throw new Error('DipsManager is not available')
964+
}
965+
dipsDeployments = await operator.dipsManager.getActiveDipsDeployments()
966+
}
913967

914968
if (
915969
network.specification.indexerOptions.allocationManagementMode ===
916970
AllocationManagementMode.MANUAL
917971
) {
918-
this.logger.debug(
919-
`Skipping allocation reconciliation since AllocationManagementMode = 'manual'`,
920-
{
921-
protocolNetwork: network.specification.networkIdentifier,
922-
targetDeployments: allocationDecisions
923-
.filter(decision => decision.toAllocate)
924-
.map(decision => decision.deployment.ipfsHash),
925-
},
926-
)
927-
validatedAllocationDecisions = [] as AllocationDecision[]
972+
if (network.specification.indexerOptions.enableDips) {
973+
this.logger.warn(
974+
`Allocation management is manual, but DIPs is enabled. Reconciling DIPs allocations anyways.`,
975+
)
976+
validatedAllocationDecisions = validatedAllocationDecisions.filter(
977+
decision => dipsDeployments.includes(decision.deployment),
978+
)
979+
} else {
980+
this.logger.trace(
981+
`Skipping allocation reconciliation since AllocationManagementMode = 'manual'`,
982+
{
983+
protocolNetwork: network.specification.networkIdentifier,
984+
targetDeployments: allocationDecisions
985+
.filter(decision => decision.toAllocate)
986+
.map(decision => decision.deployment.ipfsHash),
987+
},
988+
)
989+
validatedAllocationDecisions = [] as AllocationDecision[]
990+
}
928991
} else {
929992
const networkSubgraphDeployment = network.networkSubgraph.deployment
930993
if (
@@ -985,6 +1048,7 @@ export class Agent {
9851048
maxAllocationEpochs,
9861049
network,
9871050
operator,
1051+
dipsDeployments.includes(decision.deployment), // Force actions if this is a DIPs deployment
9881052
),
9891053
)
9901054
return

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

+28
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,26 @@ export const start = {
308308
default: 1,
309309
group: 'Indexer Infrastructure',
310310
})
311+
.option('enable-dips', {
312+
description: 'Whether to enable Indexing Fees (DIPs)',
313+
type: 'boolean',
314+
default: false,
315+
group: 'Indexing Fees ("DIPs")',
316+
})
317+
.option('dipper-endpoint', {
318+
description: 'Gateway endpoint for DIPs receipts',
319+
type: 'string',
320+
array: false,
321+
required: false,
322+
group: 'Indexing Fees ("DIPs")',
323+
})
324+
.option('dips-allocation-amount', {
325+
description: 'Amount of GRT to allocate for DIPs',
326+
type: 'number',
327+
default: 1,
328+
required: false,
329+
group: 'Indexing Fees ("DIPs")',
330+
})
311331
.check(argv => {
312332
if (
313333
!argv['network-subgraph-endpoint'] &&
@@ -335,6 +355,9 @@ export const start = {
335355
) {
336356
return 'Invalid --rebate-claim-max-batch-size provided. Must be > 0 and an integer.'
337357
}
358+
if (argv['enable-dips'] && !argv['dipper-endpoint']) {
359+
return 'Invalid --dipper-endpoint provided. Must be provided when --enable-dips is true.'
360+
}
338361
return true
339362
})
340363
},
@@ -370,6 +393,10 @@ export async function createNetworkSpecification(
370393
allocateOnNetworkSubgraph: argv.allocateOnNetworkSubgraph,
371394
register: argv.register,
372395
finalityTime: argv.chainFinalizeTime,
396+
enableDips: argv.enableDips,
397+
dipperEndpoint: argv.dipperEndpoint,
398+
dipsAllocationAmount: argv.dipsAllocationAmount,
399+
dipsEpochsMargin: argv.dipsEpochsMargin,
373400
}
374401

375402
const transactionMonitoring = {
@@ -587,6 +614,7 @@ export async function run(
587614
const network = await Network.create(
588615
logger,
589616
networkSpecification,
617+
managementModels,
590618
queryFeeModels,
591619
graphNode,
592620
metrics,

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

+1
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/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
"clean": "rm -rf ./node_modules ./dist ./tsconfig.tsbuildinfo"
2323
},
2424
"dependencies": {
25+
"@bufbuild/protobuf": "2.2.3",
2526
"@graphprotocol/common-ts": "2.0.11",
27+
"@graphprotocol/dips-proto": "0.2.2",
28+
"@grpc/grpc-js": "^1.12.6",
2629
"@pinax/graph-networks-registry": "0.6.7",
2730
"@semiotic-labs/tap-contracts-bindings": "^1.2.1",
2831
"@thi.ng/heaps": "1.2.38",

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

+3
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

+3
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/allocations/escrow-accounts.ts

+31
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ export type EscrowAccountResponse = {
1313
}[]
1414
}
1515

16+
export type EscrowSenderResponse = {
17+
signer: {
18+
sender: {
19+
id: string
20+
}
21+
}
22+
}
23+
1624
export class EscrowAccounts {
1725
constructor(private sendersBalances: Map<Address, U256>) {}
1826

@@ -65,3 +73,26 @@ export const getEscrowAccounts = async (
6573
}
6674
return EscrowAccounts.fromResponse(result.data)
6775
}
76+
77+
export const getEscrowSenderForSigner = async (
78+
tapSubgraph: SubgraphClient,
79+
signer: Address,
80+
): Promise<Address> => {
81+
const signerLower = signer.toLowerCase()
82+
const result = await tapSubgraph.query<EscrowSenderResponse>(
83+
gql`
84+
query EscrowAccountQuery($signer: ID!) {
85+
signer(id: $signer) {
86+
sender {
87+
id
88+
}
89+
}
90+
}
91+
`,
92+
{ signer: signerLower },
93+
)
94+
if (!result.data) {
95+
throw `There was an error while querying Tap Subgraph. Errors: ${result.error}`
96+
}
97+
return toAddress(result.data.signer.sender.id)
98+
}

packages/indexer-common/src/graph-node.ts

+16
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,22 @@ export class GraphNode {
708708
}
709709
}
710710

711+
public async entityCount(deployments: SubgraphDeploymentID[]): Promise<number[]> {
712+
// Query the entity count for each deployment using the indexingStatuses query
713+
const query = `
714+
query entityCounts($deployments: [String!]!) {
715+
indexingStatuses(subgraphs: $deployments) {
716+
entityCount
717+
}
718+
}
719+
`
720+
const result = await this.status
721+
.query(query, { deployments: deployments.map((id) => id.ipfsHash) })
722+
.toPromise()
723+
724+
return result.data.indexingStatuses.map((status) => status.entityCount) as number[]
725+
}
726+
711727
public async proofOfIndexing(
712728
deployment: SubgraphDeploymentID,
713729
block: BlockPointer,

packages/indexer-common/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './allocations'
33
export * from './async-cache'
44
export * from './errors'
55
export * from './indexer-management'
6+
export * from './indexing-fees'
67
export * from './graph-node'
78
export * from './operator'
89
export * from './network'
@@ -16,3 +17,4 @@ export * from './utils'
1617
export * from './parsers'
1718
export * as specification from './network-specification'
1819
export * from './sequential-timer'
20+
export * from './indexing-fees'

0 commit comments

Comments
 (0)