Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { SubgraphDeploymentID } from '@graphprotocol/common-ts'
import { findRuleRequestingAllocation } from '../allocations'
import { IndexingDecisionBasis, IndexingRuleAttributes } from '../models'
import { SubgraphIdentifierType } from '../../subgraphs'

// Pure-function coverage for the guard that stops confirmUnallocate stamping a
// `never` rule over an operator rule that currently requests allocation.
describe('findRuleRequestingAllocation', () => {
const deployment = new SubgraphDeploymentID(
'QmXZiV6S13ha6QXq4dmaM3TB4CHcDxBMvGexSNu9Kc28EH',
)
const otherDeployment = new SubgraphDeploymentID(
'QmRKs2ZfuwvmZA3QAWmCqrGUjV9pxtBUDP3wuc6iVGnjA2',
)

const rule = (overrides: Partial<IndexingRuleAttributes>) =>
({
identifier: deployment.ipfsHash,
identifierType: SubgraphIdentifierType.DEPLOYMENT,
decisionBasis: IndexingDecisionBasis.ALWAYS,
...overrides,
}) as IndexingRuleAttributes

it('finds an always rule for the deployment', () => {
const found = findRuleRequestingAllocation([rule({})], deployment)
expect(found?.decisionBasis).toBe(IndexingDecisionBasis.ALWAYS)
})

it('finds a dips rule for the deployment', () => {
const found = findRuleRequestingAllocation(
[rule({ decisionBasis: IndexingDecisionBasis.DIPS })],
deployment,
)
expect(found?.decisionBasis).toBe(IndexingDecisionBasis.DIPS)
})

it('matches a rule whose identifier is stored in bytes32 form', () => {
const found = findRuleRequestingAllocation(
[rule({ identifier: deployment.bytes32 })],
deployment,
)
expect(found).toBeDefined()
})

it('ignores opt-out and default bases', () => {
for (const basis of [
IndexingDecisionBasis.NEVER,
IndexingDecisionBasis.OFFCHAIN,
IndexingDecisionBasis.RULES,
]) {
expect(
findRuleRequestingAllocation([rule({ decisionBasis: basis })], deployment),
).toBeUndefined()
}
})

it('ignores rules for other deployments', () => {
expect(
findRuleRequestingAllocation(
[rule({ identifier: otherDeployment.ipfsHash })],
deployment,
),
).toBeUndefined()
})

it('ignores subgraph-type rules and returns undefined when no rules exist', () => {
expect(
findRuleRequestingAllocation(
[rule({ identifierType: SubgraphIdentifierType.SUBGRAPH })],
deployment,
),
).toBeUndefined()
expect(findRuleRequestingAllocation([], deployment)).toBeUndefined()
})
})
56 changes: 46 additions & 10 deletions packages/indexer-common/src/indexer-management/allocations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,22 @@ export function encodeCollectData(allocationId: string, poiData: POIData): strin
return encodeCollectIndexingRewardsData(allocationId, poiData.poi, encodedPOIMetadata)
}

// A queued close can outlive the rule that justified it. Find a deployment
// rule that currently asks for allocation (operator `always` or DIPs-managed);
// stamping `never` over it would also blocklist the deployment for DIPs.
export function findRuleRequestingAllocation(
rules: IndexingRuleAttributes[],
deployment: SubgraphDeploymentID,
): IndexingRuleAttributes | undefined {
return rules.find(
(rule) =>
rule.identifierType === SubgraphIdentifierType.DEPLOYMENT &&
new SubgraphDeploymentID(rule.identifier).bytes32 === deployment.bytes32 &&
(rule.decisionBasis === IndexingDecisionBasis.ALWAYS ||
rule.decisionBasis === IndexingDecisionBasis.DIPS),
)
}

export class AllocationManager {
declare dipsManager: DipsManager | null
constructor(
Expand Down Expand Up @@ -911,18 +927,38 @@ export class AllocationManager {
})
const allocation = await this.network.networkMonitor.allocation(allocationID)

// Upsert a rule so the agent keeps the deployment synced but doesn't allocate to it
logger.debug(
`Updating indexing rules so indexer-agent keeps the deployment synced but doesn't allocate to it`,
// The rule that justified this close may have changed while the action
// sat in the queue: an operator flipping the deployment back to `always`
// (or DIPs taking it over) must not be overwritten with `never`.
const currentRules = await this.models.IndexingRule.findAll({
where: { protocolNetwork: this.network.specification.networkIdentifier },
})
const allocateRule = findRuleRequestingAllocation(
currentRules,
allocation.subgraphDeployment.id,
)
const neverIndexingRule = {
identifier: allocation.subgraphDeployment.id.ipfsHash,
protocolNetwork: this.network.specification.networkIdentifier,
identifierType: SubgraphIdentifierType.DEPLOYMENT,
decisionBasis: IndexingDecisionBasis.NEVER,
} as Partial<IndexingRuleAttributes>
if (allocateRule) {
logger.info(
`Keeping the current indexing rule: it requests allocation, so skipping the never-rule stamp after this close`,
{
deployment: allocation.subgraphDeployment.id.ipfsHash,
currentDecisionBasis: allocateRule.decisionBasis,
},
)
} else {
// Upsert a rule so the agent keeps the deployment synced but doesn't allocate to it
logger.debug(
`Updating indexing rules so indexer-agent keeps the deployment synced but doesn't allocate to it`,
)
const neverIndexingRule = {
identifier: allocation.subgraphDeployment.id.ipfsHash,
protocolNetwork: this.network.specification.networkIdentifier,
identifierType: SubgraphIdentifierType.DEPLOYMENT,
decisionBasis: IndexingDecisionBasis.NEVER,
} as Partial<IndexingRuleAttributes>

await upsertIndexingRule(logger, this.models, neverIndexingRule)
await upsertIndexingRule(logger, this.models, neverIndexingRule)
}

return {
actionID,
Expand Down
Loading