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

// Pure-function coverage for the guard that stops the post-close rule stamp
// (never on the queued path, offchain on the direct path) from overwriting a
// DIPS rule, which the DIPS module reads as a blocklist.
describe('ruleAfterClose', () => {
const deployment = new SubgraphDeploymentID(
'QmXZiV6S13ha6QXq4dmaM3TB4CHcDxBMvGexSNu9Kc28EH',
)
const protocolNetwork = 'eip155:42161'
const stampBases = [
IndexingDecisionBasis.NEVER,
IndexingDecisionBasis.OFFCHAIN,
] as const

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

it('keeps a DIPS rule by returning no rule to write, for either stamp', () => {
for (const basis of stampBases) {
expect(
ruleAfterClose(
rule(IndexingDecisionBasis.DIPS),
deployment,
protocolNetwork,
basis,
),
).toBeNull()
}
})

it('stamps the requested basis when the deployment has no rule', () => {
for (const basis of stampBases) {
expect(ruleAfterClose(null, deployment, protocolNetwork, basis)).toEqual({
identifier: deployment.ipfsHash,
protocolNetwork,
identifierType: SubgraphIdentifierType.DEPLOYMENT,
decisionBasis: basis,
})
}
})

it('stamps over non-DIPS rules so a close still sticks', () => {
for (const existing of [
IndexingDecisionBasis.ALWAYS,
IndexingDecisionBasis.NEVER,
IndexingDecisionBasis.OFFCHAIN,
IndexingDecisionBasis.RULES,
]) {
expect(
ruleAfterClose(
rule(existing),
deployment,
protocolNetwork,
IndexingDecisionBasis.NEVER,
)?.decisionBasis,
).toBe(IndexingDecisionBasis.NEVER)
}
})
})
47 changes: 40 additions & 7 deletions packages/indexer-common/src/indexer-management/allocations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,26 @@ export function encodeCollectData(allocationId: string, poiData: POIData): strin
return encodeCollectIndexingRewardsData(allocationId, poiData.poi, encodedPOIMetadata)
}

// The rule stamped after a close must not overwrite a DIPS rule: the DIPS module
// reads both `never` and `offchain` as a blocklist that cancels agreements
// on-chain. Ending an agreement stays explicit (`rules never`), not a side effect.
export function ruleAfterClose(
existingRule: IndexingRuleAttributes | null,
deployment: SubgraphDeploymentID,
protocolNetwork: string,
decisionBasis: IndexingDecisionBasis.NEVER | IndexingDecisionBasis.OFFCHAIN,
): Partial<IndexingRuleAttributes> | null {
if (existingRule?.decisionBasis === IndexingDecisionBasis.DIPS) {
return null
}
return {
identifier: deployment.ipfsHash,
protocolNetwork,
identifierType: SubgraphIdentifierType.DEPLOYMENT,
decisionBasis,
}
}

export class AllocationManager {
declare dipsManager: DipsManager | null
constructor(
Expand Down Expand Up @@ -915,14 +935,27 @@ export class AllocationManager {
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>
const existingRule = await this.models.IndexingRule.findOne({
where: {
identifier: allocation.subgraphDeployment.id.ipfsHash,
protocolNetwork: this.network.specification.networkIdentifier,
},
})
const neverIndexingRule = ruleAfterClose(
existingRule,
allocation.subgraphDeployment.id,
this.network.specification.networkIdentifier,
IndexingDecisionBasis.NEVER,
)

await upsertIndexingRule(logger, this.models, neverIndexingRule)
if (neverIndexingRule) {
await upsertIndexingRule(logger, this.models, neverIndexingRule)
} else {
logger.info(
`Deployment is managed by an indexing agreement, keeping its DIPS rule instead of stamping never`,
{ deployment: allocation.subgraphDeployment.id.ipfsHash },
)
}

return {
actionID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
IndexingRuleAttributes,
Network,
POIData,
ruleAfterClose,
SubgraphClient,
SubgraphIdentifierType,
uniqueAllocationID,
Expand Down Expand Up @@ -939,23 +940,36 @@ export default {
logger.debug(
`Updating indexing rules, so indexer-agent keeps the deployment synced but doesn't allocate to it`,
)
const offchainIndexingRule = {
protocolNetwork: network.specification.networkIdentifier,
identifier: allocationData.subgraphDeployment.id.ipfsHash,
identifierType: SubgraphIdentifierType.DEPLOYMENT,
decisionBasis: IndexingDecisionBasis.OFFCHAIN,
} as Partial<IndexingRuleAttributes>

await models.IndexingRule.upsert(offchainIndexingRule)

// Since upsert succeeded, we _must_ have a rule
const updatedRule = await models.IndexingRule.findOne({
where: { identifier: offchainIndexingRule.identifier },
})
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
logger.info(`DecisionBasis.OFFCHAIN rule merged into indexing rules`, {
rule: updatedRule,
const existingRule = await models.IndexingRule.findOne({
where: {
identifier: allocationData.subgraphDeployment.id.ipfsHash,
protocolNetwork: network.specification.networkIdentifier,
},
})
const offchainIndexingRule = ruleAfterClose(
existingRule,
allocationData.subgraphDeployment.id,
network.specification.networkIdentifier,
IndexingDecisionBasis.OFFCHAIN,
)

if (offchainIndexingRule) {
await models.IndexingRule.upsert(offchainIndexingRule)

// Since upsert succeeded, we _must_ have a rule
const updatedRule = await models.IndexingRule.findOne({
where: { identifier: offchainIndexingRule.identifier },
})
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
logger.info(`DecisionBasis.OFFCHAIN rule merged into indexing rules`, {
rule: updatedRule,
})
} else {
logger.info(
`Deployment is managed by an indexing agreement, keeping its DIPS rule instead of stamping offchain`,
{ deployment: allocationData.subgraphDeployment.id.ipfsHash },
)
}

return {
actionID: 0,
Expand Down
Loading