From 1dc1a9399854961c3524605c8f3751c200b6b031 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 09:59:04 -0800 Subject: [PATCH 01/21] refactor: remove legacy allocation support --- packages/indexer-agent/src/agent.ts | 14 +- .../indexer-common/src/allocations/keys.ts | 13 - .../src/indexer-management/allocations.ts | 742 +++++------------- .../src/indexer-management/monitor.ts | 111 +-- .../resolvers/allocations.ts | 664 +--------------- .../resolvers/indexer-status.ts | 75 +- packages/indexer-common/src/network.ts | 93 +-- 7 files changed, 305 insertions(+), 1407 deletions(-) diff --git a/packages/indexer-agent/src/agent.ts b/packages/indexer-agent/src/agent.ts index f9f93d066..872df17d0 100644 --- a/packages/indexer-agent/src/agent.ts +++ b/packages/indexer-agent/src/agent.ts @@ -924,17 +924,9 @@ export class Agent { expiredAllocations, async (allocation: Allocation) => { try { - if (allocation.isLegacy) { - const onChainAllocation = - await network.contracts.LegacyStaking.getAllocation(allocation.id) - return onChainAllocation.closedAtEpoch == 0n - } else { - const onChainAllocation = - await network.contracts.SubgraphService.getAllocation( - allocation.id, - ) - return onChainAllocation.closedAt == 0n - } + const onChainAllocation = + await network.contracts.SubgraphService.getAllocation(allocation.id) + return onChainAllocation.closedAt == 0n } catch (err) { this.logger.warn( `Failed to cross-check allocation state with contracts; assuming it needs to be closed`, diff --git a/packages/indexer-common/src/allocations/keys.ts b/packages/indexer-common/src/allocations/keys.ts index e3dcc7d1b..d00b8e82e 100644 --- a/packages/indexer-common/src/allocations/keys.ts +++ b/packages/indexer-common/src/allocations/keys.ts @@ -90,19 +90,6 @@ export const uniqueAllocationID = ( throw new Error(`Exhausted limit of 100 parallel allocations`) } -export const legacyAllocationIdProof = ( - signer: Signer, - indexerAddress: string, - allocationId: string, -): Promise => { - const messageHash = solidityPackedKeccak256( - ['address', 'address'], - [indexerAddress, allocationId], - ) - const messageHashBytes = getBytes(messageHash) - return signer.signMessage(messageHashBytes) -} - export const EIP712_ALLOCATION_ID_PROOF_TYPES = { AllocationIdProof: [ { name: 'indexer', type: 'address' }, diff --git a/packages/indexer-common/src/indexer-management/allocations.ts b/packages/indexer-common/src/indexer-management/allocations.ts index 8170a2855..87a876ff6 100644 --- a/packages/indexer-common/src/indexer-management/allocations.ts +++ b/packages/indexer-common/src/indexer-management/allocations.ts @@ -10,7 +10,6 @@ import { ActionFailure, ActionType, Allocation, - legacyAllocationIdProof, AllocationResult, AllocationStatus, CloseAllocationResult, @@ -52,7 +51,6 @@ import { import { BigNumberish, BytesLike, - ContractTransaction, hexlify, Result, TransactionReceipt, @@ -720,29 +718,19 @@ export class AllocationManager { // Double-check whether the allocationID already exists on chain, to // avoid unnecessary transactions. - let allocationExistsSubgraphService = false - let allocationExistsStaking = false - const isHorizon = await this.network.isHorizon.value() - if (isHorizon) { - const allocation = - await this.network.contracts.SubgraphService.getAllocation(allocationId) - const legacyAllocation = - await this.network.contracts.SubgraphService.getLegacyAllocation(allocationId) - allocationExistsSubgraphService = allocation.createdAt !== 0n - allocationExistsStaking = legacyAllocation.indexer !== ZeroAddress - } else { - const state = - await this.network.contracts.LegacyStaking.getAllocationState(allocationId) - allocationExistsStaking = state !== 0n - } - - if (allocationExistsSubgraphService || allocationExistsStaking) { + const existingAllocation = + await this.network.contracts.SubgraphService.getAllocation(allocationId) + const existingLegacyAllocation = + await this.network.contracts.SubgraphService.getLegacyAllocation(allocationId) + const allocationExistsSubgraphService = existingAllocation.createdAt !== 0n + const allocationExistsLegacy = existingLegacyAllocation.indexer !== ZeroAddress + + if (allocationExistsSubgraphService || allocationExistsLegacy) { logger.debug(`Skipping allocation as it already exists onchain`, { indexer: this.network.specification.indexerOptions.address, allocation: allocationId, - isHorizon, allocationExistsSubgraphService, - allocationExistsStaking, + allocationExistsLegacy, }) throw indexerError( IndexerErrorCode.IE066, @@ -756,23 +744,16 @@ export class AllocationManager { indexerAddress: this.network.specification.indexerOptions.address, }) - const proof = isHorizon - ? await horizonAllocationIdProof( - allocationSigner, - Number(this.network.specification.networkIdentifier.split(':')[1]), - this.network.specification.indexerOptions.address, - allocationId, - this.network.contracts.SubgraphService.target.toString(), - ) - : await legacyAllocationIdProof( - allocationSigner, - this.network.specification.indexerOptions.address, - allocationId, - ) + const proof = await horizonAllocationIdProof( + allocationSigner, + Number(this.network.specification.networkIdentifier.split(':')[1]), + this.network.specification.indexerOptions.address, + allocationId, + this.network.contracts.SubgraphService.target.toString(), + ) logger.debug('Successfully generated allocation ID proof', { allocationIDProof: proof, - isLegacy: !isHorizon, }) return { @@ -792,39 +773,24 @@ export class AllocationManager { receipt: TransactionReceipt | 'paused' | 'unauthorized', ): Promise { const logger = this.logger.child({ action: actionID }) - const subgraphDeployment = new SubgraphDeploymentID(deployment) - const isLegacy = - (receipt as TransactionReceipt).to === this.network.contracts.HorizonStaking.target - logger.info(`Confirming allocation creation transaction`, { - isLegacy, - }) + logger.info(`Confirming allocation creation transaction`) if (receipt === 'paused' || receipt === 'unauthorized') { throw indexerError( IndexerErrorCode.IE062, - `Allocation not created. ${ receipt === 'paused' ? 'Network paused' : 'Operator not authorized' }`, ) } - const createAllocationEventLogs = isLegacy - ? this.network.transactionManager.findEvent( - 'AllocationCreated', - this.network.contracts.LegacyStaking.interface, - 'subgraphDeploymentID', - subgraphDeployment.bytes32, - receipt, - this.logger, - ) - : this.network.transactionManager.findEvent( - 'AllocationCreated', - this.network.contracts.SubgraphService.interface, - 'indexer', - this.network.specification.indexerOptions.address, - receipt, - logger, - ) + const createAllocationEventLogs = this.network.transactionManager.findEvent( + 'AllocationCreated', + this.network.contracts.SubgraphService.interface, + 'indexer', + this.network.specification.indexerOptions.address, + receipt, + logger, + ) if (!createAllocationEventLogs) { throw indexerError(IndexerErrorCode.IE014, `Allocation was never mined`) @@ -832,14 +798,9 @@ export class AllocationManager { logger.info(`Successfully allocated to subgraph deployment`, { amountGRT: formatGRT(createAllocationEventLogs.tokens), - allocation: isLegacy - ? createAllocationEventLogs.allocationID - : createAllocationEventLogs.allocationId, + allocation: createAllocationEventLogs.allocationId, deployment: createAllocationEventLogs.subgraphDeploymentID, - epoch: isLegacy - ? createAllocationEventLogs.epoch.toString() - : createAllocationEventLogs.currentEpoch.toString(), - isLegacy, + epoch: createAllocationEventLogs.currentEpoch.toString(), }) const subgraphDeploymentID = new SubgraphDeploymentID(deployment) @@ -864,9 +825,7 @@ export class AllocationManager { type: 'allocate', transactionID: receipt.hash, deployment: deployment, - allocation: isLegacy - ? createAllocationEventLogs.allocationID - : createAllocationEventLogs.allocationId, + allocation: createAllocationEventLogs.allocationId, allocatedTokens: amount, protocolNetwork: this.network.specification.networkIdentifier, } @@ -880,7 +839,6 @@ export class AllocationManager { actionID: number, protocolNetwork: string, ): Promise { - const isHorizon = await this.network.isHorizon.value() const params = await this.prepareAllocateParams(logger, context, deployment, amount) logger.debug(`Populating allocation creation transaction`, { indexer: params.indexer, @@ -888,41 +846,26 @@ export class AllocationManager { amount: formatGRT(params.tokens), allocation: params.allocationID, proof: params.proof, - isLegacy: !isHorizon, }) - let populatedTransaction: ContractTransaction - if (isHorizon) { - // Fail automatically if the indexer is not registered - // This provides a better ux during the transition period - const registrationData = await this.network.contracts.SubgraphService.indexers( + // Fail automatically if the indexer is not registered + const registrationData = await this.network.contracts.SubgraphService.indexers( + params.indexer, + ) + if (registrationData.url.length === 0) { + throw indexerError(IndexerErrorCode.IE086) + } + const encodedData = encodeStartServiceData( + params.subgraphDeploymentID.toString(), + BigInt(params.tokens), + params.allocationID, + params.proof.toString(), + ) + const populatedTransaction = + await this.network.contracts.SubgraphService.startService.populateTransaction( params.indexer, + encodedData, ) - if (registrationData.url.length === 0) { - throw indexerError(IndexerErrorCode.IE086) - } - const encodedData = encodeStartServiceData( - params.subgraphDeploymentID.toString(), - BigInt(params.tokens), - params.allocationID, - params.proof.toString(), - ) - populatedTransaction = - await this.network.contracts.SubgraphService.startService.populateTransaction( - params.indexer, - encodedData, - ) - } else { - populatedTransaction = - await this.network.contracts.LegacyStaking.allocateFrom.populateTransaction( - params.indexer, - params.subgraphDeploymentID, - params.tokens, - params.allocationID, - params.metadata, - params.proof, - ) - } return { protocolNetwork, actionID, @@ -959,19 +902,10 @@ export class AllocationManager { // Double-check whether the allocation is still active on chain, to // avoid unnecessary transactions. - if (allocation.isLegacy) { - const state = await this.network.contracts.HorizonStaking.getAllocationState( - allocation.id, - ) - if (state !== 1n) { - throw indexerError(IndexerErrorCode.IE065) - } - } else { - const allocation = - await this.network.contracts.SubgraphService.getAllocation(allocationID) - if (allocation.closedAt !== 0n) { - throw indexerError(IndexerErrorCode.IE065) - } + const onChainAllocation = + await this.network.contracts.SubgraphService.getAllocation(allocationID) + if (onChainAllocation.closedAt !== 0n) { + throw indexerError(IndexerErrorCode.IE065) } return { @@ -990,13 +924,8 @@ export class AllocationManager { receipt: TransactionReceipt | 'paused' | 'unauthorized', ): Promise { const logger = this.logger.child({ action: actionID }) - const isLegacy = - (receipt as TransactionReceipt).to === this.network.contracts.HorizonStaking.target - const isHorizon = await this.network.isHorizon.value() - logger.info(`Confirming unallocate transaction`, { - isLegacy, - }) + logger.info(`Confirming unallocate transaction`) if (receipt === 'paused' || receipt === 'unauthorized') { throw indexerError( @@ -1005,23 +934,14 @@ export class AllocationManager { ) } - const closeAllocationEventLogs = isLegacy - ? this.network.transactionManager.findEvent( - 'AllocationClosed', - this.network.contracts.LegacyStaking.interface, - 'allocationID', - allocationID, - receipt, - this.logger, - ) - : this.network.transactionManager.findEvent( - 'AllocationClosed', - this.network.contracts.SubgraphService.interface, - 'allocationId', - allocationID, - receipt, - this.logger, - ) + const closeAllocationEventLogs = this.network.transactionManager.findEvent( + 'AllocationClosed', + this.network.contracts.SubgraphService.interface, + 'allocationId', + allocationID, + receipt, + this.logger, + ) if (!closeAllocationEventLogs) { throw indexerError( @@ -1030,38 +950,23 @@ export class AllocationManager { ) } - const rewardsEventLogs = isLegacy - ? this.network.transactionManager.findEvent( - isHorizon ? 'HorizonRewardsAssigned' : 'RewardsAssigned', - this.network.contracts.RewardsManager.interface, - 'allocationID', - allocationID, - receipt, - this.logger, - ) - : this.network.transactionManager.findEvent( - 'IndexingRewardsCollected', - this.network.contracts.SubgraphService.interface, - 'allocationId', - allocationID, - receipt, - this.logger, - ) + const rewardsEventLogs = this.network.transactionManager.findEvent( + 'IndexingRewardsCollected', + this.network.contracts.SubgraphService.interface, + 'allocationId', + allocationID, + receipt, + this.logger, + ) - const rewardsAssigned = rewardsEventLogs - ? isLegacy - ? rewardsEventLogs.amount - : rewardsEventLogs.tokensIndexerRewards - : 0 + const rewardsAssigned = rewardsEventLogs ? rewardsEventLogs.tokensIndexerRewards : 0 if (rewardsAssigned == 0) { logger.warn('No rewards were distributed upon closing the allocation') } const subgraphDeploymentID = new SubgraphDeploymentID( - isLegacy - ? closeAllocationEventLogs.subgraphDeploymentID - : closeAllocationEventLogs.subgraphDeploymentId, + closeAllocationEventLogs.subgraphDeploymentId, ) logger.info(`Successfully closed allocation`, { @@ -1111,56 +1016,40 @@ export class AllocationManager { poiData: params.poi, }) - if (params.isLegacy) { - const tx = - await this.network.contracts.HorizonStaking.closeAllocation.populateTransaction( - params.allocationID, - params.poi.poi, - ) - return { - protocolNetwork: params.protocolNetwork, - actionID: params.actionID, - ...tx, - } - } else { - // Horizon: Need to multicall collect and stopService - - // collect - const collectIndexingRewardsData = encodeCollectIndexingRewardsData( - params.allocationID, - params.poi.poi, - encodePOIMetadata( - params.poi.blockNumber, - params.poi.publicPOI, - params.poi.indexingStatus, - 0, - 0, - ), - ) - const collectCallData = - this.network.contracts.SubgraphService.interface.encodeFunctionData('collect', [ - params.indexer, - PaymentTypes.IndexingRewards, - collectIndexingRewardsData, - ]) - - // stopService - const stopServiceCallData = - this.network.contracts.SubgraphService.interface.encodeFunctionData( - 'stopService', - [params.indexer, encodeStopServiceData(params.allocationID)], - ) + // Multicall collect and stopService + const collectIndexingRewardsData = encodeCollectIndexingRewardsData( + params.allocationID, + params.poi.poi, + encodePOIMetadata( + params.poi.blockNumber, + params.poi.publicPOI, + params.poi.indexingStatus, + 0, + 0, + ), + ) + const collectCallData = + this.network.contracts.SubgraphService.interface.encodeFunctionData('collect', [ + params.indexer, + PaymentTypes.IndexingRewards, + collectIndexingRewardsData, + ]) - const tx = - await this.network.contracts.SubgraphService.multicall.populateTransaction([ - collectCallData, - stopServiceCallData, - ]) - return { - protocolNetwork: params.protocolNetwork, - actionID: params.actionID, - ...tx, - } + const stopServiceCallData = + this.network.contracts.SubgraphService.interface.encodeFunctionData('stopService', [ + params.indexer, + encodeStopServiceData(params.allocationID), + ]) + + const tx = + await this.network.contracts.SubgraphService.multicall.populateTransaction([ + collectCallData, + stopServiceCallData, + ]) + return { + protocolNetwork: params.protocolNetwork, + actionID: params.actionID, + ...tx, } } @@ -1247,24 +1136,11 @@ export class AllocationManager { // Double-check whether the allocation is still active on chain, to // avoid unnecessary transactions. - if (allocation.isLegacy) { - const state = await this.network.contracts.HorizonStaking.getAllocationState( - allocation.id, - ) - if (state !== 1n) { - logger.warn(`Allocation has already been closed`) - throw indexerError( - IndexerErrorCode.IE065, - `Legacy allocation has already been closed`, - ) - } - } else { - const allocationData = - await this.network.contracts.SubgraphService.getAllocation(allocationID) - if (allocationData.closedAt !== 0n) { - logger.warn(`Allocation has already been closed`) - throw indexerError(IndexerErrorCode.IE065, `Allocation has already been closed`) - } + const onChainAllocation = + await this.network.contracts.SubgraphService.getAllocation(allocationID) + if (onChainAllocation.closedAt !== 0n) { + logger.warn(`Allocation has already been closed`) + throw indexerError(IndexerErrorCode.IE065, `Allocation has already been closed`) } if (amount < 0n) { @@ -1296,56 +1172,32 @@ export class AllocationManager { // Double-check whether the allocationID already exists on chain, to // avoid unnecessary transactions. - const isHorizon = await this.network.isHorizon.value() - if (isHorizon) { - const allocationData = - await this.network.contracts.SubgraphService.getAllocation(newAllocationId) - if (allocationData.createdAt !== 0n) { - logger.warn(`Skipping allocation as it already exists onchain`, { - indexer: this.network.specification.indexerOptions.address, - allocation: newAllocationId, - allocationData, - isHorizon, - }) - throw indexerError(IndexerErrorCode.IE066, 'AllocationID already exists') - } - } else { - const newAllocationState = - await this.network.contracts.HorizonStaking.getAllocationState(newAllocationId) - if (newAllocationState !== 0n) { - logger.warn(`Skipping allocation as it already exists onchain (legacy)`, { - indexer: this.network.specification.indexerOptions.address, - allocation: newAllocationId, - newAllocationState, - isHorizon, - }) - throw indexerError(IndexerErrorCode.IE066, 'Legacy AllocationID already exists') - } + const allocationData = + await this.network.contracts.SubgraphService.getAllocation(newAllocationId) + if (allocationData.createdAt !== 0n) { + logger.warn(`Skipping allocation as it already exists onchain`, { + indexer: this.network.specification.indexerOptions.address, + allocation: newAllocationId, + allocationData, + }) + throw indexerError(IndexerErrorCode.IE066, 'AllocationID already exists') } logger.debug('Generating new allocation ID proof', { newAllocationSigner: allocationSigner, newAllocationID: newAllocationId, indexerAddress: this.network.specification.indexerOptions.address, - isHorizon, }) - const proof = isHorizon - ? await horizonAllocationIdProof( - allocationSigner, - Number(this.network.specification.networkIdentifier.split(':')[1]), - this.network.specification.indexerOptions.address, - newAllocationId, - this.network.contracts.SubgraphService.target.toString(), - ) - : await legacyAllocationIdProof( - allocationSigner, - this.network.specification.indexerOptions.address, - newAllocationId, - ) + const proof = await horizonAllocationIdProof( + allocationSigner, + Number(this.network.specification.networkIdentifier.split(':')[1]), + this.network.specification.indexerOptions.address, + newAllocationId, + this.network.contracts.SubgraphService.target.toString(), + ) logger.debug('Successfully generated allocation ID proof', { allocationIDProof: proof, - isHorizon, }) logger.info(`Prepared close and allocate multicall transaction`, { @@ -1381,114 +1233,55 @@ export class AllocationManager { receipts: TransactionReceipt[], ): Promise { const logger = this.logger.child({ action: actionID }) - const isHorizon = await this.network.isHorizon.value() logger.info(`Confirming reallocate transaction`, { allocationID, - isHorizon, receiptCount: receipts.length, }) - // We need to find the close and create allocation events and obtain the subgraph deployment ID and rewards assigned - // We could have one receipt per contract (Horizon Staking and Subgraph Service) so we need to check both targets + // Find close and create allocation events from SubgraphService contract let closeAllocationEventLogs: Result | undefined let createAllocationEventLogs: Result | undefined let subgraphDeploymentID: SubgraphDeploymentID | undefined let rewardsAssigned = 0n + for (const receipt of receipts) { - // Staking contract - if (receipt.to === this.network.contracts.HorizonStaking.target) { - const stakingCloseAllocationEventLogs = this.network.transactionManager.findEvent( - 'AllocationClosed', - this.network.contracts.LegacyStaking.interface, - 'allocationID', + const closeEventLogs = this.network.transactionManager.findEvent( + 'AllocationClosed', + this.network.contracts.SubgraphService.interface, + 'allocationId', + allocationID, + receipt, + this.logger, + ) + if (closeEventLogs !== undefined) { + closeAllocationEventLogs = closeEventLogs + + const rewardsEventLogs = this.network.transactionManager.findEvent( + 'IndexingRewardsCollected', + this.network.contracts.SubgraphService.interface, + 'allocationId', allocationID, receipt, this.logger, ) - - if (stakingCloseAllocationEventLogs !== undefined) { - closeAllocationEventLogs = stakingCloseAllocationEventLogs - - const rewardsEventLogs = this.network.transactionManager.findEvent( - isHorizon ? 'HorizonRewardsAssigned' : 'RewardsAssigned', - this.network.contracts.RewardsManager.interface, - 'allocationID', - allocationID, - receipt, - this.logger, - ) - if (rewardsEventLogs !== undefined) { - rewardsAssigned = rewardsEventLogs.amount - } - - subgraphDeploymentID = new SubgraphDeploymentID( - stakingCloseAllocationEventLogs.subgraphDeploymentID, - ) - - // not possible to create legacy allocation if what was closed was not a legacy allocation - const stakingCreateAllocationEventLogs = - this.network.transactionManager.findEvent( - 'AllocationCreated', - this.network.contracts.LegacyStaking.interface, - 'subgraphDeploymentID', - stakingCloseAllocationEventLogs.subgraphDeploymentID, - receipt, - this.logger, - ) - - if (stakingCreateAllocationEventLogs !== undefined) { - createAllocationEventLogs = stakingCreateAllocationEventLogs - } + if (rewardsEventLogs !== undefined) { + rewardsAssigned = rewardsEventLogs.tokensIndexerRewards } - } - // Subgraph Service contract - else { - // Subgraph Service contract - // Possible transactions to handle: - // - collect + stopService for a new allocation - // - startService for a new allocation - const subgraphServiceCloseAllocationEventLogs = - this.network.transactionManager.findEvent( - 'AllocationClosed', - this.network.contracts.SubgraphService.interface, - 'allocationId', - allocationID, - receipt, - this.logger, - ) - if (subgraphServiceCloseAllocationEventLogs !== undefined) { - closeAllocationEventLogs = subgraphServiceCloseAllocationEventLogs - - const rewardsEventLogs = this.network.transactionManager.findEvent( - 'IndexingRewardsCollected', - this.network.contracts.SubgraphService.interface, - 'allocationId', - allocationID, - receipt, - this.logger, - ) - if (rewardsEventLogs !== undefined) { - rewardsAssigned = rewardsEventLogs.tokensIndexerRewards - } - subgraphDeploymentID = new SubgraphDeploymentID( - subgraphServiceCloseAllocationEventLogs.subgraphDeploymentId, - ) - } + subgraphDeploymentID = new SubgraphDeploymentID(closeEventLogs.subgraphDeploymentId) + } - const subgraphServiceCreateAllocationEventLogs = - this.network.transactionManager.findEvent( - 'AllocationCreated', - this.network.contracts.SubgraphService.interface, - 'indexer', - this.network.specification.indexerOptions.address, - receipt, - logger, - ) - if (subgraphServiceCreateAllocationEventLogs !== undefined) { - createAllocationEventLogs = subgraphServiceCreateAllocationEventLogs - } + const createEventLogs = this.network.transactionManager.findEvent( + 'AllocationCreated', + this.network.contracts.SubgraphService.interface, + 'indexer', + this.network.specification.indexerOptions.address, + receipt, + logger, + ) + if (createEventLogs !== undefined) { + createAllocationEventLogs = createEventLogs } } @@ -1607,166 +1400,61 @@ export class AllocationManager { metadata: params.metadata, proof: params.proof, }) - const isHorizon = await this.network.isHorizon.value() const txs: TransactionRequest[] = [] - // -- close allocation - if (params.closingAllocationIsLegacy) { - txs.push( - await this.network.contracts.LegacyStaking.closeAllocation.populateTransaction( - params.closingAllocationID, - params.poi.poi, - ), - ) - - // If we are in Horizon and the indexer already has a provision - // automatically add the allocated stake to the Subgraph Service provision - // this prevents the indexer from having to do it manually, which is likely to cause downtime - if (isHorizon) { - const provision = await this.network.contracts.HorizonStaking.getProvision( - this.network.specification.indexerOptions.address, - this.network.contracts.SubgraphService.target, - ) - if (provision.createdAt > 0n) { - logger.info( - 'Automatically provisioning the legacy allocation unallocated stake to the Subgraph Service', - { - indexer: this.network.specification.indexerOptions.address, - subgraphDeploymentID: params.subgraphDeploymentID.toString(), - legacyAllocationID: params.closingAllocationID, - newAllocationID: params.newAllocationID, - tokens: formatGRT(params.tokens), - }, - ) + // -- close allocation: multicall collect and stopService + const collectIndexingRewardsData = encodeCollectIndexingRewardsData( + params.closingAllocationID, + params.poi.poi, + encodePOIMetadata( + params.poi.blockNumber, + params.poi.publicPOI, + params.poi.indexingStatus, + 0, + 0, + ), + ) + const collectCallData = + this.network.contracts.SubgraphService.interface.encodeFunctionData('collect', [ + params.indexer, + PaymentTypes.IndexingRewards, + collectIndexingRewardsData, + ]) - try { - // Calculate how much of the allocated amount "corresponds" to the indexer's own stake - // Note that this calculation is imperfect by design, the real calculation is too complicated to be done here - // but also the network state can change between the calculation and the moment the transaction is executed - // In those cases it's possible that the transaction will fail and the reallocation will be rejected, requiring - // manual intervention from the indexer. - const ownStake = - await this.network.contracts.HorizonStaking.getProviderTokensAvailable( - params.indexer, - this.network.contracts.SubgraphService.target, - ) - const delegatedStake = - await this.network.contracts.HorizonStaking.getDelegatedTokensAvailable( - params.indexer, - this.network.contracts.SubgraphService.target, - ) - const totalStake = ownStake + delegatedStake - const stakeRatio = ownStake / totalStake - const tokensToAdd = BigInt(params.tokens) * stakeRatio - logger.info('Automatic provisioning amount calculated', { - indexer: params.indexer, - ownStake: formatGRT(ownStake), - delegatedStake: formatGRT(delegatedStake), - stakeRatio: stakeRatio.toString(), - tokensToAdd: formatGRT(tokensToAdd), - }) - if (tokensToAdd > 0n) { - txs.push( - await this.network.contracts.HorizonStaking.addToProvision.populateTransaction( - params.indexer, - this.network.contracts.SubgraphService.target, - tokensToAdd, - ), - ) - } - } catch (error) { - logger.error( - 'Error while automatically provisioning the legacy allocation unallocated stake to the Subgraph Service', - { - error: error, - }, - ) - } - } else { - logger.info( - 'Skipping automatic provisioning after legacy allocation closure, could not find indexer provision', - { - indexer: this.network.specification.indexerOptions.address, - subgraphDeploymentID: params.subgraphDeploymentID.toString(), - legacyAllocationID: params.closingAllocationID, - newAllocationID: params.newAllocationID, - tokens: formatGRT(params.tokens), - }, - ) - } - } - } else { - // Horizon: Need to multicall collect and stopService - - // collect - const collectIndexingRewardsData = encodeCollectIndexingRewardsData( - params.closingAllocationID, - params.poi.poi, - encodePOIMetadata( - params.poi.blockNumber, - params.poi.publicPOI, - params.poi.indexingStatus, - 0, - 0, - ), - ) - const collectCallData = - this.network.contracts.SubgraphService.interface.encodeFunctionData('collect', [ - params.indexer, - PaymentTypes.IndexingRewards, - collectIndexingRewardsData, - ]) - - // stopService - const stopServiceCallData = - this.network.contracts.SubgraphService.interface.encodeFunctionData( - 'stopService', - [params.indexer, encodeStopServiceData(params.closingAllocationID)], - ) + const stopServiceCallData = + this.network.contracts.SubgraphService.interface.encodeFunctionData('stopService', [ + params.indexer, + encodeStopServiceData(params.closingAllocationID), + ]) + + txs.push( + await this.network.contracts.SubgraphService.multicall.populateTransaction([ + collectCallData, + stopServiceCallData, + ]), + ) - txs.push( - await this.network.contracts.SubgraphService.multicall.populateTransaction([ - collectCallData, - stopServiceCallData, - ]), - ) + // -- create new allocation + // Fail automatically if the indexer is not registered + const registrationData = await this.network.contracts.SubgraphService.indexers( + params.indexer, + ) + if (registrationData.url.length === 0) { + throw indexerError(IndexerErrorCode.IE086) } - // -- create new allocation - if (isHorizon) { - // Fail automatically if the indexer is not registered - // This provides a better ux during the transition period - const registrationData = await this.network.contracts.SubgraphService.indexers( + const encodedData = encodeStartServiceData( + params.subgraphDeploymentID.toString(), + BigInt(params.tokens), + params.newAllocationID, + params.proof.toString(), + ) + txs.push( + await this.network.contracts.SubgraphService.startService.populateTransaction( params.indexer, - ) - if (registrationData.url.length === 0) { - throw indexerError(IndexerErrorCode.IE086) - } - - const encodedData = encodeStartServiceData( - params.subgraphDeploymentID.toString(), - BigInt(params.tokens), - params.newAllocationID, - params.proof.toString(), - ) - txs.push( - await this.network.contracts.SubgraphService.startService.populateTransaction( - params.indexer, - encodedData, - ), - ) - } else { - txs.push( - await this.network.contracts.LegacyStaking.allocateFrom.populateTransaction( - params.indexer, - params.subgraphDeploymentID, - params.tokens, - params.newAllocationID, - params.metadata, - params.proof, - ), - ) - } + encodedData, + ), + ) return txs.map((tx) => ({ actionID: params.actionID, @@ -1865,27 +1553,20 @@ export class AllocationManager { const logger = this.logger.child({ function: 'validateActionBatch' }) logger.debug(`Validating action batch`, { size: batch.length }) - // Validate stake feasibility - we need to analyse stake depending on the action type + // Validate stake feasibility const indexerFreeStake = await this.network.networkMonitor.freeStake() const actionsBatchStakeUsageSummaries = await pMap(batch, async (action: Action) => this.stakeUsageSummary(action), ) - const batchDeltaLegacy = actionsBatchStakeUsageSummaries - .filter((summary: ActionStakeUsageSummary) => summary.action.isLegacy) - .map((summary: ActionStakeUsageSummary) => summary.balance) - .reduce((a: bigint, b: bigint) => a + b, 0n) const batchDelta = actionsBatchStakeUsageSummaries - .filter((summary: ActionStakeUsageSummary) => !summary.action.isLegacy) .map((summary: ActionStakeUsageSummary) => summary.balance) .reduce((a: bigint, b: bigint) => a + b, 0n) const indexerNewBalance = indexerFreeStake.horizon - batchDelta - const indexerNewBalanceLegacy = indexerFreeStake.legacy - batchDeltaLegacy logger.trace('Action batch stake usage summary', { - indexerFreeStake: indexerFreeStake.toString(), - indexerFreeStakeLegacy: indexerFreeStake.legacy.toString(), + indexerFreeStake: indexerFreeStake.horizon.toString(), actionsBatchStakeUsageSummaries: actionsBatchStakeUsageSummaries.map((summary) => { return { action: summary.action, @@ -1896,22 +1577,15 @@ export class AllocationManager { } }), batchDelta: batchDelta.toString(), - batchDeltaLegacy: batchDeltaLegacy.toString(), indexerNewBalance: indexerNewBalance.toString(), - indexerNewBalanceLegacy: indexerNewBalanceLegacy.toString(), }) - if (indexerNewBalance < 0n || indexerNewBalanceLegacy < 0n) { - { - throw indexerError( - IndexerErrorCode.IE013, - `Unfeasible action batch: Approved action batch GRT balance is ` + - `${formatGRT(batchDelta)} for horizon actions and ` + - `${formatGRT(batchDeltaLegacy)} for legacy actions ` + - `but available horizon stake equals ${formatGRT(indexerFreeStake.horizon)} ` + - `and legacy stake equals ${formatGRT(indexerFreeStake.legacy)}.`, - ) - } + if (indexerNewBalance < 0n) { + throw indexerError( + IndexerErrorCode.IE013, + `Unfeasible action batch: Approved action batch GRT balance is ` + + `${formatGRT(batchDelta)} but available stake equals ${formatGRT(indexerFreeStake.horizon)}.`, + ) } /* Return actions sorted by GRT balance (ascending). diff --git a/packages/indexer-common/src/indexer-management/monitor.ts b/packages/indexer-common/src/indexer-management/monitor.ts index eb39634d5..18fa80336 100644 --- a/packages/indexer-common/src/indexer-management/monitor.ts +++ b/packages/indexer-common/src/indexer-management/monitor.ts @@ -71,37 +71,24 @@ export class NetworkMonitor { // To simplify the agent logic, this function converts horizon allocation values, returning epoch values // regardless of the allocation type. async maxAllocationDuration(): Promise { - const isHorizon = await this.isHorizon() - - if (isHorizon) { - // TODO HORIZON: this assumes a block time of 12 seconds which is true for current protocol chain but not always - const BLOCK_IN_SECONDS = 12n - const epochLengthInBlocks = await this.contracts.EpochManager.epochLength() - const epochLengthInSeconds = Number(epochLengthInBlocks * BLOCK_IN_SECONDS) - - // When converting to epochs we give it a bit of leeway since missing the allocation expiration in horizon - // incurs in a severe penalty (missing out on indexing rewards) - const horizonDurationInSeconds = Number( - await this.contracts.SubgraphService.maxPOIStaleness(), - ) - const horizonDurationInEpochs = Math.max( - 1, - Math.floor(horizonDurationInSeconds / epochLengthInSeconds) - 1, - ) + // TODO HORIZON: this assumes a block time of 12 seconds which is true for current protocol chain but not always + const BLOCK_IN_SECONDS = 12n + const epochLengthInBlocks = await this.contracts.EpochManager.epochLength() + const epochLengthInSeconds = Number(epochLengthInBlocks * BLOCK_IN_SECONDS) + + // When converting to epochs we give it a bit of leeway since missing the allocation expiration in horizon + // incurs in a severe penalty (missing out on indexing rewards) + const horizonDurationInSeconds = Number( + await this.contracts.SubgraphService.maxPOIStaleness(), + ) + const horizonDurationInEpochs = Math.max( + 1, + Math.floor(horizonDurationInSeconds / epochLengthInSeconds) - 1, + ) - return { - // Hardcoded to the latest known value. This is required to check for legacy allo expiration during the transition period. - // - Arbitrum One: 28 - // - Arbitrum Sepolia: 8 - // - Local Network: 4 - legacy: 28, - horizon: horizonDurationInEpochs, - } - } else { - return { - legacy: Number(await this.contracts.LegacyStaking.maxAllocationEpochs()), - horizon: 0, - } + return { + legacy: 0, + horizon: horizonDurationInEpochs, } } @@ -109,42 +96,27 @@ export class NetworkMonitor { * Returns the amount of free stake for the indexer. * * The free stake is the amount of tokens that the indexer can use to stake in - * new allocations. - * - * Horizon: It's calculated as the difference between the tokens - * available in the provision and the tokens already locked allocations. - * - * Legacy: It's given by the indexer's stake capacity. + * new allocations. It's calculated as the difference between the tokens + * available in the provision and the tokens already locked in allocations. * * @returns The amount of free stake for the indexer. */ async freeStake(): Promise> { - const isHorizon = await this.isHorizon() - - if (isHorizon) { - const address = this.indexerOptions.address - const dataService = this.contracts.SubgraphService.target.toString() - const delegationRatio = await this.contracts.SubgraphService.getDelegationRatio() - const tokensAvailable = await this.contracts.HorizonStaking.getTokensAvailable( - address, - dataService, - delegationRatio, - ) - const lockedStake = - await this.contracts.SubgraphService.allocationProvisionTracker(address) - const freeStake = tokensAvailable > lockedStake ? tokensAvailable - lockedStake : 0n + const address = this.indexerOptions.address + const dataService = this.contracts.SubgraphService.target.toString() + const delegationRatio = await this.contracts.SubgraphService.getDelegationRatio() + const tokensAvailable = await this.contracts.HorizonStaking.getTokensAvailable( + address, + dataService, + delegationRatio, + ) + const lockedStake = + await this.contracts.SubgraphService.allocationProvisionTracker(address) + const freeStake = tokensAvailable > lockedStake ? tokensAvailable - lockedStake : 0n - return { - legacy: 0n, // In horizon new legacy allocations cannot be created so we return 0 - horizon: freeStake, - } - } else { - return { - legacy: await this.contracts.LegacyStaking.getIndexerCapacity( - this.indexerOptions.address, - ), - horizon: 0n, - } + return { + legacy: 0n, + horizon: freeStake, } } @@ -1394,18 +1366,11 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n } private async isOperator(operatorAddress: string, indexerAddress: string) { - if (await this.isHorizon()) { - return await this.contracts.HorizonStaking.isAuthorized( - indexerAddress, - this.contracts.SubgraphService.target, - operatorAddress, - ) - } else { - return await this.contracts.LegacyStaking.isOperator( - operatorAddress, - indexerAddress, - ) - } + return await this.contracts.HorizonStaking.isAuthorized( + indexerAddress, + this.contracts.SubgraphService.target, + operatorAddress, + ) } // Returns a tuple of [POI, blockNumber] diff --git a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts index 2f7803c62..331ac6b12 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts @@ -3,7 +3,7 @@ import pMap from 'p-map' import gql from 'graphql-tag' -import { ethers, hexlify, ZeroAddress } from 'ethers' +import { ethers, ZeroAddress } from 'ethers' import { Address, @@ -26,7 +26,6 @@ import { IndexerManagementResolverContext, IndexingDecisionBasis, IndexingRuleAttributes, - legacyAllocationIdProof, Network, POIData, ReallocateAllocationResult, @@ -318,163 +317,7 @@ async function queryAllocations( ) } -async function createLegacyAllocation( - network: Network, - graphNode: GraphNode, - allocationAmount: bigint, - logger: Logger, - subgraphDeployment: SubgraphDeploymentID, - currentEpoch: bigint, - activeAllocations: Allocation[], - protocolNetwork: string, -): Promise<{ txHash: string; allocationId: Address }> { - const contracts = network.contracts - const transactionManager = network.transactionManager - const address = network.specification.indexerOptions.address - - // Identify how many GRT the indexer has staked - const freeStake = await contracts.LegacyStaking.getIndexerCapacity(address) - - // If there isn't enough left for allocating, abort - if (freeStake < allocationAmount) { - logger.error( - `Legacy allocation of ${formatGRT( - allocationAmount, - )} GRT cancelled: indexer only has a free stake amount of ${formatGRT( - freeStake, - )} GRT`, - ) - throw indexerError( - IndexerErrorCode.IE013, - `Legacy allocation of ${formatGRT( - allocationAmount, - )} GRT cancelled: indexer only has a free stake amount of ${formatGRT( - freeStake, - )} GRT`, - ) - } - - // Ensure subgraph is deployed before allocating - await graphNode.ensure( - `indexer-agent/${subgraphDeployment.ipfsHash.slice(-10)}`, - subgraphDeployment, - ) - - logger.debug('Obtain a unique legacy Allocation ID') - - // Obtain a unique allocation ID - const recentlyClosedAllocations = - await network.networkMonitor.recentlyClosedAllocations(Number(currentEpoch), 2) - const activeAndRecentlyClosedAllocations: Allocation[] = [ - ...recentlyClosedAllocations, - ...activeAllocations, - ] - const { allocationSigner, allocationId } = uniqueAllocationID( - transactionManager.wallet.mnemonic!.phrase, - Number(currentEpoch), - subgraphDeployment, - activeAndRecentlyClosedAllocations.map((allocation) => allocation.id), - ) - - // Double-check whether the allocationID already exists on chain, to - // avoid unnecessary transactions. - // Note: We're checking the allocation state here, which is defined as - // - // enum AllocationState { Null, Active, Closed, Finalized } - // - // in the contracts. - const state = await contracts.LegacyStaking.getAllocationState(allocationId) - if (state !== 0n) { - logger.debug(`Skipping legacy allocation as it already exists onchain`, { - indexer: address, - allocation: allocationId, - }) - throw indexerError( - IndexerErrorCode.IE066, - `Legacy allocation '${allocationId}' already exists onchain`, - ) - } - - logger.debug('Generating new legacy allocation ID proof', { - newAllocationSigner: allocationSigner, - newAllocationID: allocationId, - indexerAddress: address, - }) - - const proof = await legacyAllocationIdProof(allocationSigner, address, allocationId) - - logger.debug('Successfully generated legacy allocation ID proof', { - allocationIDProof: proof, - }) - - logger.debug(`Sending legacy allocateFrom transaction`, { - indexer: address, - subgraphDeployment: subgraphDeployment.ipfsHash, - amount: formatGRT(allocationAmount), - allocation: allocationId, - proof, - protocolNetwork, - }) - - const receipt = await transactionManager.executeTransaction( - async () => - contracts.LegacyStaking.allocateFrom.estimateGas( - address, - subgraphDeployment.bytes32, - allocationAmount, - allocationId, - hexlify(new Uint8Array(32).fill(0)), - proof, - ), - async (gasLimit) => - contracts.LegacyStaking.allocateFrom( - address, - subgraphDeployment.bytes32, - allocationAmount, - allocationId, - hexlify(new Uint8Array(32).fill(0)), - proof, - { gasLimit }, - ), - logger.child({ action: 'allocate' }), - ) - - if (receipt === 'paused' || receipt === 'unauthorized') { - throw indexerError( - IndexerErrorCode.IE062, - `Legacy allocation not created. ${ - receipt === 'paused' ? 'Network paused' : 'Operator not authorized' - }`, - ) - } - - const createAllocationEventLogs = network.transactionManager.findEvent( - 'AllocationCreated', - network.contracts.LegacyStaking.interface, - 'subgraphDeploymentID', - subgraphDeployment.toString(), - receipt, - logger, - ) - - if (!createAllocationEventLogs) { - throw indexerError( - IndexerErrorCode.IE014, - `Legacy allocation create transaction was never mined`, - ) - } - - logger.info(`Successfully legacy allocated to subgraph deployment`, { - amountGRT: formatGRT(createAllocationEventLogs.tokens), - allocation: createAllocationEventLogs.allocationID, - epoch: createAllocationEventLogs.epoch.toString(), - transaction: receipt.hash, - }) - - return { txHash: receipt.hash, allocationId: createAllocationEventLogs.allocationID } -} - -async function createHorizonAllocation( +async function createAllocation( network: Network, graphNode: GraphNode, allocationAmount: bigint, @@ -629,95 +472,7 @@ async function createHorizonAllocation( return { txHash: receipt.hash, allocationId } } -async function closeLegacyAllocation( - allocation: Allocation, - poi: string, - network: Network, - logger: Logger, -): Promise<{ txHash: string; rewardsAssigned: bigint }> { - const contracts = network.contracts - const transactionManager = network.transactionManager - const isHorizon = await network.isHorizon.value() - - // Double-check whether the allocation is still active on chain, to - // avoid unnecessary transactions. - // Note: We're checking the allocation state here, which is defined as - // - // enum AllocationState { Null, Active, Closed, Finalized } - // - // in the contracts. - const state = await contracts.LegacyStaking.getAllocationState(allocation.id) - if (state !== 1n) { - throw indexerError( - IndexerErrorCode.IE065, - 'Legacy allocation has already been closed', - ) - } - - logger.debug('Sending legacy closeAllocation transaction') - const receipt = await transactionManager.executeTransaction( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - () => contracts.LegacyStaking.closeAllocation.estimateGas(allocation.id, poi!), - (gasLimit) => - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - contracts.LegacyStaking.closeAllocation(allocation.id, poi!, { - gasLimit, - }), - logger, - ) - - if (receipt === 'paused' || receipt === 'unauthorized') { - throw indexerError( - IndexerErrorCode.IE062, - `Legacy allocation '${allocation.id}' could not be closed: ${receipt}`, - ) - } - - const closeAllocationEventLogs = transactionManager.findEvent( - 'AllocationClosed', - contracts.LegacyStaking.interface, - 'allocationID', - allocation.id, - receipt, - logger, - ) - - if (!closeAllocationEventLogs) { - throw indexerError( - IndexerErrorCode.IE015, - `Legacy allocation close transaction was never successfully mined`, - ) - } - - const rewardsEventLogs = transactionManager.findEvent( - isHorizon ? 'HorizonRewardsAssigned' : 'RewardsAssigned', - contracts.RewardsManager.interface, - 'allocationID', - allocation.id, - receipt, - logger, - ) - - const rewardsAssigned = rewardsEventLogs ? rewardsEventLogs.amount : 0 - if (rewardsAssigned == 0) { - logger.warn('No rewards were distributed upon closing the legacy allocation') - } - - logger.info(`Successfully closed legacy allocation`, { - deployment: closeAllocationEventLogs.subgraphDeploymentID, - allocation: closeAllocationEventLogs.allocationID, - indexer: closeAllocationEventLogs.indexer, - amountGRT: formatGRT(closeAllocationEventLogs.tokens), - poi: closeAllocationEventLogs.poi, - epoch: closeAllocationEventLogs.epoch.toString(), - transaction: receipt.hash, - indexingRewards: rewardsAssigned, - }) - - return { txHash: receipt.hash, rewardsAssigned } -} - -async function closeHorizonAllocation( +async function closeAllocation( allocation: Allocation, poiData: POIData, network: Network, @@ -841,226 +596,7 @@ async function closeHorizonAllocation( return { txHash: receipt.hash, rewardsAssigned } } -// isHorizon: false -async function reallocateLegacyAllocation( - allocation: Allocation, - allocationAmount: bigint, - activeAllocations: Allocation[], - poi: string, - network: Network, - logger: Logger, -): Promise<{ txHash: string; rewardsAssigned: bigint; newAllocationId: Address }> { - const contracts = network.contracts - const transactionManager = network.transactionManager - const address = network.specification.indexerOptions.address - const currentEpoch = await contracts.EpochManager.currentEpoch() - const isHorizon = await network.isHorizon.value() - - // Double-check whether the allocation is still active on chain, to - // avoid unnecessary transactions. - // Note: We're checking the allocation state here, which is defined as - // - // enum AllocationState { Null, Active, Closed, Finalized } - // - // in the contracts. - const state = await contracts.LegacyStaking.getAllocationState(allocation.id) - if (state !== 1n) { - logger.warn(`Legacy allocation has already been closed`) - throw indexerError( - IndexerErrorCode.IE065, - `Legacy allocation has already been closed`, - ) - } - - if (allocationAmount < 0n) { - logger.warn('Cannot legacy reallocate a negative amount of GRT', { - amount: allocationAmount.toString(), - }) - throw indexerError( - IndexerErrorCode.IE061, - 'Cannot legacy reallocate a negative amount of GRT', - ) - } - - logger.info(`Legacy reallocate to subgraph deployment`, { - existingAllocationAmount: formatGRT(allocation.allocatedTokens), - newAllocationAmount: formatGRT(allocationAmount), - epoch: currentEpoch.toString(), - }) - - // Identify how many GRT the indexer has staked - const freeStake = (await network.networkMonitor.freeStake()).legacy - - // When reallocating, we will first close the old allocation and free up the GRT in that allocation - // This GRT will be available in addition to freeStake for the new allocation - const postCloseFreeStake = freeStake + allocation.allocatedTokens - - // If there isn't enough left for allocating, abort - if (postCloseFreeStake < allocationAmount) { - throw indexerError( - IndexerErrorCode.IE013, - `Unable to legacy allocate ${formatGRT( - allocationAmount, - )} GRT: indexer only has a free stake amount of ${formatGRT( - freeStake, - )} GRT, plus ${formatGRT( - allocation.allocatedTokens, - )} GRT from the existing allocation`, - ) - } - - logger.debug('Generating a new unique legacy Allocation ID') - const recentlyClosedAllocations = - await network.networkMonitor.recentlyClosedAllocations(Number(currentEpoch), 2) - const activeAndRecentlyClosedAllocations: Allocation[] = [ - ...recentlyClosedAllocations, - ...activeAllocations, - ] - const { allocationSigner, allocationId: newAllocationId } = uniqueAllocationID( - transactionManager.wallet.mnemonic!.phrase, - Number(currentEpoch), - allocation.subgraphDeployment.id, - activeAndRecentlyClosedAllocations.map((allocation) => allocation.id), - ) - - logger.debug('New unique legacy Allocation ID generated', { - newAllocationID: newAllocationId, - newAllocationSigner: allocationSigner, - }) - - // Double-check whether the allocationID already exists on chain, to - // avoid unnecessary transactions. - // Note: We're checking the allocation state here, which is defined as - // - // enum AllocationState { Null, Active, Closed, Finalized } - // - // in the contracts. - const newAllocationState = - await contracts.LegacyStaking.getAllocationState(newAllocationId) - if (newAllocationState !== 0n) { - logger.warn(`Skipping legacy Allocation as it already exists onchain`, { - indexer: address, - allocation: newAllocationId, - newAllocationState, - }) - throw indexerError(IndexerErrorCode.IE066, 'AllocationID already exists') - } - - logger.debug('Generating new legacy allocation ID proof', { - newAllocationSigner: allocationSigner, - newAllocationID: newAllocationId, - indexerAddress: address, - }) - const proof = await legacyAllocationIdProof(allocationSigner, address, newAllocationId) - logger.debug('Successfully generated legacy allocation ID proof', { - allocationIDProof: proof, - }) - - logger.info(`Sending legacy close and legacy allocate multicall transaction`, { - indexer: address, - amount: formatGRT(allocationAmount), - oldAllocation: allocation.id, - newAllocation: newAllocationId, - newAllocationAmount: formatGRT(allocationAmount), - deployment: allocation.subgraphDeployment.id.toString(), - poi: poi, - proof, - epoch: currentEpoch.toString(), - }) - - const callData = [ - await contracts.LegacyStaking.closeAllocation.populateTransaction(allocation.id, poi), - await contracts.LegacyStaking.allocateFrom.populateTransaction( - address, - allocation.subgraphDeployment.id.bytes32, - allocationAmount, - newAllocationId, - hexlify(new Uint8Array(32).fill(0)), // metadata - proof, - ), - ].map((tx) => tx.data as string) - - const receipt = await transactionManager.executeTransaction( - async () => contracts.LegacyStaking.multicall.estimateGas(callData), - async (gasLimit) => contracts.LegacyStaking.multicall(callData, { gasLimit }), - logger.child({ - function: 'closeAndAllocate', - }), - ) - - if (receipt === 'paused' || receipt === 'unauthorized') { - throw indexerError( - IndexerErrorCode.IE062, - `Legacy allocation '${newAllocationId}' could not be closed: ${receipt}`, - ) - } - - const createAllocationEventLogs = transactionManager.findEvent( - 'AllocationCreated', - contracts.LegacyStaking.interface, - 'subgraphDeploymentID', - allocation.subgraphDeployment.id.toString(), - receipt, - logger, - ) - - if (!createAllocationEventLogs) { - throw indexerError(IndexerErrorCode.IE014, `Legacy allocation was never mined`) - } - - const closeAllocationEventLogs = transactionManager.findEvent( - 'AllocationClosed', - contracts.LegacyStaking.interface, - 'allocationID', - allocation.id, - receipt, - logger, - ) - - if (!closeAllocationEventLogs) { - throw indexerError( - IndexerErrorCode.IE015, - `Legacy allocation close transaction was never successfully mined`, - ) - } - - const rewardsEventLogs = transactionManager.findEvent( - isHorizon ? 'HorizonRewardsAssigned' : 'RewardsAssigned', - contracts.RewardsManager.interface, - 'allocationID', - allocation.id, - receipt, - logger, - ) - - const rewardsAssigned = rewardsEventLogs ? rewardsEventLogs.amount : 0 - if (rewardsAssigned == 0) { - logger.warn('No rewards were distributed upon closing the legacy allocation') - } - - logger.info(`Successfully reallocated legacy allocation`, { - deployment: createAllocationEventLogs.subgraphDeploymentID, - closedAllocation: closeAllocationEventLogs.allocationID, - closedAllocationStakeGRT: formatGRT(closeAllocationEventLogs.tokens), - closedAllocationPOI: closeAllocationEventLogs.poi, - closedAllocationEpoch: closeAllocationEventLogs.epoch.toString(), - indexingRewardsCollected: rewardsAssigned, - createdAllocation: createAllocationEventLogs.allocationID, - createdAllocationStakeGRT: formatGRT(createAllocationEventLogs.tokens), - indexer: createAllocationEventLogs.indexer, - epoch: createAllocationEventLogs.epoch.toString(), - transaction: receipt.hash, - }) - - logger.info('Identifying receipts worth collecting', { - allocation: closeAllocationEventLogs.allocationID, - }) - - return { txHash: receipt.hash, rewardsAssigned, newAllocationId } -} - -// isHorizon: true and allocation: not legacy -async function reallocateHorizonAllocation( +async function reallocateAllocation( allocation: Allocation, allocationAmount: bigint, activeAllocations: Allocation[], @@ -1312,47 +848,6 @@ async function reallocateHorizonAllocation( return { txHash: receipt.hash, rewardsAssigned, newAllocationId } } -// isHorizon: true and allocation: legacy -async function migrateLegacyAllocationToHorizon( - allocation: Allocation, - allocationAmount: bigint, - activeAllocations: Allocation[], - poi: string, - network: Network, - graphNode: GraphNode, - logger: Logger, -): Promise<{ txHash: string; rewardsAssigned: bigint; newAllocationId: Address }> { - const contracts = network.contracts - const currentEpoch = await contracts.EpochManager.currentEpoch() - - // We want to make sure that we close the legacy allocation even if reallocating to horizon would fail - // so we don't use a multicall but send separate transactions for closing - const closeAllocationResult = await closeLegacyAllocation( - allocation, - poi, - network, - logger, - ) - - // After closing the legacy allocation, we attempt to create a new horizon allocation - const createAllocationResult = await createHorizonAllocation( - network, - graphNode, - allocationAmount, - logger, - allocation.subgraphDeployment.id, - currentEpoch, - activeAllocations, - network.specification.networkIdentifier, - ) - - return { - txHash: createAllocationResult.txHash, - rewardsAssigned: closeAllocationResult.rewardsAssigned, - newAllocationId: createAllocationResult.allocationId, - } -} - export default { allocations: async ( { filter }: { filter: AllocationFilter }, @@ -1478,43 +973,19 @@ export default { try { const currentEpoch = await network.contracts.EpochManager.currentEpoch() - const isHorizon = await network.isHorizon.value() - logger.debug('createAllocation: Checking allocation resolution path', { - isHorizon, - }) - - let txHash: string - let allocationId: Address - if (isHorizon) { - logger.debug('Creating horizon allocation') - const result = await createHorizonAllocation( - network, - graphNode, - allocationAmount, - logger, - subgraphDeployment, - currentEpoch, - activeAllocations, - protocolNetwork, - ) - txHash = result.txHash - allocationId = result.allocationId - } else { - logger.debug('Creating legacy allocation') - const result = await createLegacyAllocation( - network, - graphNode, - allocationAmount, - logger, - subgraphDeployment, - currentEpoch, - activeAllocations, - protocolNetwork, - ) - txHash = result.txHash - allocationId = result.allocationId - } + const result = await createAllocation( + network, + graphNode, + allocationAmount, + logger, + subgraphDeployment, + currentEpoch, + activeAllocations, + protocolNetwork, + ) + const txHash = result.txHash + const allocationId = result.allocationId logger.debug( `Updating indexing rules, so indexer-agent will now manage the active allocation`, @@ -1595,8 +1066,8 @@ export default { const poiData = await networkMonitor.resolvePOI( allocationData, poi, - allocationData.isLegacy ? undefined : publicPOI, - allocationData.isLegacy || blockNumber === null ? undefined : Number(blockNumber), + publicPOI, + blockNumber === null ? undefined : Number(blockNumber), force, ) logger.debug('POI resolved', { @@ -1609,33 +1080,14 @@ export default { force, }) - logger.debug('closeAllocation: Checking allocation resolution path', { - allocationIsLegacy: allocationData.isLegacy, - }) - - let txHash: string - let rewardsAssigned: bigint - if (allocationData.isLegacy) { - logger.debug('Closing legacy allocation') - const result = await closeLegacyAllocation( - allocationData, - poiData.poi, - network, - logger, - ) - txHash = result.txHash - rewardsAssigned = result.rewardsAssigned - } else { - logger.debug('Closing horizon allocation') - const result = await closeHorizonAllocation( - allocationData, - poiData, - network, - logger, - ) - txHash = result.txHash - rewardsAssigned = result.rewardsAssigned - } + const result = await closeAllocation( + allocationData, + poiData, + network, + logger, + ) + const txHash = result.txHash + const rewardsAssigned = result.rewardsAssigned logger.debug( `Updating indexing rules, so indexer-agent keeps the deployment synced but doesn't reallocate to it`, @@ -1692,7 +1144,7 @@ export default { force: boolean protocolNetwork: string }, - { logger, models, multiNetworks, graphNode }: IndexerManagementResolverContext, + { logger, models, multiNetworks }: IndexerManagementResolverContext, ): Promise => { logger = logger.child({ component: 'reallocateAllocationResolver', @@ -1750,57 +1202,17 @@ export default { force, }) - const isHorizon = await network.isHorizon.value() - - logger.debug('reallocateAllocation: Checking allocation resolution path', { - isHorizon, - allocationIsLegacy: allocationData.isLegacy, - }) - - let txHash: string - let rewardsAssigned: bigint - let newAllocationId: Address - if (!isHorizon) { - logger.debug('Reallocating legacy allocation') - const result = await reallocateLegacyAllocation( - allocationData, - allocationAmount, - activeAllocations, - poiData.poi, - network, - logger, - ) - txHash = result.txHash - rewardsAssigned = result.rewardsAssigned - newAllocationId = result.newAllocationId - } else if (allocationData.isLegacy) { - logger.debug('Migrating legacy allocation to horizon') - const result = await migrateLegacyAllocationToHorizon( - allocationData, - allocationAmount, - activeAllocations, - poiData.poi, - network, - graphNode, - logger, - ) - txHash = result.txHash - rewardsAssigned = result.rewardsAssigned - newAllocationId = result.newAllocationId - } else { - logger.debug('Reallocating horizon allocation') - const result = await reallocateHorizonAllocation( - allocationData, - allocationAmount, - activeAllocations, - poiData, - network, - logger, - ) - txHash = result.txHash - rewardsAssigned = result.rewardsAssigned - newAllocationId = result.newAllocationId - } + const result = await reallocateAllocation( + allocationData, + allocationAmount, + activeAllocations, + poiData, + network, + logger, + ) + const txHash = result.txHash + const rewardsAssigned = result.rewardsAssigned + const newAllocationId = result.newAllocationId logger.debug( `Updating indexing rules, so indexer-agent will now manage the active allocation`, diff --git a/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts b/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts index 3d90bd748..e465d9f08 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts @@ -63,7 +63,7 @@ const URL_VALIDATION_TEST: Test = { export default { indexerRegistration: async ( { protocolNetwork: unvalidatedProtocolNetwork }: { protocolNetwork: string }, - { multiNetworks, logger }: IndexerManagementResolverContext, + { multiNetworks }: IndexerManagementResolverContext, ): Promise => { if (!multiNetworks) { throw Error( @@ -78,40 +78,17 @@ export default { const registrationInfo: RegistrationInfo[] = [] - // Check if the indexer is registered in the legacy service registry - try { - const registered = await contracts.LegacyServiceRegistry.isRegistered(address) - if (registered) { - const service = await contracts.LegacyServiceRegistry.services(address) - registrationInfo.push({ - address, - protocolNetwork, - url: service.url, - location: geohash.decode(service.geoHash), - registered, - isLegacy: true, - __typename: 'IndexerRegistration', - }) - } - } catch (e) { - logger?.debug( - `Could not get legacy service registration for indexer. It's likely that the legacy protocol is not reachable.`, - ) - } - - if (await network.isHorizon.value()) { - const service = await contracts.SubgraphService.indexers(address) - if (service.url.length > 0) { - registrationInfo.push({ - address, - protocolNetwork, - url: service.url, - location: geohash.decode(service.geoHash), - registered: true, - isLegacy: false, - __typename: 'IndexerRegistration', - }) - } + const service = await contracts.SubgraphService.indexers(address) + if (service.url.length > 0) { + registrationInfo.push({ + address, + protocolNetwork, + url: service.url, + location: geohash.decode(service.geoHash), + registered: true, + isLegacy: false, + __typename: 'IndexerRegistration', + }) } if (registrationInfo.length === 0) { @@ -248,21 +225,8 @@ export default { return } try { - // Always try to get legacy endpoints, but fail gracefully if they don't exist - try { - const networkEndpoints = await endpointForNetwork(network, false) - endpoints.push(networkEndpoints) - } catch (e) { - logger?.debug( - `Could not get legacy service endpoints for network. It's likely that the legacy protocol is not reachable.`, - ) - } - - // Only get horizon endpoints when horizon is enabled - if (await network.isHorizon.value()) { - const networkEndpoints = await endpointForNetwork(network, true) - endpoints.push(networkEndpoints) - } + const networkEndpoints = await endpointForNetwork(network) + endpoints.push(networkEndpoints) } catch (err) { // Ignore endpoints for this network logger?.warn(`Failed to detect service endpoints for network`, { @@ -329,16 +293,11 @@ function defaultEndpoints(protocolNetwork: string, isHorizon: boolean): Endpoint } } -async function endpointForNetwork( - network: Network, - isHorizon: boolean, -): Promise { +async function endpointForNetwork(network: Network): Promise { const contracts = network.contracts const address = network.specification.indexerOptions.address - const endpoints = defaultEndpoints(network.specification.networkIdentifier, isHorizon) - const service = isHorizon - ? await contracts.SubgraphService.indexers(address) - : await contracts.LegacyServiceRegistry.services(address) + const endpoints = defaultEndpoints(network.specification.networkIdentifier, true) + const service = await contracts.SubgraphService.indexers(address) if (service) { { const { url, tests, ok } = await testURL(service.url, [ diff --git a/packages/indexer-common/src/network.ts b/packages/indexer-common/src/network.ts index 72c548907..a055e9b58 100644 --- a/packages/indexer-common/src/network.ts +++ b/packages/indexer-common/src/network.ts @@ -596,11 +596,7 @@ export class Network { await pRetry( async () => { try { - if (await this.isHorizon.value()) { - await this._register(logger, geoHash, url) - } else { - await this._registerLegacy(logger, geoHash, url) - } + await this._register(logger, geoHash, url) } catch (error) { const err = indexerError(IndexerErrorCode.IE012, error) logger.error(INDEXER_ERROR_MESSAGES[IndexerErrorCode.IE012], { @@ -699,80 +695,6 @@ export class Network { logger.info(`Successfully registered indexer`) } - - private async _registerLegacy( - logger: Logger, - geoHash: string, - url: string, - ): Promise { - logger.info(`Register indexer`, { - url, - geoCoordinates: this.specification.indexerOptions.geoCoordinates, - geoHash, - }) - - // Register the indexer (only if it hasn't been registered yet or - // if its URL/geohash is different from what is registered on chain) - const isRegistered = await this.contracts.LegacyServiceRegistry.isRegistered( - this.specification.indexerOptions.address, - ) - if (isRegistered) { - const service = await this.contracts.LegacyServiceRegistry.services( - this.specification.indexerOptions.address, - ) - if (service.url === url && service.geoHash === geoHash) { - logger.debug('Indexer already registered', { - address: this.specification.indexerOptions.address, - serviceRegistry: this.contracts.LegacyServiceRegistry.target, - service, - }) - if (await this.transactionManager.isOperator.value()) { - logger.info(`Indexer already registered, operator status already granted`) - } else { - logger.info(`Indexer already registered, operator status not yet granted`) - } - return - } - } - const receipt = await this.transactionManager.executeTransaction( - () => - this.contracts.LegacyServiceRegistry.registerFor.estimateGas( - this.specification.indexerOptions.address, - this.specification.indexerOptions.url, - geoHash, - ), - (gasLimit) => - this.contracts.LegacyServiceRegistry.registerFor( - this.specification.indexerOptions.address, - this.specification.indexerOptions.url, - geoHash, - { - gasLimit, - }, - ), - logger.child({ function: 'serviceRegistry.registerFor' }), - ) - if (receipt === 'paused' || receipt === 'unauthorized') { - return - } - const events = receipt.logs - const event = events.find((event) => - event.topics.includes( - this.contracts.LegacyServiceRegistry.interface.getEvent('ServiceRegistered') - .topicHash, - ), - ) - logger.info('Event', { - event, - events, - topicHash: - this.contracts.LegacyServiceRegistry.interface.getEvent('ServiceRegistered') - .topicHash, - }) - assert.ok(event) - - logger.info(`Successfully registered indexer (Legacy registration)`) - } } async function connectWallet( @@ -849,15 +771,6 @@ async function connectToProtocolContracts( 'SubgraphService', ] - // Before horizon we need the LegacyServiceRegistry contract as well - const isHorizon = await contracts.HorizonStaking.getMaxThawingPeriod() - .then((maxThawingPeriod) => maxThawingPeriod > 0) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .catch((_) => false) - if (!isHorizon) { - requiredContracts.push('LegacyServiceRegistry') - } - const missingContracts = requiredContracts.filter( (contract) => !(contract in contracts), ) @@ -869,7 +782,6 @@ async function connectToProtocolContracts( process.exit(1) } - // Only list contracts that are used by the indexer logger.info(`Successfully connected to Horizon contracts`, { epochManager: contracts.EpochManager.target, rewardsManager: contracts.RewardsManager.target, @@ -878,9 +790,6 @@ async function connectToProtocolContracts( graphPaymentsEscrow: contracts.PaymentsEscrow.target, }) logger.info(`Successfully connected to Subgraph Service contracts`, { - ...(isHorizon - ? {} - : { legacyServiceRegistry: contracts.LegacyServiceRegistry.target }), subgraphService: contracts.SubgraphService.target, }) return contracts From 210bd136447a16290231c9b3611724374bc0bad9 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 13:33:35 -0800 Subject: [PATCH 02/21] ci: update actions/checkout and actions/setup-node to v4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - check-formatting.yml: checkout@v2 → v4, setup-node@v2.1.5 → v4 - ci.yml: checkout@v2 → v4, setup-node@v1 → v4 Resolves deprecation warnings for Node.js 16 actions --- .github/workflows/check-formatting.yml | 4 ++-- .github/workflows/ci.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index 931fd4811..e7ca38153 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -10,9 +10,9 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Node.js v20 - uses: actions/setup-node@v2.1.5 + uses: actions/setup-node@v4 with: node-version: 20 - name: Build and Format diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7827622d..5ea0d0562 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,13 +32,13 @@ jobs: --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: update OS run: | sudo apt-get update sudo apt install -y --no-install-recommends gcc g++ make build-essential - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ From 9c93070dc45e332d51ae2be30abf4c1ff128195c Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 13:33:36 -0800 Subject: [PATCH 03/21] ci: update CodeQL actions to v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - checkout@v2 → v4 - codeql-action/init@v1 → v3 - codeql-action/autobuild@v1 → v3 - codeql-action/analyze@v1 → v3 --- .github/workflows/codeql-analysis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5bc9669f9..59b45f7ea 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 From c1891fc087d596411478a0e8f1decd8069a8a9f4 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 13:33:36 -0800 Subject: [PATCH 04/21] ci: update Docker workflow actions to latest versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace deprecated crazy-max/ghaction-docker-meta@v1 with docker/metadata-action@v5 - Update tag configuration to use new metadata-action format - docker/setup-qemu-action@v1 → v3 - docker/setup-buildx-action@v1 → v3 - docker/login-action@v2 → v3 - docker/build-push-action@v2 → v6 - actions/setup-python@v4 → v5 - actions/setup-node@v2.1.5 → v4 - actions/checkout@v2 → v4 --- .github/workflows/indexer-agent-image.yml | 21 ++++++++++++--------- .github/workflows/indexer-cli-image.yml | 21 ++++++++++++--------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.github/workflows/indexer-agent-image.yml b/.github/workflows/indexer-agent-image.yml index aee116c5a..592fd5ab3 100644 --- a/.github/workflows/indexer-agent-image.yml +++ b/.github/workflows/indexer-agent-image.yml @@ -17,34 +17,37 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Docker meta id: docker_meta - uses: crazy-max/ghaction-docker-meta@v1 + uses: docker/metadata-action@v5 with: images: ghcr.io/graphprotocol/indexer-agent - tag-sha: true + tags: | + type=sha + type=ref,event=branch + type=ref,event=tag - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{github.repository_owner}} password: ${{secrets.GITHUB_TOKEN}} - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' - name: Set up Node.js v20 - uses: actions/setup-node@v2.1.5 + uses: actions/setup-node@v4 with: node-version: 20 - name: Build and push Indexer Agent image id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: Dockerfile.indexer-agent diff --git a/.github/workflows/indexer-cli-image.yml b/.github/workflows/indexer-cli-image.yml index c3b36fb83..7e2a49edd 100644 --- a/.github/workflows/indexer-cli-image.yml +++ b/.github/workflows/indexer-cli-image.yml @@ -17,34 +17,37 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Docker meta id: docker_meta - uses: crazy-max/ghaction-docker-meta@v1 + uses: docker/metadata-action@v5 with: images: ghcr.io/graphprotocol/indexer-cli - tag-sha: true + tags: | + type=sha + type=ref,event=branch + type=ref,event=tag - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{github.repository_owner}} password: ${{secrets.GITHUB_TOKEN}} - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' - name: Set up Node.js v20 - uses: actions/setup-node@v2.1.5 + uses: actions/setup-node@v4 with: node-version: 20 - name: Build and push Indexer CLI image id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . file: Dockerfile.indexer-cli From 295440a2a00a5508d16a514f6f26aef030a4894a Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 13:50:27 -0800 Subject: [PATCH 05/21] ci: upgrade to Node.js 24 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - check-formatting.yml: Node 20 → 24 - ci.yml: test matrix [20, 22] → [22, 24] - indexer-agent-image.yml: Node 20 → 24 - indexer-cli-image.yml: Node 20 → 24 --- .github/workflows/check-formatting.yml | 4 ++-- .github/workflows/ci.yml | 2 +- .github/workflows/indexer-agent-image.yml | 4 ++-- .github/workflows/indexer-cli-image.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index e7ca38153..a3d53c24e 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -11,10 +11,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up Node.js v20 + - name: Set up Node.js v24 uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Build and Format run: yarn - name: Check Formatting diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ea0d0562..bf0dcf756 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: build: strategy: matrix: - node-version: [20, 22] + node-version: [22, 24] system: - os: ubuntu-22.04 runs-on: ${{ matrix.system.os }} diff --git a/.github/workflows/indexer-agent-image.yml b/.github/workflows/indexer-agent-image.yml index 592fd5ab3..03dc9add9 100644 --- a/.github/workflows/indexer-agent-image.yml +++ b/.github/workflows/indexer-agent-image.yml @@ -41,10 +41,10 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.11' - - name: Set up Node.js v20 + - name: Set up Node.js v24 uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Build and push Indexer Agent image id: docker_build uses: docker/build-push-action@v6 diff --git a/.github/workflows/indexer-cli-image.yml b/.github/workflows/indexer-cli-image.yml index 7e2a49edd..979609d59 100644 --- a/.github/workflows/indexer-cli-image.yml +++ b/.github/workflows/indexer-cli-image.yml @@ -41,10 +41,10 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.11' - - name: Set up Node.js v20 + - name: Set up Node.js v24 uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Build and push Indexer CLI image id: docker_build uses: docker/build-push-action@v6 From 272d624a756ae68990761d5937217ac9be4e7fca Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 14:04:58 -0800 Subject: [PATCH 06/21] trying to fix some tests --- .../src/__tests__/references/indexer-cost.stdout | 9 +++++---- .../src/__tests__/references/indexer-help.stdout | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout index 96fb64cb6..00d6fcebb 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout @@ -1,6 +1,7 @@ Manage costing for subgraphs - indexer cost set model Update a cost model - indexer cost get Get cost models for one or all subgraphs - indexer cost delete Remove one or many cost models - indexer cost Manage costing for subgraphs + indexer cost set variables Update cost model variables + indexer cost set model Update a cost model + indexer cost get Get cost models for one or all subgraphs + indexer cost delete Remove one or many cost models + indexer cost Manage costing for subgraphs diff --git a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout index 8b6f5671d..5f8f0789b 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout @@ -18,9 +18,10 @@ Manage indexer configuration indexer provision Manage indexer's provision indexer disputes get Cross-check POIs submitted in the network indexer disputes Configure allocation POI monitoring - indexer cost set model Update a cost model - indexer cost get Get cost models for one or all subgraphs - indexer cost delete Remove one or many cost models + indexer cost set variables Update cost model variables + indexer cost set model Update a cost model + indexer cost get Get cost models for one or all subgraphs + indexer cost delete Remove one or many cost models indexer cost Manage costing for subgraphs indexer connect Connect to indexer management API indexer allocations reallocate Reallocate to subgraph deployment From 45f187591f5cd43e6d3641212cd91eddde04741a Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 14:09:23 -0800 Subject: [PATCH 07/21] trying to fix some tests 2 --- .../src/__tests__/references/indexer-cost.stdout | 10 +++++----- .../src/__tests__/references/indexer-help.stdout | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout index 00d6fcebb..b64e990d1 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout @@ -1,7 +1,7 @@ Manage costing for subgraphs - indexer cost set variables Update cost model variables - indexer cost set model Update a cost model - indexer cost get Get cost models for one or all subgraphs - indexer cost delete Remove one or many cost models - indexer cost Manage costing for subgraphs + indexer cost set variables Update cost model variables + indexer cost set model Update a cost model + indexer cost get Get cost models for one or all subgraphs + indexer cost delete Remove one or many cost models + indexer cost Manage costing for subgraphs diff --git a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout index 5f8f0789b..f9818f08b 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout @@ -18,10 +18,10 @@ Manage indexer configuration indexer provision Manage indexer's provision indexer disputes get Cross-check POIs submitted in the network indexer disputes Configure allocation POI monitoring - indexer cost set variables Update cost model variables - indexer cost set model Update a cost model - indexer cost get Get cost models for one or all subgraphs - indexer cost delete Remove one or many cost models + indexer cost set variables Update cost model variables + indexer cost set model Update a cost model + indexer cost get Get cost models for one or all subgraphs + indexer cost delete Remove one or many cost models indexer cost Manage costing for subgraphs indexer connect Connect to indexer management API indexer allocations reallocate Reallocate to subgraph deployment From f769ac8bc27e8b3de239cc12af25ba052760639d Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 14:13:54 -0800 Subject: [PATCH 08/21] style: apply prettier formatting --- .../src/indexer-management/allocations.ts | 16 +++++++++------- .../indexer-management/resolvers/allocations.ts | 7 +------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/indexer-common/src/indexer-management/allocations.ts b/packages/indexer-common/src/indexer-management/allocations.ts index 87a876ff6..8f8621d32 100644 --- a/packages/indexer-common/src/indexer-management/allocations.ts +++ b/packages/indexer-common/src/indexer-management/allocations.ts @@ -1041,11 +1041,9 @@ export class AllocationManager { encodeStopServiceData(params.allocationID), ]) - const tx = - await this.network.contracts.SubgraphService.multicall.populateTransaction([ - collectCallData, - stopServiceCallData, - ]) + const tx = await this.network.contracts.SubgraphService.multicall.populateTransaction( + [collectCallData, stopServiceCallData], + ) return { protocolNetwork: params.protocolNetwork, actionID: params.actionID, @@ -1269,7 +1267,9 @@ export class AllocationManager { rewardsAssigned = rewardsEventLogs.tokensIndexerRewards } - subgraphDeploymentID = new SubgraphDeploymentID(closeEventLogs.subgraphDeploymentId) + subgraphDeploymentID = new SubgraphDeploymentID( + closeEventLogs.subgraphDeploymentId, + ) } const createEventLogs = this.network.transactionManager.findEvent( @@ -1584,7 +1584,9 @@ export class AllocationManager { throw indexerError( IndexerErrorCode.IE013, `Unfeasible action batch: Approved action batch GRT balance is ` + - `${formatGRT(batchDelta)} but available stake equals ${formatGRT(indexerFreeStake.horizon)}.`, + `${formatGRT(batchDelta)} but available stake equals ${formatGRT( + indexerFreeStake.horizon, + )}.`, ) } diff --git a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts index 331ac6b12..2e5186c36 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts @@ -1080,12 +1080,7 @@ export default { force, }) - const result = await closeAllocation( - allocationData, - poiData, - network, - logger, - ) + const result = await closeAllocation(allocationData, poiData, network, logger) const txHash = result.txHash const rewardsAssigned = result.rewardsAssigned From 69ed65c7162bd65b50616a3693b7a98bf3032f4a Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 14:27:05 -0800 Subject: [PATCH 09/21] fix(cli): remove non-existent 'cost set variables' from test references - Remove 'indexer cost set variables' from expected CLI output - This command doesn't exist (no variables.ts file in cost/set/) - Fixes failing cost.test.ts that expected output without this line Reverts incorrect changes from commit 272d624a --- .../src/__tests__/references/indexer-cost.stdout | 9 ++++----- .../src/__tests__/references/indexer-help.stdout | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout index b64e990d1..debbbf837 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout @@ -1,7 +1,6 @@ Manage costing for subgraphs - indexer cost set variables Update cost model variables - indexer cost set model Update a cost model - indexer cost get Get cost models for one or all subgraphs - indexer cost delete Remove one or many cost models - indexer cost Manage costing for subgraphs + indexer cost set model Update a cost model + indexer cost get Get cost models for one or all subgraphs + indexer cost delete Remove one or many cost models + indexer cost Manage costing for subgraphs diff --git a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout index f9818f08b..d315eadb0 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout @@ -18,8 +18,7 @@ Manage indexer configuration indexer provision Manage indexer's provision indexer disputes get Cross-check POIs submitted in the network indexer disputes Configure allocation POI monitoring - indexer cost set variables Update cost model variables - indexer cost set model Update a cost model + indexer cost set model Update a cost model indexer cost get Get cost models for one or all subgraphs indexer cost delete Remove one or many cost models indexer cost Manage costing for subgraphs From c6f3ce15c24c566019873ae3786e9f48afd1c354 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 14:32:20 -0800 Subject: [PATCH 10/21] fix(cli): restore test references from main - Remove 'indexer cost set variables' from expected CLI output - This command doesn't exist (no variables.ts file in cost/set/) - Restore proper trailing whitespace alignment - Fixes failing cost.test.ts and cli.test.ts Reverts incorrect changes from commit 272d624a --- .../src/__tests__/references/indexer-cost.stdout | 8 ++++---- .../src/__tests__/references/indexer-help.stdout | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout index debbbf837..96fb64cb6 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout @@ -1,6 +1,6 @@ Manage costing for subgraphs - indexer cost set model Update a cost model - indexer cost get Get cost models for one or all subgraphs - indexer cost delete Remove one or many cost models - indexer cost Manage costing for subgraphs + indexer cost set model Update a cost model + indexer cost get Get cost models for one or all subgraphs + indexer cost delete Remove one or many cost models + indexer cost Manage costing for subgraphs diff --git a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout index d315eadb0..8b6f5671d 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout @@ -18,7 +18,7 @@ Manage indexer configuration indexer provision Manage indexer's provision indexer disputes get Cross-check POIs submitted in the network indexer disputes Configure allocation POI monitoring - indexer cost set model Update a cost model + indexer cost set model Update a cost model indexer cost get Get cost models for one or all subgraphs indexer cost delete Remove one or many cost models indexer cost Manage costing for subgraphs From 0708c54f1b3f59c222704cc4da0694b1b6d90777 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 12:56:47 -0800 Subject: [PATCH 11/21] common: add helper utilities for single-network resolvers - Add getNetwork() to get network from context - Add getProtocolNetwork() to resolve optional protocolNetwork parameter - These helpers simplify resolver code by centralizing network resolution --- .../src/indexer-management/resolvers/utils.ts | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/packages/indexer-common/src/indexer-management/resolvers/utils.ts b/packages/indexer-common/src/indexer-management/resolvers/utils.ts index baa061fdf..dbcc55e69 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/utils.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/utils.ts @@ -1,26 +1,50 @@ -import { - MultiNetworks, - Network, - validateNetworkIdentifier, -} from '@graphprotocol/indexer-common' +import { Network, validateNetworkIdentifier } from '@graphprotocol/indexer-common' +import { IndexerManagementResolverContext } from '../client' -export function extractNetwork( - unvalidatedNetworkIdentifier: string, - multiNetworks: MultiNetworks, -): Network { - let networkIdentifier: string - try { - networkIdentifier = validateNetworkIdentifier(unvalidatedNetworkIdentifier) - } catch (parseError) { +/** + * Gets the protocol network identifier from the context's single network. + * Falls back to provided value if given (for backwards compatibility during migration). + */ +export function getProtocolNetwork( + context: IndexerManagementResolverContext, + providedNetwork?: string | null, +): string { + // If provided, validate and use it (backwards compatibility) + if (providedNetwork) { + return validateNetworkIdentifier(providedNetwork) + } + + // Otherwise, get from the single configured network + if (!context.network) { throw new Error( - `Invalid protocol network identifier: '${unvalidatedNetworkIdentifier}'. Error: ${parseError}`, + 'No network configured. Either provide protocolNetwork argument or configure a network.', ) } - const network = multiNetworks.inner[networkIdentifier] - if (!network) { + return context.network.specification.networkIdentifier +} + +/** + * Gets the Network object from context, optionally validating a provided network identifier. + */ +export function getNetwork( + context: IndexerManagementResolverContext, + providedNetwork?: string | null, +): Network { + if (!context.network) { throw new Error( - `Could not find a configured protocol network named ${networkIdentifier}`, + 'No network configured. Either provide protocolNetwork argument or configure a network.', ) } - return network + + // If a network was provided, validate it matches the configured one + if (providedNetwork) { + const validated = validateNetworkIdentifier(providedNetwork) + if (validated !== context.network.specification.networkIdentifier) { + throw new Error( + `Provided network '${validated}' does not match configured network '${context.network.specification.networkIdentifier}'`, + ) + } + } + + return context.network } From 574cd4f51989751915cc752293bb20d073b4b30c Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 12:56:49 -0800 Subject: [PATCH 12/21] common: make protocolNetwork optional in GraphQL resolvers - Update all resolvers to use getNetwork()/getProtocolNetwork() helpers - protocolNetwork parameter now defaults to configured network - Removes need for clients to always specify network --- .../indexer-management/resolvers/actions.ts | 103 +++----- .../resolvers/allocations.ts | 144 +++++------ .../resolvers/cost-models.ts | 7 +- .../resolvers/indexer-status.ts | 83 ++----- .../resolvers/indexing-rules.ts | 56 ++--- .../resolvers/poi-disputes.ts | 43 ++-- .../resolvers/provisions.ts | 234 ++++++++---------- 7 files changed, 270 insertions(+), 400 deletions(-) diff --git a/packages/indexer-common/src/indexer-management/resolvers/actions.ts b/packages/indexer-common/src/indexer-management/resolvers/actions.ts index 308e57c3d..3851aacf9 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/actions.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/actions.ts @@ -11,16 +11,15 @@ import { ActionType, ActionUpdateInput, IndexerManagementModels, - Network, - NetworkMapped, OrderDirection, validateActionInputs, - validateNetworkIdentifier, } from '@graphprotocol/indexer-common' import { literal, Op, Transaction } from 'sequelize' import { ActionManager } from '../actions' -import groupBy from 'lodash.groupby' -import { extractNetwork } from './utils' +import { getProtocolNetwork } from './utils' + +/** ActionInput with protocolNetwork guaranteed to be set (after normalization) */ +type NormalizedActionInput = ActionInput & { protocolNetwork: string } // Perform insert, update, or no-op depending on existing queue data // INSERT - No item in the queue yet targeting this deploymentID @@ -29,7 +28,7 @@ import { extractNetwork } from './utils' // TODO: Use pending status for actions in process of execution, detect here and if duplicate pending found, NOOP async function executeQueueOperation( logger: Logger, - action: ActionInput, + action: NormalizedActionInput, actionsAwaitingExecution: Action[], recentlyAttemptedActions: Action[], models: IndexerManagementModels, @@ -142,64 +141,51 @@ export default { }) return await ActionManager.fetchActions( models, - null, - filter, orderBy, - orderDirection, + filter, first, + orderDirection, ) }, queueActions: async ( { actions }: { actions: ActionInput[] }, - { actionManager, logger, multiNetworks, models }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { actionManager, logger, network, models } = context logger.debug(`Execute 'queueActions' mutation`, { actions, }) - if (!actionManager || !multiNetworks) { + if (!actionManager || !network) { throw Error('IndexerManagementClient must be in `network` mode to modify actions') } - // Sanitize protocol network identifier + // Normalize protocol network identifier - use context network if not provided actions.forEach((action) => { - try { - action.protocolNetwork = validateNetworkIdentifier(action.protocolNetwork) - } catch (e) { - throw Error(`Invalid value for the field 'protocolNetwork'. ${e}`) - } + action.protocolNetwork = getProtocolNetwork(context, action.protocolNetwork) }) // Set proper value for isLegacy - any new actions in horizon are not legacy await Promise.all( actions.map(async (action) => { - const network = extractNetwork(action.protocolNetwork, multiNetworks) action.isLegacy = !(await network.isHorizon.value()) }), ) - // Let Network Monitors validate actions based on their protocol networks - await multiNetworks.mapNetworkMapped( - groupBy(actions, (action) => action.protocolNetwork), - (network: Network, actions: ActionInput[]) => - validateActionInputs(actions, network.networkMonitor, logger), - ) + // Validate actions using the network monitor + await validateActionInputs(actions, network.networkMonitor, logger) let results: ActionResult[] = [] // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await models.Action.sequelize!.transaction(async (transaction) => { - const alreadyQueuedActions = await ActionManager.fetchActions(models, transaction, { + const alreadyQueuedActions = await ActionManager.fetchActions(models, null, { status: ActionStatus.QUEUED, }) - const alreadyApprovedActions = await ActionManager.fetchActions( - models, - transaction, - { - status: ActionStatus.APPROVED, - }, - ) + const alreadyApprovedActions = await ActionManager.fetchActions(models, null, { + status: ActionStatus.APPROVED, + }) const actionsAwaitingExecution = alreadyQueuedActions.concat(alreadyApprovedActions) // Fetch recently attempted actions @@ -207,23 +193,15 @@ export default { [Op.gte]: literal("NOW() - INTERVAL '15m'"), } - const recentlyFailedActions = await ActionManager.fetchActions( - models, - transaction, - { - status: ActionStatus.FAILED, - updatedAt: last15Minutes, - }, - ) + const recentlyFailedActions = await ActionManager.fetchActions(models, null, { + status: ActionStatus.FAILED, + updatedAt: last15Minutes, + }) - const recentlySuccessfulActions = await ActionManager.fetchActions( - models, - transaction, - { - status: ActionStatus.SUCCESS, - updatedAt: last15Minutes, - }, - ) + const recentlySuccessfulActions = await ActionManager.fetchActions(models, null, { + status: ActionStatus.SUCCESS, + updatedAt: last15Minutes, + }) logger.trace('Recently attempted actions', { recentlySuccessfulActions, @@ -234,7 +212,8 @@ export default { recentlySuccessfulActions, ) - for (const action of actions) { + // Cast to NormalizedActionInput since we've already normalized protocolNetwork + for (const action of actions as NormalizedActionInput[]) { const result = await executeQueueOperation( logger, action, @@ -369,28 +348,18 @@ export default { if (!actionManager) { throw Error('IndexerManagementClient must be in `network` mode to modify actions') } - const result: NetworkMapped = await actionManager.multiNetworks.map( - async (network: Network) => { - logger.debug(`Execute 'executeApprovedActions' mutation`, { - protocolNetwork: network.specification.networkIdentifier, - }) - try { - return await actionManager.executeApprovedActions(network) - } catch (error) { - logger.error('Failed to execute approved actions for network', { - protocolNetwork: network.specification.networkIdentifier, - error, - }) - return [] - } - }, - ) - return Object.values(result).flat() + logger.debug(`Execute 'executeApprovedActions' mutation`) + try { + return await actionManager.executeApprovedActions() + } catch (error) { + logger.error('Failed to execute approved actions', { error }) + return [] + } }, } // Helper function to assess equality among a enqueued and a proposed actions -function compareActions(enqueued: Action, proposed: ActionInput): boolean { +function compareActions(enqueued: Action, proposed: NormalizedActionInput): boolean { // actions are not the same if they target different protocol networks if (enqueued.protocolNetwork !== proposed.protocolNetwork) { return false diff --git a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts index 2e5186c36..a2faf0974 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts @@ -40,7 +40,7 @@ import { encodeStopServiceData, PaymentTypes, } from '@graphprotocol/toolshed' -import { extractNetwork } from './utils' +import { getNetwork, getProtocolNetwork } from './utils' import { tryParseCustomError } from '../../utils' import { GraphNode } from '../../graph-node' @@ -851,95 +851,79 @@ async function reallocateAllocation( export default { allocations: async ( { filter }: { filter: AllocationFilter }, - { multiNetworks, logger }: IndexerManagementResolverContext, + { network, logger }: IndexerManagementResolverContext, ): Promise => { logger.debug('Execute allocations() query', { filter, }) - if (!multiNetworks) { + if (!network) { throw Error( 'IndexerManagementClient must be in `network` mode to fetch allocations', ) } - const allocationsByNetwork = await multiNetworks.map( - async (network: Network): Promise => { - // Return early if a different protocol network is specifically requested - if ( - filter.protocolNetwork && - filter.protocolNetwork !== network.specification.networkIdentifier - ) { - return [] - } - - const { - networkMonitor, - networkSubgraph, - contracts, - specification: { - indexerOptions: { address }, - }, - } = network - - const [currentEpoch, maxAllocationDuration, epochLength] = await Promise.all([ - networkMonitor.networkCurrentEpoch(), - networkMonitor.maxAllocationDuration(), - contracts.EpochManager.epochLength(), - ]) - - const allocation = filter.allocation - ? filter.allocation === 'all' - ? null - : toAddress(filter.allocation) - : null - - const variables = { - indexer: toAddress(address), - allocation, - status: filter.status, - } - - const context = { - currentEpoch: currentEpoch.epochNumber, - currentEpochStartBlock: currentEpoch.startBlockNumber, - currentEpochElapsedBlocks: epochElapsedBlocks(currentEpoch), - latestBlock: currentEpoch.latestBlock, - maxAllocationDuration: maxAllocationDuration, - blocksPerEpoch: Number(epochLength), - avgBlockTime: 13000, - protocolNetwork: network.specification.networkIdentifier, - } - - return queryAllocations(logger, networkSubgraph, variables, context) + const { + networkMonitor, + networkSubgraph, + contracts, + specification: { + indexerOptions: { address }, }, - ) + } = network + + const [currentEpoch, maxAllocationDuration, epochLength] = await Promise.all([ + networkMonitor.networkCurrentEpoch(), + networkMonitor.maxAllocationDuration(), + contracts.EpochManager.epochLength(), + ]) + + const allocation = filter.allocation + ? filter.allocation === 'all' + ? null + : toAddress(filter.allocation) + : null + + const variables = { + indexer: toAddress(address), + allocation, + status: filter.status, + } + + const context = { + currentEpoch: currentEpoch.epochNumber, + currentEpochStartBlock: currentEpoch.startBlockNumber, + currentEpochElapsedBlocks: epochElapsedBlocks(currentEpoch), + latestBlock: currentEpoch.latestBlock, + maxAllocationDuration: maxAllocationDuration, + blocksPerEpoch: Number(epochLength), + avgBlockTime: 13000, + protocolNetwork: network.specification.networkIdentifier, + } - return Object.values(allocationsByNetwork).flat() + return queryAllocations(logger, networkSubgraph, variables, context) }, createAllocation: async ( { deployment, amount, - protocolNetwork, + protocolNetwork: providedProtocolNetwork, }: { deployment: string amount: string - protocolNetwork: string + protocolNetwork: string | undefined }, - { multiNetworks, graphNode, logger, models }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { graphNode, logger, models } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, providedProtocolNetwork) logger.debug('Execute createAllocation() mutation', { deployment, amount, protocolNetwork, }) - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to fetch allocations', - ) - } - const network = extractNetwork(protocolNetwork, multiNetworks) + const network = getNetwork(context, protocolNetwork) const networkMonitor = network.networkMonitor const allocationAmount = parseGRT(amount) const subgraphDeployment = new SubgraphDeploymentID(deployment) @@ -1035,29 +1019,27 @@ export default { blockNumber, publicPOI, force, - protocolNetwork, + protocolNetwork: providedProtocolNetwork, }: { allocation: string poi: string | undefined blockNumber: string | undefined publicPOI: string | undefined force: boolean - protocolNetwork: string + protocolNetwork: string | undefined }, - { logger, models, multiNetworks }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { logger, models } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, providedProtocolNetwork) logger.debug('Execute closeAllocation() mutation', { allocationID: allocation, poi: poi || 'none provided', blockNumber: blockNumber || 'none provided', publicPOI: publicPOI || 'none provided', }) - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to fetch allocations', - ) - } - const network = extractNetwork(protocolNetwork, multiNetworks) + const network = getNetwork(context, protocolNetwork) const networkMonitor = network.networkMonitor const allocationData = await networkMonitor.allocation(allocation) @@ -1129,7 +1111,7 @@ export default { publicPOI, amount, force, - protocolNetwork, + protocolNetwork: providedProtocolNetwork, }: { allocation: string poi: string | undefined @@ -1137,10 +1119,14 @@ export default { publicPOI: string | undefined amount: string force: boolean - protocolNetwork: string + protocolNetwork: string | undefined }, - { logger, models, multiNetworks }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + let { logger } = context + const { models } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, providedProtocolNetwork) logger = logger.child({ component: 'reallocateAllocationResolver', }) @@ -1152,14 +1138,8 @@ export default { force, }) - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to fetch allocations', - ) - } - // Obtain the Network object and its associated components and data - const network = extractNetwork(protocolNetwork, multiNetworks) + const network = getNetwork(context, protocolNetwork) const networkMonitor = network.networkMonitor const allocationAmount = parseGRT(amount) diff --git a/packages/indexer-common/src/indexer-management/resolvers/cost-models.ts b/packages/indexer-common/src/indexer-management/resolvers/cost-models.ts index 720878a38..d09f78ce8 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/cost-models.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/cost-models.ts @@ -78,14 +78,11 @@ export default { setCostModel: async ( { costModel }: { deployment: string; costModel: GraphQLCostModel }, - { models, multiNetworks }: IndexerManagementResolverContext, + { models, network }: IndexerManagementResolverContext, ): Promise => { - if (!multiNetworks) { + if (!network) { throw new Error('No network configuration available') } - if (Object.keys(multiNetworks.inner).length !== 1) { - throw Error('Must be in single network mode to set cost models') - } const update = parseGraphQLCostModel(costModel) // Validate cost model matches 'default => x;' where x is an integer or float diff --git a/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts b/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts index e465d9f08..24dd5a127 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts @@ -4,13 +4,8 @@ import geohash from 'ngeohash' import gql from 'graphql-tag' import { IndexerManagementResolverContext } from '../client' import { SubgraphDeploymentID } from '@graphprotocol/common-ts' -import { - indexerError, - IndexerErrorCode, - Network, - validateNetworkIdentifier, -} from '@graphprotocol/indexer-common' -import { extractNetwork } from './utils' +import { indexerError, IndexerErrorCode, Network } from '@graphprotocol/indexer-common' +import { getNetwork } from './utils' interface Test { test: (url: string) => string run: (url: string) => Promise @@ -62,16 +57,12 @@ const URL_VALIDATION_TEST: Test = { export default { indexerRegistration: async ( - { protocolNetwork: unvalidatedProtocolNetwork }: { protocolNetwork: string }, - { multiNetworks }: IndexerManagementResolverContext, + { + protocolNetwork: unvalidatedProtocolNetwork, + }: { protocolNetwork: string | undefined }, + context: IndexerManagementResolverContext, ): Promise => { - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to fetch indexer registration information', - ) - } - - const network = extractNetwork(unvalidatedProtocolNetwork, multiNetworks) + const network = getNetwork(context, unvalidatedProtocolNetwork) const protocolNetwork = network.specification.networkIdentifier const address = network.specification.indexerOptions.address const contracts = network.contracts @@ -118,16 +109,11 @@ export default { }, indexerAllocations: async ( - { protocolNetwork }: { protocolNetwork: string }, - { multiNetworks, logger }: IndexerManagementResolverContext, + { protocolNetwork: providedProtocolNetwork }: { protocolNetwork: string | undefined }, + context: IndexerManagementResolverContext, ): Promise => { - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to fetch indexer allocations', - ) - } - - const network = extractNetwork(protocolNetwork, multiNetworks) + const { logger } = context + const network = getNetwork(context, providedProtocolNetwork) const address = network.specification.indexerOptions.address try { @@ -195,46 +181,23 @@ export default { indexerEndpoints: async ( { protocolNetwork: unvalidatedProtocolNetwork }: { protocolNetwork: string | null }, - { multiNetworks, logger }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to fetch indexer endpoints', - ) - } + const { logger } = context const endpoints: Endpoints[] = [] - let networkIdentifier: string | null = null - // Validate protocol network + // Get the network - uses context network if not provided + const network = getNetwork(context, unvalidatedProtocolNetwork) try { - if (unvalidatedProtocolNetwork) { - networkIdentifier = validateNetworkIdentifier(unvalidatedProtocolNetwork) - } - } catch (parseError) { - throw new Error( - `Invalid protocol network identifier: '${unvalidatedProtocolNetwork}'. Error: ${parseError}`, - ) + const networkEndpoints = await endpointForNetwork(network) + endpoints.push(networkEndpoints) + } catch (err) { + // Ignore endpoints for this network + logger?.warn(`Failed to detect service endpoints for network`, { + err, + protocolNetwork: network.specification.networkIdentifier, + }) } - - await multiNetworks.map(async (network: Network) => { - // Skip if this query asks for another protocol network - if ( - networkIdentifier && - networkIdentifier !== network.specification.networkIdentifier - ) { - return - } - try { - const networkEndpoints = await endpointForNetwork(network) - endpoints.push(networkEndpoints) - } catch (err) { - // Ignore endpoints for this network - logger?.warn(`Failed to detect service endpoints for network`, { - err, - protocolNetwork: network.specification.networkIdentifier, - }) - } - }) return endpoints }, } diff --git a/packages/indexer-common/src/indexer-management/resolvers/indexing-rules.ts b/packages/indexer-common/src/indexer-management/resolvers/indexing-rules.ts index 2adcdfe8e..787078047 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/indexing-rules.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/indexing-rules.ts @@ -10,9 +10,8 @@ import { IndexerManagementDefaults, IndexerManagementResolverContext } from '../ import { Transaction } from 'sequelize' import { fetchIndexingRules } from '../rules' import { ensureAllocationLifetime, processIdentifier } from '../../' -import { validateNetworkIdentifier } from '../../parsers' import groupBy from 'lodash.groupby' -import { extractNetwork } from './utils' +import { getNetwork, getProtocolNetwork } from './utils' const resetGlobalRule = async ( ruleIdentifier: IndexingRuleIdentifier, @@ -36,15 +35,17 @@ export default { identifier: indexingRuleIdentifier, merged, }: { identifier: IndexingRuleIdentifier; merged: boolean }, - { models }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { models } = context const [identifier] = await processIdentifier(indexingRuleIdentifier.identifier, { all: false, global: true, }) - // Sanitize protocol network identifier - const protocolNetwork = validateNetworkIdentifier( + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork( + context, indexingRuleIdentifier.protocolNetwork, ) @@ -67,39 +68,27 @@ export default { merged, protocolNetwork: uncheckedProtocolNetwork, }: { merged: boolean; protocolNetwork: string | undefined }, - { models }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { - // Convert the input `protocolNetwork` value to a CAIP2-ID - const protocolNetwork = uncheckedProtocolNetwork - ? validateNetworkIdentifier(uncheckedProtocolNetwork) - : undefined + const { models } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, uncheckedProtocolNetwork) return await fetchIndexingRules(models, merged, protocolNetwork) }, setIndexingRule: async ( { rule }: { rule: IndexingRuleCreationAttributes }, - { multiNetworks, models }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { models } = context if (!rule.identifier) { throw Error('Cannot set indexingRule without identifier') } - if (!rule.protocolNetwork) { - throw Error("Cannot set an indexing rule without the field 'protocolNetwork'") - } else { - try { - rule.protocolNetwork = validateNetworkIdentifier(rule.protocolNetwork) - } catch (e) { - throw Error(`Invalid value for the field 'protocolNetwork'. ${e}`) - } - } + // Get protocol network from context or provided value + rule.protocolNetwork = getProtocolNetwork(context, rule.protocolNetwork) - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to set indexing rules', - ) - } - const network = extractNetwork(rule.protocolNetwork, multiNetworks) + const network = getNetwork(context, rule.protocolNetwork) const [isValid, maxSuggestedLifetime] = await ensureAllocationLifetime(rule, network) if (!isValid) { @@ -121,15 +110,17 @@ export default { deleteIndexingRule: async ( { identifier: indexingRuleIdentifier }: { identifier: IndexingRuleIdentifier }, - { models, defaults }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { models, defaults } = context const [identifier] = await processIdentifier(indexingRuleIdentifier.identifier, { all: false, global: true, }) - // Sanitize protocol network identifier - const protocolNetwork = validateNetworkIdentifier( + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork( + context, indexingRuleIdentifier.protocolNetwork, ) @@ -161,13 +152,14 @@ export default { deleteIndexingRules: async ( { identifiers: indexingRuleIdentifiers }: { identifiers: IndexingRuleIdentifier[] }, - { models, defaults }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { models, defaults } = context let totalNumDeleted = 0 - // Sanitize protocol network identifiers + // Normalize protocol network identifiers - use context network if not provided for (const identifier of indexingRuleIdentifiers) { - identifier.protocolNetwork = validateNetworkIdentifier(identifier.protocolNetwork) + identifier.protocolNetwork = getProtocolNetwork(context, identifier.protocolNetwork) } // Batch deletions by the `IndexingRuleIdentifier.protocolNetwork` attribute . diff --git a/packages/indexer-common/src/indexer-management/resolvers/poi-disputes.ts b/packages/indexer-common/src/indexer-management/resolvers/poi-disputes.ts index faf200fcd..48661a472 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/poi-disputes.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/poi-disputes.ts @@ -2,17 +2,20 @@ import { POIDispute, POIDisputeIdentifier, POIDisputeCreationAttributes } from '../models' import { IndexerManagementResolverContext } from '../client' -import { validateNetworkIdentifier } from '../../parsers' import { Op, WhereOptions } from 'sequelize' import groupBy from 'lodash.groupby' +import { getProtocolNetwork } from './utils' export default { dispute: async ( { identifier }: { identifier: POIDisputeIdentifier }, - { models }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { models } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, identifier.protocolNetwork) const dispute = await models.POIDispute.findOne({ - where: { ...identifier }, + where: { allocationID: identifier.allocationID, protocolNetwork }, }) return dispute?.toGraphQL() || dispute }, @@ -23,23 +26,19 @@ export default { minClosedEpoch, protocolNetwork: uncheckedProtocolNetwork, }: { status: string; minClosedEpoch: number; protocolNetwork: string | undefined }, - { models }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { - // Sanitize protocol network identifier - const protocolNetwork = uncheckedProtocolNetwork - ? validateNetworkIdentifier(uncheckedProtocolNetwork) - : undefined + const { models } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, uncheckedProtocolNetwork) // eslint-disable-next-line @typescript-eslint/no-explicit-any const sqlAndExpression: WhereOptions = [ { status }, { closedEpoch: { [Op.gte]: minClosedEpoch } }, + { protocolNetwork }, ] - if (protocolNetwork) { - sqlAndExpression.push({ protocolNetwork }) - } - const disputes = await models.POIDispute.findAll({ where: { [Op.and]: sqlAndExpression }, order: [['allocationAmount', 'DESC']], @@ -49,14 +48,12 @@ export default { storeDisputes: async ( { disputes }: { disputes: POIDisputeCreationAttributes[] }, - { models }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { - // Sanitize protocol network identifiers + const { models } = context + // Normalize protocol network identifiers - use context network if not provided for (const dispute of disputes) { - if (!dispute.protocolNetwork) { - throw new Error(`Dispute is missing the attribute 'protocolNetwork'`) - } - dispute.protocolNetwork = validateNetworkIdentifier(dispute.protocolNetwork) + dispute.protocolNetwork = getProtocolNetwork(context, dispute.protocolNetwork) } const createdDisputes = await models.POIDispute.bulkCreate(disputes, { @@ -74,16 +71,14 @@ export default { deleteDisputes: async ( { identifiers }: { identifiers: POIDisputeIdentifier[] }, - { models }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { models } = context let totalNumDeleted = 0 - // Sanitize protocol network identifiers + // Normalize protocol network identifiers - use context network if not provided for (const identifier of identifiers) { - if (!identifier.protocolNetwork) { - throw new Error(`Dispute is missing the attribute 'protocolNetwork'`) - } - identifier.protocolNetwork = validateNetworkIdentifier(identifier.protocolNetwork) + identifier.protocolNetwork = getProtocolNetwork(context, identifier.protocolNetwork) } // Batch by protocolNetwork diff --git a/packages/indexer-common/src/indexer-management/resolvers/provisions.ts b/packages/indexer-common/src/indexer-management/resolvers/provisions.ts index 00c9e522f..f9056c737 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/provisions.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/provisions.ts @@ -1,11 +1,11 @@ -import { indexerError, IndexerErrorCode, Network } from '@graphprotocol/indexer-common' +import { indexerError, IndexerErrorCode } from '@graphprotocol/indexer-common' /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/ban-types */ import gql from 'graphql-tag' import { IndexerManagementResolverContext } from '@graphprotocol/indexer-common' -import { extractNetwork } from './utils' +import { getNetwork, getProtocolNetwork } from './utils' import { tryParseCustomError } from '../../utils' import { formatGRT, parseGRT } from '@graphprotocol/common-ts' import { ThawRequestType } from '@graphprotocol/toolshed' @@ -112,21 +112,20 @@ const PROVISION_QUERIES = { export default { provisions: async ( { - protocolNetwork, + protocolNetwork: providedProtocolNetwork, }: { - protocolNetwork: string + protocolNetwork: string | undefined }, - { multiNetworks, logger }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { logger } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, providedProtocolNetwork) logger.debug('Execute provisions() query', { protocolNetwork, }) - if (!multiNetworks) { - throw Error('IndexerManagementClient must be in `network` mode to fetch provisions') - } - - const network = extractNetwork(protocolNetwork, multiNetworks) + const network = getNetwork(context, protocolNetwork) if (!(await network.isHorizon.value())) { throw indexerError(IndexerErrorCode.IE082) @@ -136,73 +135,65 @@ export default { const dataService = network.contracts.SubgraphService.target.toString().toLowerCase() const idleStake = await network.contracts.HorizonStaking.getIdleStake(indexer) - const provisionsByNetwork = await multiNetworks.map( - async (network: Network): Promise => { - // Return early if a different protocol network is specifically requested - if ( - protocolNetwork && - protocolNetwork !== network.specification.networkIdentifier - ) { - return [] - } - - const { networkSubgraph } = network + const { networkSubgraph } = network - logger.trace('Query Provisions', { - indexer, - dataService, - }) + logger.trace('Query Provisions', { + indexer, + dataService, + }) - const result = await networkSubgraph.checkedQuery(PROVISION_QUERIES.all, { - indexer, - dataService, - }) + const result = await networkSubgraph.checkedQuery(PROVISION_QUERIES.all, { + indexer, + dataService, + }) - if (result.error) { - logger.error('Querying provisions failed', { - error: result.error, - }) - } + if (result.error) { + logger.error('Querying provisions failed', { + error: result.error, + }) + } - return result.data.provisions.map((provision) => ({ - id: provision.id, - dataService, - indexer, - tokensProvisioned: provision.tokensProvisioned, - tokensAllocated: provision.tokensAllocated, - tokensThawing: provision.tokensThawing, - maxVerifierCut: provision.maxVerifierCut, - thawingPeriod: provision.thawingPeriod, - protocolNetwork: network.specification.networkIdentifier, - idleStake: idleStake.toString(), - })) - }, + return result.data.provisions.map( + (provision: { + id: string + tokensProvisioned: string + tokensAllocated: string + tokensThawing: string + maxVerifierCut: string + thawingPeriod: string + }) => ({ + id: provision.id, + dataService, + indexer, + tokensProvisioned: provision.tokensProvisioned, + tokensAllocated: provision.tokensAllocated, + tokensThawing: provision.tokensThawing, + maxVerifierCut: provision.maxVerifierCut, + thawingPeriod: provision.thawingPeriod, + protocolNetwork: network.specification.networkIdentifier, + idleStake: idleStake.toString(), + }), ) - - return Object.values(provisionsByNetwork).flat() }, addToProvision: async ( { - protocolNetwork, + protocolNetwork: providedProtocolNetwork, amount, }: { - protocolNetwork: string + protocolNetwork: string | undefined amount: string }, - { multiNetworks, logger }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { logger } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, providedProtocolNetwork) logger.debug('Execute addToProvision() mutation', { protocolNetwork, amount, }) - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to add stake to a provision', - ) - } - - const network = extractNetwork(protocolNetwork, multiNetworks) + const network = getNetwork(context, protocolNetwork) const networkMonitor = network.networkMonitor const contracts = network.contracts const transactionManager = network.transactionManager @@ -302,26 +293,23 @@ export default { }, thawFromProvision: async ( { - protocolNetwork, + protocolNetwork: providedProtocolNetwork, amount, }: { - protocolNetwork: string + protocolNetwork: string | undefined amount: string }, - { multiNetworks, logger }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { logger } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, providedProtocolNetwork) logger.debug('Execute thawFromProvision() mutation', { protocolNetwork, amount, }) - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to add stake to a provision', - ) - } - - const network = extractNetwork(protocolNetwork, multiNetworks) + const network = getNetwork(context, protocolNetwork) const networkMonitor = network.networkMonitor const contracts = network.contracts const transactionManager = network.transactionManager @@ -448,21 +436,20 @@ export default { }, thawRequests: async ( { - protocolNetwork, + protocolNetwork: providedProtocolNetwork, }: { - protocolNetwork: string + protocolNetwork: string | undefined }, - { multiNetworks, logger }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { logger } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, providedProtocolNetwork) logger.debug('Execute thawRequests() query', { protocolNetwork, }) - if (!multiNetworks) { - throw Error('IndexerManagementClient must be in `network` mode to fetch provisions') - } - - const network = extractNetwork(protocolNetwork, multiNetworks) + const network = getNetwork(context, protocolNetwork) if (!(await network.isHorizon.value())) { throw indexerError(IndexerErrorCode.IE082) @@ -471,74 +458,61 @@ export default { const indexer = network.specification.indexerOptions.address.toLowerCase() const dataService = network.contracts.SubgraphService.target.toString().toLowerCase() - const thawRequestsByNetwork = await multiNetworks.map( - async (network: Network): Promise => { - // Return early if a different protocol network is specifically requested - if ( - protocolNetwork && - protocolNetwork !== network.specification.networkIdentifier - ) { - return [] - } - - const { networkSubgraph } = network + const { networkSubgraph } = network - logger.trace('Query Thaw Requests', { - indexer, - dataService, - }) - - const result = await networkSubgraph.checkedQuery( - PROVISION_QUERIES.thawRequests, - { - indexer, - dataService, - }, - ) + logger.trace('Query Thaw Requests', { + indexer, + dataService, + }) - if (result.error) { - logger.error('Querying thaw requests failed', { - error: result.error, - }) - } + const result = await networkSubgraph.checkedQuery(PROVISION_QUERIES.thawRequests, { + indexer, + dataService, + }) - const currentBlockTimestamp = - (await network.networkProvider.getBlock('latest'))?.timestamp ?? 0 + if (result.error) { + logger.error('Querying thaw requests failed', { + error: result.error, + }) + } - return result.data.thawRequests.map((thawRequest) => ({ - id: thawRequest.id, - fulfilled: thawRequest.fulfilled, - dataService, - indexer, - shares: thawRequest.shares, - thawingUntil: thawRequest.thawingUntil, - currentBlockTimestamp: currentBlockTimestamp.toString(), - protocolNetwork: network.specification.networkIdentifier, - })) - }, + const currentBlockTimestamp = + (await network.networkProvider.getBlock('latest'))?.timestamp ?? 0 + + return result.data.thawRequests.map( + (thawRequest: { + id: string + fulfilled: string + shares: string + thawingUntil: string + }) => ({ + id: thawRequest.id, + fulfilled: thawRequest.fulfilled, + dataService, + indexer, + shares: thawRequest.shares, + thawingUntil: thawRequest.thawingUntil, + currentBlockTimestamp: currentBlockTimestamp.toString(), + protocolNetwork: network.specification.networkIdentifier, + }), ) - - return Object.values(thawRequestsByNetwork).flat() }, removeFromProvision: async ( { - protocolNetwork, + protocolNetwork: providedProtocolNetwork, }: { - protocolNetwork: string + protocolNetwork: string | undefined }, - { multiNetworks, logger }: IndexerManagementResolverContext, + context: IndexerManagementResolverContext, ): Promise => { + const { logger } = context + // Get protocol network from context or provided value + const protocolNetwork = getProtocolNetwork(context, providedProtocolNetwork) logger.debug('Execute removeFromProvision() mutation', { protocolNetwork, }) - if (!multiNetworks) { - throw Error( - 'IndexerManagementClient must be in `network` mode to add stake to a provision', - ) - } - - const network = extractNetwork(protocolNetwork, multiNetworks) + const network = getNetwork(context, protocolNetwork) const networkMonitor = network.networkMonitor const contracts = network.contracts const transactionManager = network.transactionManager From 6db93aceb01e918c43c8f223d0579e98c8a3bff3 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 12:56:49 -0800 Subject: [PATCH 13/21] common: refactor ActionManager and RulesManager for single network - Change from MultiNetworks to single Network - Simplify internal logic that previously iterated over networks - Update action and rule management to work with single network context --- packages/indexer-common/src/actions.ts | 7 +- .../src/indexer-management/actions.ts | 235 ++++++++---------- .../src/indexer-management/rules.ts | 19 +- 3 files changed, 111 insertions(+), 150 deletions(-) diff --git a/packages/indexer-common/src/actions.ts b/packages/indexer-common/src/actions.ts index 68769dc9b..c67f10ec0 100644 --- a/packages/indexer-common/src/actions.ts +++ b/packages/indexer-common/src/actions.ts @@ -53,7 +53,12 @@ export interface ActionInput { reason: string status: ActionStatus priority: number | undefined - protocolNetwork: string + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Optional in API - resolvers will use the context's single network if not provided. + */ + protocolNetwork?: string isLegacy: boolean } diff --git a/packages/indexer-common/src/indexer-management/actions.ts b/packages/indexer-common/src/indexer-management/actions.ts index dc3df35c7..157932940 100644 --- a/packages/indexer-common/src/indexer-management/actions.ts +++ b/packages/indexer-common/src/indexer-management/actions.ts @@ -13,8 +13,6 @@ import { IndexerErrorCode, IndexerManagementModels, isActionFailure, - MultiNetworks, - NetworkMapped, Network, OrderDirection, GraphNode, @@ -23,37 +21,34 @@ import { import { Order, Transaction } from 'sequelize' import { Eventual, join, Logger } from '@graphprotocol/common-ts' -import groupBy from 'lodash.groupby' export class ActionManager { - declare multiNetworks: MultiNetworks + declare network: Network declare logger: Logger declare models: IndexerManagementModels - declare allocationManagers: NetworkMapped + declare allocationManager: AllocationManager executeBatchActionsPromise: Promise | undefined static async create( - multiNetworks: MultiNetworks, + network: Network, logger: Logger, models: IndexerManagementModels, graphNode: GraphNode, ): Promise { const actionManager = new ActionManager() - actionManager.multiNetworks = multiNetworks + actionManager.network = network actionManager.logger = logger.child({ component: 'ActionManager' }) actionManager.models = models - actionManager.allocationManagers = await multiNetworks.map(async (network) => { - return new AllocationManager( - logger.child({ - component: 'AllocationManager', - protocolNetwork: network.specification.networkIdentifier, - }), - models, - graphNode, - network, - ) - }) + actionManager.allocationManager = new AllocationManager( + logger.child({ + component: 'AllocationManager', + protocolNetwork: network.specification.networkIdentifier, + }), + models, + graphNode, + network, + ) logger.info('Begin monitoring the queue for approved actions to execute') await actionManager.monitorQueue() @@ -61,11 +56,7 @@ export class ActionManager { return actionManager } - private async batchReady( - approvedActions: Action[], - network: Network, - logger: Logger, - ): Promise { + private async batchReady(approvedActions: Action[], logger: Logger): Promise { logger.info('Batch ready?', { approvedActions, }) @@ -75,6 +66,8 @@ export class ActionManager { return false } + const network = this.network + // In auto management mode the worker will execute the batch if: // 1) Number of approved actions >= minimum batch size // or 2) Oldest affected allocation will expiring after the current epoch @@ -125,6 +118,9 @@ export class ActionManager { async monitorQueue(): Promise { const logger = this.logger.child({ component: 'QueueMonitor' }) + const network = this.network + const protocolNetwork = network.specification.networkIdentifier + const approvedActions: Eventual = sequentialTimerMap( { logger, @@ -136,6 +132,7 @@ export class ActionManager { try { actions = await ActionManager.fetchActions(this.models, null, { status: [ActionStatus.APPROVED, ActionStatus.DEPLOYING], + protocolNetwork, }) logger.trace(`Fetched ${actions.length} approved actions`) } catch (err) { @@ -153,62 +150,53 @@ export class ActionManager { join({ approvedActions }).pipe(async ({ approvedActions }) => { logger.debug('Approved actions found, evaluating batch') - const approvedActionsByNetwork: NetworkMapped = groupBy( - approvedActions, - (action: Action) => action.protocolNetwork, - ) - await this.multiNetworks.mapNetworkMapped( - approvedActionsByNetwork, - async (network: Network, approvedActions: Action[]) => { - const networkLogger = logger.child({ - protocolNetwork: network.specification.networkIdentifier, - indexer: network.specification.indexerOptions.address, - operator: network.transactionManager.wallet.address, + const networkLogger = logger.child({ + protocolNetwork: network.specification.networkIdentifier, + indexer: network.specification.indexerOptions.address, + operator: network.transactionManager.wallet.address, + }) + + if (await this.batchReady(approvedActions, networkLogger)) { + const paused = await network.paused.value() + const isOperator = await network.isOperator.value() + networkLogger.debug('Batch ready, preparing to execute', { + paused, + isOperator, + protocolNetwork: network.specification.networkIdentifier, + }) + // Do nothing else if the network is paused + if (paused) { + networkLogger.info( + `The network is currently paused, not doing anything until it resumes`, + ) + return + } + + // Do nothing if we're not authorized as an operator for the indexer + if (!isOperator) { + networkLogger.error(`Not authorized as an operator for the indexer`, { + err: indexerError(IndexerErrorCode.IE034), }) + return + } - if (await this.batchReady(approvedActions, network, networkLogger)) { - const paused = await network.paused.value() - const isOperator = await network.isOperator.value() - networkLogger.debug('Batch ready, preparing to execute', { - paused, - isOperator, - protocolNetwork: network.specification.networkIdentifier, - }) - // Do nothing else if the network is paused - if (paused) { - networkLogger.info( - `The network is currently paused, not doing anything until it resumes`, - ) - return - } - - // Do nothing if we're not authorized as an operator for the indexer - if (!isOperator) { - networkLogger.error(`Not authorized as an operator for the indexer`, { - err: indexerError(IndexerErrorCode.IE034), - }) - return - } - - networkLogger.info('Executing batch of approved actions', { - actions: approvedActions, - note: 'If actions were approved very recently they may be missing from this batch', - }) + networkLogger.info('Executing batch of approved actions', { + actions: approvedActions, + note: 'If actions were approved very recently they may be missing from this batch', + }) - try { - const attemptedActions = await this.executeApprovedActions(network) - networkLogger.trace('Attempted to execute all approved actions', { - actions: attemptedActions, - }) - } catch (error) { - networkLogger.error('Failed to execute batch of approved actions', { - error, - }) - } - } - }, - ) + try { + const attemptedActions = await this.executeApprovedActions() + networkLogger.trace('Attempted to execute all approved actions', { + actions: attemptedActions, + }) + } catch (error) { + networkLogger.error('Failed to execute batch of approved actions', { + error, + }) + } + } }) } @@ -275,7 +263,7 @@ export class ActionManager { } // a promise guard to ensure that only one batch of actions is executed at a time - async executeApprovedActions(network: Network): Promise { + async executeApprovedActions(): Promise { if (this.executeBatchActionsPromise) { this.logger.warn('Previous batch action execution is still in progress') return this.executeBatchActionsPromise @@ -283,7 +271,7 @@ export class ActionManager { let updatedActions: Action[] = [] try { - this.executeBatchActionsPromise = this.executeApprovedActionsInner(network) + this.executeBatchActionsPromise = this.executeApprovedActionsInner() updatedActions = await this.executeBatchActionsPromise } catch (error) { this.logger.error(`Failed to execute batch of approved actions -> ${error}`) @@ -293,9 +281,9 @@ export class ActionManager { return updatedActions } - async executeApprovedActionsInner(network: Network): Promise { + async executeApprovedActionsInner(): Promise { let updatedActions: Action[] = [] - const protocolNetwork = network.specification.networkIdentifier + const protocolNetwork = this.network.specification.networkIdentifier const logger = this.logger.child({ function: 'executeApprovedActions', protocolNetwork, @@ -371,9 +359,6 @@ export class ActionManager { startTimeMs: Date.now() - batchStartTime, }) - const allocationManager = - this.allocationManagers[network.specification.networkIdentifier] - let results: AllocationResult[] try { // TODO: we should lift the batch execution (graph-node, then contracts) up to here so we can @@ -394,92 +379,68 @@ export class ActionManager { ) } // This will return all results if successful, if failed it will return the failed actions - results = await allocationManager.executeBatch( + results = await this.allocationManager.executeBatch( prioritizedActions, onFinishedDeploying, ) - logger.debug('Completed batch action execution', { - results, - endTimeMs: Date.now() - batchStartTime, - }) } catch (error) { - // Release the actions from the PENDING state. This means they will be retried again on the next batch execution. - logger.error( - `Error raised during executeBatch, releasing ${prioritizedActions.length} actions from PENDING state. \ - These will be attempted again on the next batch.`, - error, - ) + logger.error('Failed to execute batch of approved actions', { error }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await this.models.Action.sequelize!.transaction( { isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE }, async (transaction) => { - return await this.markActions( - prioritizedActions, - transaction, - ActionStatus.APPROVED, - ) + await this.markActions(prioritizedActions, transaction, ActionStatus.APPROVED) }, ) return [] } - - // Happy path: execution went well (success or failure but no exceptions). Update the actions with the results. - updatedActions = await this.models.Action.sequelize!.transaction( + logger.debug('Finished executing batch of approved actions', { + results, + elapsedMs: Date.now() - batchStartTime, + }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await this.models.Action.sequelize!.transaction( { isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE }, async (transaction) => { - return await this.updateActionStatusesWithResults(results, transaction) + updatedActions = await this.updateActionStatusesWithResults( + results, + transaction, + ) }, ) - - logger.debug('Updated action statuses', { - updatedActions, - updatedTimeMs: Date.now() - batchStartTime, - }) } catch (error) { - logger.error(`Failed to execute batch tx on staking contract: ${error}`) - throw indexerError(IndexerErrorCode.IE072, error) + logger.error('Failed to execute batch of approved actions', { error }) } - - logger.debug('End executing approved actions') return updatedActions } - public static async fetchActions( + static async fetchActions( models: IndexerManagementModels, - transaction: Transaction | null, - filter: ActionFilter, - orderBy?: ActionParams, - orderDirection?: OrderDirection, + orderBy: ActionParams | null, + filter?: ActionFilter, first?: number, + orderDirection?: OrderDirection, ): Promise { - const orderObject: Order = orderBy + const order: Order | undefined = orderBy ? [[orderBy.toString(), orderDirection ?? 'desc']] - : [['id', 'desc']] - + : undefined + const whereClause = filter ? actionFilterToWhereOptions(filter) : undefined + const limit = first ?? undefined return await models.Action.findAll({ - transaction, - where: actionFilterToWhereOptions(filter), - order: orderObject, - limit: first, + order, + where: whereClause, + limit, }) } - public static async updateActions( + static async updateActions( models: IndexerManagementModels, action: ActionUpdateInput, filter: ActionFilter, ): Promise<[number, Action[]]> { - if (Object.keys(filter).length === 0) { - throw Error( - 'Cannot bulk update actions without a filter, please provide a least 1 filter value', - ) - } - return await models.Action.update( - { ...action }, - { - where: actionFilterToWhereOptions(filter), - returning: true, - validate: true, - }, - ) + return await models.Action.update(action, { + where: actionFilterToWhereOptions(filter), + returning: true, + }) } } diff --git a/packages/indexer-common/src/indexer-management/rules.ts b/packages/indexer-common/src/indexer-management/rules.ts index 55a4d0ccd..f48195c3d 100644 --- a/packages/indexer-common/src/indexer-management/rules.ts +++ b/packages/indexer-common/src/indexer-management/rules.ts @@ -4,27 +4,21 @@ import { INDEXING_RULE_GLOBAL, IndexingRule, IndexingRuleAttributes, - MultiNetworks, Network, sequentialTimerMap, } from '@graphprotocol/indexer-common' import { parseIndexingRule } from '../rules' import groupBy from 'lodash.groupby' -import { extractNetwork } from './resolvers/utils' import { IndexingRuleCreationAttributes } from './models' export class RulesManager { - declare multiNetworks: MultiNetworks + declare network: Network declare models: IndexerManagementModels declare logger: Logger - static async create( - multiNetworks: MultiNetworks, - logger: Logger, - models: IndexerManagementModels, - ) { + static async create(network: Network, logger: Logger, models: IndexerManagementModels) { const rulesManager = new RulesManager() - rulesManager.multiNetworks = multiNetworks + rulesManager.network = network rulesManager.logger = logger rulesManager.models = models @@ -36,6 +30,8 @@ export class RulesManager { async monitorRules(): Promise { const logger = this.logger.child({ component: 'RulesMonitor' }) + const protocolNetwork = this.network.specification.networkIdentifier + const rules: Eventual = sequentialTimerMap( { logger, @@ -45,7 +41,7 @@ export class RulesManager { logger.trace('Fetching indexing rules') let rules: IndexingRuleAttributes[] = [] try { - rules = await fetchIndexingRules(this.models, true) + rules = await fetchIndexingRules(this.models, true, protocolNetwork) logger.trace(`Fetched ${rules.length} indexing rules`) } catch (err) { logger.warn('Failed to fetch indexing rules', { err }) @@ -62,10 +58,9 @@ export class RulesManager { join({ rules }).pipe(async ({ rules }) => { logger.info(`Indexing rules found, evaluating allocation lifetime`) for (const rule of rules) { - const network = extractNetwork(rule.protocolNetwork, this.multiNetworks) const [isValid, maxSuggestedLifetime] = await ensureAllocationLifetime( rule, - network, + this.network, ) if (!isValid) { logger.warn(`Invalid rule allocation lifetime. Indexing rewards at risk!`, { From dd1506ac044ea0d6890e00aea8e76293854cb2c8 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 12:56:50 -0800 Subject: [PATCH 14/21] common: update IndexerManagementClient for single network - Replace multiNetworks option with single network - Update IndexerManagementResolverContext to use Network instead of MultiNetworks - Simplify client creation and context passing --- .../src/indexer-management/client.ts | 57 ++++++++----------- .../src/indexer-management/models/action.ts | 5 ++ .../models/indexing-rule.ts | 26 ++++++++- .../indexer-management/models/poi-dispute.ts | 26 ++++++++- .../indexer-common/src/query-fees/models.ts | 55 ++++++++++++++++++ 5 files changed, 131 insertions(+), 38 deletions(-) diff --git a/packages/indexer-common/src/indexer-management/client.ts b/packages/indexer-common/src/indexer-management/client.ts index 27cd68ce9..671c90363 100644 --- a/packages/indexer-common/src/indexer-management/client.ts +++ b/packages/indexer-common/src/indexer-management/client.ts @@ -14,12 +14,7 @@ import poiDisputeResolvers from './resolvers/poi-disputes' import statusResolvers from './resolvers/indexer-status' import provisionResolvers from './resolvers/provisions' import { GraphNode } from '../graph-node' -import { - ActionManager, - MultiNetworks, - Network, - RulesManager, -} from '@graphprotocol/indexer-common' +import { ActionManager, Network, RulesManager } from '@graphprotocol/indexer-common' export interface IndexerManagementResolverContext { models: IndexerManagementModels @@ -28,7 +23,7 @@ export interface IndexerManagementResolverContext { defaults: IndexerManagementDefaults actionManager: ActionManager | undefined rulesManager: RulesManager | undefined - multiNetworks: MultiNetworks | undefined + network: Network | undefined } const SCHEMA_SDL = gql` @@ -157,7 +152,7 @@ const SCHEMA_SDL = gql` source: String! reason: String! priority: Int! - protocolNetwork: String! + protocolNetwork: String isLegacy: Boolean! } @@ -226,7 +221,7 @@ const SCHEMA_SDL = gql` input POIDisputeIdentifier { allocationID: String! - protocolNetwork: String! + protocolNetwork: String } type POIDispute { @@ -260,7 +255,7 @@ const SCHEMA_SDL = gql` previousEpochStartBlockNumber: Int! previousEpochReferenceProof: String status: String! - protocolNetwork: String! + protocolNetwork: String } type IndexingRule { @@ -298,12 +293,12 @@ const SCHEMA_SDL = gql` decisionBasis: IndexingDecisionBasis requireSupported: Boolean safety: Boolean - protocolNetwork: String! + protocolNetwork: String } input IndexingRuleIdentifier { identifier: String! - protocolNetwork: String! + protocolNetwork: String } type GeoLocation { @@ -445,9 +440,9 @@ const SCHEMA_SDL = gql` merged: Boolean! = false ): IndexingRule indexingRules(merged: Boolean! = false, protocolNetwork: String): [IndexingRule!]! - indexerRegistration(protocolNetwork: String!): [IndexerRegistration]! + indexerRegistration(protocolNetwork: String): [IndexerRegistration]! indexerDeployments: [IndexerDeployment]! - indexerAllocations(protocolNetwork: String!): [IndexerAllocation]! + indexerAllocations(protocolNetwork: String): [IndexerAllocation]! indexerEndpoints(protocolNetwork: String): [IndexerEndpoints!]! costModels(deployments: [String!]): [CostModel!]! @@ -471,8 +466,8 @@ const SCHEMA_SDL = gql` first: Int ): [Action]! - provisions(protocolNetwork: String!): [Provision!]! - thawRequests(protocolNetwork: String!): [ThawRequest!]! + provisions(protocolNetwork: String): [Provision!]! + thawRequests(protocolNetwork: String): [ThawRequest!]! } type Mutation { @@ -490,7 +485,7 @@ const SCHEMA_SDL = gql` deployment: String! amount: String! indexNode: String - protocolNetwork: String! + protocolNetwork: String ): CreateAllocationResult! closeAllocation( allocation: String! @@ -498,7 +493,7 @@ const SCHEMA_SDL = gql` blockNumber: Int publicPOI: String force: Boolean - protocolNetwork: String! + protocolNetwork: String ): CloseAllocationResult! reallocateAllocation( allocation: String! @@ -507,9 +502,9 @@ const SCHEMA_SDL = gql` publicPOI: String amount: String! force: Boolean - protocolNetwork: String! + protocolNetwork: String ): ReallocateAllocationResult! - submitCollectReceiptsJob(allocation: String!, protocolNetwork: String!): Boolean! + submitCollectReceiptsJob(allocation: String!, protocolNetwork: String): Boolean! updateAction(action: ActionInput!): Action! updateActions(filter: ActionFilter!, action: ActionUpdateInput!): [Action]! @@ -519,9 +514,9 @@ const SCHEMA_SDL = gql` approveActions(actionIDs: [String!]!): [Action]! executeApprovedActions: [ActionResult!]! - addToProvision(protocolNetwork: String!, amount: String!): AddToProvisionResult! - thawFromProvision(protocolNetwork: String!, amount: String!): ThawFromProvisionResult! - removeFromProvision(protocolNetwork: String!): RemoveFromProvisionResult! + addToProvision(protocolNetwork: String, amount: String!): AddToProvisionResult! + thawFromProvision(protocolNetwork: String, amount: String!): ThawFromProvisionResult! + removeFromProvision(protocolNetwork: String): RemoveFromProvisionResult! } ` @@ -536,7 +531,7 @@ export interface IndexerManagementClientOptions { logger: Logger models: IndexerManagementModels graphNode: GraphNode - multiNetworks: MultiNetworks | undefined + network: Network | undefined defaults: IndexerManagementDefaults } @@ -552,12 +547,10 @@ export class IndexerManagementClient extends Client { } } -// TODO:L2: Put the IndexerManagementClient creation inside the Agent, and receive -// MultiNetworks from it export const createIndexerManagementClient = async ( options: IndexerManagementClientOptions, ): Promise => { - const { models, graphNode, logger, defaults, multiNetworks } = options + const { models, graphNode, logger, defaults, network } = options const schema = buildSchema(print(SCHEMA_SDL)) const resolvers = { ...indexingRuleResolvers, @@ -569,12 +562,12 @@ export const createIndexerManagementClient = async ( ...provisionResolvers, } - const actionManager = multiNetworks - ? await ActionManager.create(multiNetworks, logger, models, graphNode) + const actionManager = network + ? await ActionManager.create(network, logger, models, graphNode) : undefined - const rulesManager = multiNetworks - ? await RulesManager.create(multiNetworks, logger, models) + const rulesManager = network + ? await RulesManager.create(network, logger, models) : undefined const context: IndexerManagementResolverContext = { @@ -582,7 +575,7 @@ export const createIndexerManagementClient = async ( graphNode, defaults, logger: logger.child({ component: 'IndexerManagementClient' }), - multiNetworks, + network, actionManager, rulesManager, } diff --git a/packages/indexer-common/src/indexer-management/models/action.ts b/packages/indexer-common/src/indexer-management/models/action.ts index e40f37b99..cc77862d1 100644 --- a/packages/indexer-common/src/indexer-management/models/action.ts +++ b/packages/indexer-common/src/indexer-management/models/action.ts @@ -35,6 +35,7 @@ export class Action extends Model< declare createdAt: CreationOptional declare updatedAt: CreationOptional + /** @deprecated LEGACY DEBT: Multi-network removed. See protocolNetwork field comment in defineActionModels. */ declare protocolNetwork: string declare isLegacy: boolean @@ -150,6 +151,10 @@ export const defineActionModels = (sequelize: Sequelize): ActionModels => { allowNull: true, defaultValue: null, }, + // LEGACY DEBT: Part of composite primary key for multi-network support. + // Multi-network has been removed but this field remains in the DB schema. + // Always populated with the single configured network. Can be removed in a future + // migration that changes the primary key structure. protocolNetwork: { type: DataTypes.STRING(50), primaryKey: true, diff --git a/packages/indexer-common/src/indexer-management/models/indexing-rule.ts b/packages/indexer-common/src/indexer-management/models/indexing-rule.ts index 594191537..99ebb06f8 100644 --- a/packages/indexer-common/src/indexer-management/models/indexing-rule.ts +++ b/packages/indexer-common/src/indexer-management/models/indexing-rule.ts @@ -30,14 +30,29 @@ export interface IndexingRuleAttributes { decisionBasis: IndexingDecisionBasis requireSupported: boolean safety: boolean + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Always populated with the single configured network. Can be removed in a future + * migration that changes the primary key structure. + */ protocolNetwork: string } -// Unambiguously identify a Indexing Rule in the Database. -// This type should match the IndexingRules primary key columns. +/** + * Unambiguously identify an Indexing Rule in the Database. + * This type should match the IndexingRules primary key columns. + */ export interface IndexingRuleIdentifier { identifier: string - protocolNetwork: string + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Always populated with the single configured network. Can be removed in a future + * migration that changes the primary key structure. + * Optional in API - resolvers will use the context's single network if not provided. + */ + protocolNetwork?: string } export interface IndexingRuleCreationAttributes @@ -82,6 +97,7 @@ export class IndexingRule declare decisionBasis: IndexingDecisionBasis declare requireSupported: boolean declare safety: boolean + /** @deprecated LEGACY DEBT: Multi-network removed. See IndexingRuleAttributes.protocolNetwork */ declare protocolNetwork: string declare createdAt: Date @@ -259,6 +275,10 @@ export const defineIndexingRuleModels = (sequelize: Sequelize): IndexingRuleMode allowNull: false, defaultValue: true, }, + // LEGACY DEBT: Part of composite primary key for multi-network support. + // Multi-network has been removed but this field remains in the DB schema. + // Always populated with the single configured network. Can be removed in a future + // migration that changes the primary key structure. protocolNetwork: { type: DataTypes.STRING, primaryKey: true, diff --git a/packages/indexer-common/src/indexer-management/models/poi-dispute.ts b/packages/indexer-common/src/indexer-management/models/poi-dispute.ts index a4cd1e862..ccfb0a7b8 100644 --- a/packages/indexer-common/src/indexer-management/models/poi-dispute.ts +++ b/packages/indexer-common/src/indexer-management/models/poi-dispute.ts @@ -18,14 +18,29 @@ export interface POIDisputeAttributes { previousEpochStartBlockHash: string previousEpochStartBlockNumber: number status: string + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Always populated with the single configured network. Can be removed in a future + * migration that changes the primary key structure. + */ protocolNetwork: string } -// Unambiguously identify a POI Dispute in the Database. -// This type should match the POIDispute primary key columns. +/** + * Unambiguously identify a POI Dispute in the Database. + * This type should match the POIDispute primary key columns. + */ export interface POIDisputeIdentifier { allocationID: string - protocolNetwork: string + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Always populated with the single configured network. Can be removed in a future + * migration that changes the primary key structure. + * Optional in API - resolvers will use the context's single network if not provided. + */ + protocolNetwork?: string } export interface POIDisputeCreationAttributes @@ -64,6 +79,7 @@ export class POIDispute declare previousEpochStartBlockHash: string declare previousEpochStartBlockNumber: number declare status: string + /** @deprecated LEGACY DEBT: Multi-network removed. See POIDisputeAttributes.protocolNetwork */ declare protocolNetwork: string declare createdAt: Date @@ -255,6 +271,10 @@ export const definePOIDisputeModels = (sequelize: Sequelize): POIDisputeModels = type: DataTypes.STRING, allowNull: false, }, + // LEGACY DEBT: Part of composite primary key for multi-network support. + // Multi-network has been removed but this field remains in the DB schema. + // Always populated with the single configured network. Can be removed in a future + // migration that changes the primary key structure. protocolNetwork: { type: DataTypes.STRING, primaryKey: true, diff --git a/packages/indexer-common/src/query-fees/models.ts b/packages/indexer-common/src/query-fees/models.ts index 132e9240e..2116584d4 100644 --- a/packages/indexer-common/src/query-fees/models.ts +++ b/packages/indexer-common/src/query-fees/models.ts @@ -110,6 +110,12 @@ export interface AllocationReceiptAttributes { allocation: Address fees: string signature: string + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Always populated with the single configured network. Can be removed in a future + * migration that changes the primary key structure. + */ protocolNetwork: string } @@ -121,6 +127,7 @@ export class AllocationReceipt public allocation!: Address public fees!: string public signature!: string + /** @deprecated LEGACY DEBT: Multi-network removed. See AllocationReceiptAttributes.protocolNetwork */ public protocolNetwork!: string public readonly createdAt!: Date @@ -131,6 +138,12 @@ export interface VoucherAttributes { allocation: Address amount: string signature: string + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Always populated with the single configured network. Can be removed in a future + * migration that changes the primary key structure. + */ protocolNetwork: string } @@ -141,6 +154,7 @@ export class Voucher extends Model implements VoucherAttribut public readonly createdAt!: Date public readonly updatedAt!: Date + /** @deprecated LEGACY DEBT: Multi-network removed. See VoucherAttributes.protocolNetwork */ public protocolNetwork!: string public readonly allocationSummary?: AllocationSummary @@ -307,6 +321,12 @@ export interface TransferReceiptAttributes { signer: Address fees: string signature: string + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Always populated with the single configured network. Can be removed in a future + * migration that changes the primary key structure. + */ protocolNetwork: string } @@ -318,6 +338,7 @@ export class TransferReceipt public signer!: Address public fees!: string public signature!: string + /** @deprecated LEGACY DEBT: Multi-network removed. See TransferReceiptAttributes.protocolNetwork */ public protocolNetwork!: string public readonly createdAt!: Date @@ -343,6 +364,12 @@ export interface TransferAttributes { signer: Address allocationClosedAt: Date | null status: TransferStatus + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Always populated with the single configured network. Can be removed in a future + * migration that changes the primary key structure. + */ protocolNetwork: string } @@ -352,6 +379,7 @@ export class Transfer extends Model implements TransferAttri public signer!: Address public allocationClosedAt!: Date | null public status!: TransferStatus + /** @deprecated LEGACY DEBT: Multi-network removed. See TransferAttributes.protocolNetwork */ public protocolNetwork!: string public readonly createdAt!: Date @@ -373,6 +401,12 @@ export interface AllocationSummaryAttributes { openTransfers: number collectedFees: string withdrawnFees: string + /** + * @deprecated LEGACY DEBT: Part of composite primary key for multi-network support. + * Multi-network has been removed but this field remains in the DB schema. + * Always populated with the single configured network. Can be removed in a future + * migration that changes the primary key structure. + */ protocolNetwork: string } @@ -388,6 +422,7 @@ export class AllocationSummary declare openTransfers: number declare collectedFees: string declare withdrawnFees: string + /** @deprecated LEGACY DEBT: Multi-network removed. See AllocationSummaryAttributes.protocolNetwork */ declare protocolNetwork: string declare readonly createdAt: Date @@ -452,6 +487,10 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { min: 0.0, }, }, + // LEGACY DEBT: Part of composite primary key for multi-network support. + // Multi-network has been removed but this field remains in the DB schema. + // Always populated with the single configured network. Can be removed in a future + // migration that changes the primary key structure. protocolNetwork: { type: DataTypes.STRING, primaryKey: true, @@ -482,6 +521,10 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { type: DataTypes.STRING, allowNull: false, }, + // LEGACY DEBT: Part of composite primary key for multi-network support. + // Multi-network has been removed but this field remains in the DB schema. + // Always populated with the single configured network. Can be removed in a future + // migration that changes the primary key structure. protocolNetwork: { type: DataTypes.STRING, primaryKey: true, @@ -693,6 +736,10 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { min: 0.0, }, }, + // LEGACY DEBT: Part of composite primary key for multi-network support. + // Multi-network has been removed but this field remains in the DB schema. + // Always populated with the single configured network. Can be removed in a future + // migration that changes the primary key structure. protocolNetwork: { type: DataTypes.STRING, primaryKey: true, @@ -734,6 +781,10 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { ), allowNull: false, }, + // LEGACY DEBT: Part of composite primary key for multi-network support. + // Multi-network has been removed but this field remains in the DB schema. + // Always populated with the single configured network. Can be removed in a future + // migration that changes the primary key structure. protocolNetwork: { type: DataTypes.STRING, primaryKey: true, @@ -781,6 +832,10 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { type: DataTypes.DECIMAL, allowNull: false, }, + // LEGACY DEBT: Part of composite primary key for multi-network support. + // Multi-network has been removed but this field remains in the DB schema. + // Always populated with the single configured network. Can be removed in a future + // migration that changes the primary key structure. protocolNetwork: { type: DataTypes.STRING, primaryKey: true, From 7f3bd5669d0be3a22e2581db692258339283b60d Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 12:56:50 -0800 Subject: [PATCH 15/21] cli: make protocolNetwork optional in CLI commands - Update command helpers to handle optional protocolNetwork - CLI commands no longer require --network flag when only one network configured --- packages/indexer-cli/src/actions.ts | 2 +- packages/indexer-cli/src/command-helpers.ts | 32 ++++++++----------- .../src/commands/indexer/allocations/close.ts | 2 +- .../commands/indexer/allocations/collect.ts | 2 +- .../commands/indexer/allocations/create.ts | 2 +- .../src/commands/indexer/allocations/get.ts | 2 +- .../indexer/allocations/reallocate.ts | 2 +- .../src/commands/indexer/provision/add.ts | 2 +- .../src/commands/indexer/provision/get.ts | 2 +- .../commands/indexer/provision/list-thaw.ts | 2 +- .../src/commands/indexer/provision/remove.ts | 2 +- .../src/commands/indexer/provision/thaw.ts | 2 +- 12 files changed, 24 insertions(+), 30 deletions(-) diff --git a/packages/indexer-cli/src/actions.ts b/packages/indexer-cli/src/actions.ts index 74f464929..5270f7ebc 100644 --- a/packages/indexer-cli/src/actions.ts +++ b/packages/indexer-cli/src/actions.ts @@ -35,7 +35,7 @@ export async function buildActionInput( reason: string, status: ActionStatus, priority: number, - protocolNetwork: string, + protocolNetwork?: string, ): Promise { await validateActionInput(type, actionParams) diff --git a/packages/indexer-cli/src/command-helpers.ts b/packages/indexer-cli/src/command-helpers.ts index 212a9b7c8..b1fa236b2 100644 --- a/packages/indexer-cli/src/command-helpers.ts +++ b/packages/indexer-cli/src/command-helpers.ts @@ -230,22 +230,15 @@ export function suggestCommands( } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function extractProtocolNetworkOption( - options: { - [key: string]: any - }, - required = false, -): string | undefined { +export function extractProtocolNetworkOption(options: { + [key: string]: any +}): string | undefined { const { n, network } = options // Tries to extract the --network option from Gluegun options. - // Throws if required is set to true and the option is not found. + // Returns undefined if not provided (single network mode will use context network). if (!n && !network) { - if (required) { - throw new Error("The option '--network' is required") - } else { - return undefined - } + return undefined } // Check for invalid usage @@ -264,13 +257,14 @@ export function extractProtocolNetworkOption( } } -// Same as `extractProtocolNetworkOption`, but always require the --network option to be set -export function requireProtocolNetworkOption(options: { [key: string]: any }): string { - const protocolNetwork = extractProtocolNetworkOption(options, true) - if (!protocolNetwork) { - throw new Error("The option '--network' is required") - } - return protocolNetwork +/** + * @deprecated Use extractProtocolNetworkOption instead. Network is no longer required + * as the system operates in single-network mode. + */ +export function requireProtocolNetworkOption(options: { + [key: string]: any +}): string | undefined { + return extractProtocolNetworkOption(options) } export function wrapCell(value: unknown, wrapWidth: number): string { diff --git a/packages/indexer-cli/src/commands/indexer/allocations/close.ts b/packages/indexer-cli/src/commands/indexer/allocations/close.ts index 3e4c033c4..d9b114f01 100644 --- a/packages/indexer-cli/src/commands/indexer/allocations/close.ts +++ b/packages/indexer-cli/src/commands/indexer/allocations/close.ts @@ -83,7 +83,7 @@ module.exports = { spinner.text = `Closing allocation '${id}` try { - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!protocolNetwork) { throw new Error( diff --git a/packages/indexer-cli/src/commands/indexer/allocations/collect.ts b/packages/indexer-cli/src/commands/indexer/allocations/collect.ts index d0b293b17..331a5ce0f 100644 --- a/packages/indexer-cli/src/commands/indexer/allocations/collect.ts +++ b/packages/indexer-cli/src/commands/indexer/allocations/collect.ts @@ -52,7 +52,7 @@ module.exports = { spinner.text = `Collecting receipts for allocation '${id}` try { - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!protocolNetwork) { throw new Error( diff --git a/packages/indexer-cli/src/commands/indexer/allocations/create.ts b/packages/indexer-cli/src/commands/indexer/allocations/create.ts index f635e4d34..9047ec813 100644 --- a/packages/indexer-cli/src/commands/indexer/allocations/create.ts +++ b/packages/indexer-cli/src/commands/indexer/allocations/create.ts @@ -52,7 +52,7 @@ module.exports = { const [deploymentID, amount, indexNode] = parameters.array || [] try { - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!deploymentID || !amount || !protocolNetwork) { throw new Error( diff --git a/packages/indexer-cli/src/commands/indexer/allocations/get.ts b/packages/indexer-cli/src/commands/indexer/allocations/get.ts index 5be6a6f70..4d1cf0e43 100644 --- a/packages/indexer-cli/src/commands/indexer/allocations/get.ts +++ b/packages/indexer-cli/src/commands/indexer/allocations/get.ts @@ -46,7 +46,7 @@ module.exports = { } try { - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!['json', 'yaml', 'table'].includes(outputFormat)) { throw Error( diff --git a/packages/indexer-cli/src/commands/indexer/allocations/reallocate.ts b/packages/indexer-cli/src/commands/indexer/allocations/reallocate.ts index 95923c4ac..7d6f68e18 100644 --- a/packages/indexer-cli/src/commands/indexer/allocations/reallocate.ts +++ b/packages/indexer-cli/src/commands/indexer/allocations/reallocate.ts @@ -75,7 +75,7 @@ module.exports = { } try { - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!protocolNetwork) { throw new Error( diff --git a/packages/indexer-cli/src/commands/indexer/provision/add.ts b/packages/indexer-cli/src/commands/indexer/provision/add.ts index ce4be5257..c0fe0a710 100644 --- a/packages/indexer-cli/src/commands/indexer/provision/add.ts +++ b/packages/indexer-cli/src/commands/indexer/provision/add.ts @@ -42,7 +42,7 @@ module.exports = { throw new Error('Must provide an amount to add to the provision') } - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!['json', 'yaml', 'table'].includes(outputFormat)) { throw Error( diff --git a/packages/indexer-cli/src/commands/indexer/provision/get.ts b/packages/indexer-cli/src/commands/indexer/provision/get.ts index 31e9dad99..c8727df5c 100644 --- a/packages/indexer-cli/src/commands/indexer/provision/get.ts +++ b/packages/indexer-cli/src/commands/indexer/provision/get.ts @@ -37,7 +37,7 @@ module.exports = { } try { - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!['json', 'yaml', 'table'].includes(outputFormat)) { throw Error( diff --git a/packages/indexer-cli/src/commands/indexer/provision/list-thaw.ts b/packages/indexer-cli/src/commands/indexer/provision/list-thaw.ts index 167c2844a..eecaef782 100644 --- a/packages/indexer-cli/src/commands/indexer/provision/list-thaw.ts +++ b/packages/indexer-cli/src/commands/indexer/provision/list-thaw.ts @@ -36,7 +36,7 @@ module.exports = { } try { - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!['json', 'yaml', 'table'].includes(outputFormat)) { throw Error( diff --git a/packages/indexer-cli/src/commands/indexer/provision/remove.ts b/packages/indexer-cli/src/commands/indexer/provision/remove.ts index c585ef5aa..40352cdc5 100644 --- a/packages/indexer-cli/src/commands/indexer/provision/remove.ts +++ b/packages/indexer-cli/src/commands/indexer/provision/remove.ts @@ -38,7 +38,7 @@ module.exports = { } try { - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!['json', 'yaml', 'table'].includes(outputFormat)) { throw Error( diff --git a/packages/indexer-cli/src/commands/indexer/provision/thaw.ts b/packages/indexer-cli/src/commands/indexer/provision/thaw.ts index 2e4dda78a..fc6dc433e 100644 --- a/packages/indexer-cli/src/commands/indexer/provision/thaw.ts +++ b/packages/indexer-cli/src/commands/indexer/provision/thaw.ts @@ -42,7 +42,7 @@ module.exports = { throw new Error('Must provide an amount to thaw from the provision') } - const protocolNetwork = extractProtocolNetworkOption(parameters.options, true) + const protocolNetwork = extractProtocolNetworkOption(parameters.options) if (!['json', 'yaml', 'table'].includes(outputFormat)) { throw Error( From 43682d08692d2cbd6198f2bea8818b8650431446 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 12:56:50 -0800 Subject: [PATCH 16/21] agent: refactor Agent class for single network operation - Replace MultiNetworks with single network/operator - Remove NetworkAndOperator type, simplify AgentConfigs - Simplify reconciliationLoop() - remove NetworkMapped types - Update syncing server to take single network instead of record - Remove complex zip/mapNetworkMapped operations --- packages/indexer-agent/src/agent.ts | 618 +++++++------------ packages/indexer-agent/src/commands/start.ts | 26 +- packages/indexer-agent/src/syncing-server.ts | 15 +- packages/indexer-agent/src/types.ts | 11 +- 4 files changed, 252 insertions(+), 418 deletions(-) diff --git a/packages/indexer-agent/src/agent.ts b/packages/indexer-agent/src/agent.ts index 872df17d0..7aee596e5 100644 --- a/packages/indexer-agent/src/agent.ts +++ b/packages/indexer-agent/src/agent.ts @@ -29,8 +29,6 @@ import { GraphNode, Operator, validateProviderNetworkIdentifier, - MultiNetworks, - NetworkMapped, DeploymentManagementMode, SubgraphStatus, sequentialTimerMap, @@ -40,14 +38,7 @@ import { import PQueue from 'p-queue' import pMap from 'p-map' import pFilter from 'p-filter' -import zip from 'lodash.zip' -import { AgentConfigs, NetworkAndOperator } from './types' - -type ActionReconciliationContext = [ - AllocationDecision[], - number, - HorizonTransitionValue, -] +import { AgentConfigs } from './types' const deploymentInList = ( list: SubgraphDeploymentID[], @@ -128,65 +119,12 @@ export const convertSubgraphBasedRulesToDeploymentBased = ( return rules } -// Extracts the network identifier from a pair of matching Network and Operator objects. -function networkAndOperatorIdentity({ - network, - operator, -}: NetworkAndOperator): string { - const networkId = network.specification.networkIdentifier - const operatorId = operator.specification.networkIdentifier - if (networkId !== operatorId) { - throw new Error( - `Network and Operator pairs have different network identifiers: ${networkId} != ${operatorId}`, - ) - } - return networkId -} - -// Helper function to produce a `MultiNetworks` while validating its -// inputs. -function createMultiNetworks( - networks: Network[], - operators: Operator[], -): MultiNetworks { - // Validate that Networks and Operator arrays have even lengths and - // contain unique, matching network identifiers. - const visited = new Set() - const validInputs = - networks.length === operators.length && - networks.every((network, index) => { - const sameIdentifier = - network.specification.networkIdentifier === - operators[index].specification.networkIdentifier - if (!sameIdentifier) { - return false - } - if (visited.has(network.specification.networkIdentifier)) { - return false - } - visited.add(network.specification.networkIdentifier) - return true - }) - - if (!validInputs) { - throw new Error( - 'Invalid Networks and Operator pairs used in Agent initialization', - ) - } - // Note on undefineds: `lodash.zip` can return `undefined` if array lengths are - // uneven, but we have just checked that. - const networksAndOperators = zip(networks, operators).map(pair => { - const [network, operator] = pair - return { network: network!, operator: operator! } - }) - return new MultiNetworks(networksAndOperators, networkAndOperatorIdentity) -} - export class Agent { logger: Logger metrics: Metrics graphNode: GraphNode - multiNetworks: MultiNetworks + network: Network + operator: Operator indexerManagement: IndexerManagementClient offchainSubgraphs: SubgraphDeploymentID[] deploymentManagement: DeploymentManagementMode @@ -197,10 +135,8 @@ export class Agent { this.metrics = configs.metrics this.graphNode = configs.graphNode this.indexerManagement = configs.indexerManagement - this.multiNetworks = createMultiNetworks( - configs.networks, - configs.operators, - ) + this.network = configs.network + this.operator = configs.operator this.offchainSubgraphs = configs.offchainSubgraphs this.deploymentManagement = configs.deploymentManagement this.pollingInterval = configs.pollingInterval @@ -226,24 +162,20 @@ export class Agent { // * Ensure NetworkSubgraph is indexing // * Register the Indexer in the Network // -------------------------------------------------------------------------------- - await this.multiNetworks.map( - async ({ network, operator }: NetworkAndOperator) => { - try { - await operator.ensureGlobalIndexingRule() - await this.ensureAllSubgraphsIndexing(network) - await network.provision() - await network.register() - } catch (err) { - this.logger.critical( - `Failed to prepare indexer for ${network.specification.networkIdentifier}`, - { - error: err.message, - }, - ) - process.exit(1) - } - }, - ) + try { + await this.operator.ensureGlobalIndexingRule() + await this.ensureAllSubgraphsIndexing(this.network) + await this.network.provision() + await this.network.register() + } catch (err) { + this.logger.critical( + `Failed to prepare indexer for ${this.network.specification.networkIdentifier}`, + { + error: err.message, + }, + ) + process.exit(1) + } this.reconciliationLoop() return this @@ -253,68 +185,65 @@ export class Agent { const requestIntervalSmall = this.pollingInterval const requestIntervalLarge = this.pollingInterval * 5 const logger = this.logger.child({ component: 'ReconciliationLoop' }) - const currentEpochNumber: Eventual> = - sequentialTimerMap( - { logger, milliseconds: requestIntervalLarge }, - async () => - await this.multiNetworks.map(({ network }) => { - logger.trace('Fetching current epoch number', { - protocolNetwork: network.specification.networkIdentifier, - }) - return network.networkMonitor.currentEpochNumber() - }), - { - onError: error => - logger.warn(`Failed to fetch current epoch`, { error }), - }, - ) + const network = this.network + const operator = this.operator - const maxAllocationDuration: Eventual< - NetworkMapped - > = sequentialTimerMap( + const currentEpochNumber: Eventual = sequentialTimerMap( { logger, milliseconds: requestIntervalLarge }, - () => - this.multiNetworks.map(({ network }) => { - logger.trace('Fetching max allocation duration', { - protocolNetwork: network.specification.networkIdentifier, - }) - return network.networkMonitor.maxAllocationDuration() - }), + async () => { + logger.trace('Fetching current epoch number', { + protocolNetwork: network.specification.networkIdentifier, + }) + return network.networkMonitor.currentEpochNumber() + }, { onError: error => - logger.warn(`Failed to fetch max allocation duration`, { error }), + logger.warn(`Failed to fetch current epoch`, { error }), }, ) - const indexingRules: Eventual> = + const maxAllocationDuration: Eventual = + sequentialTimerMap( + { logger, milliseconds: requestIntervalLarge }, + () => { + logger.trace('Fetching max allocation duration', { + protocolNetwork: network.specification.networkIdentifier, + }) + return network.networkMonitor.maxAllocationDuration() + }, + { + onError: error => + logger.warn(`Failed to fetch max allocation duration`, { error }), + }, + ) + + const indexingRules: Eventual = sequentialTimerMap( { logger, milliseconds: requestIntervalSmall }, async () => { - return this.multiNetworks.map(async ({ network, operator }) => { - logger.trace('Fetching indexing rules', { - protocolNetwork: network.specification.networkIdentifier, - }) - let rules = await operator.indexingRules(true) - const subgraphRuleIds = rules - .filter( - rule => rule.identifierType == SubgraphIdentifierType.SUBGRAPH, - ) - .map(rule => rule.identifier!) - const subgraphsMatchingRules = - await network.networkMonitor.subgraphs(subgraphRuleIds) - if (subgraphsMatchingRules.length >= 1) { - const epochLength = - await network.contracts.EpochManager.epochLength() - const blockPeriod = 15 - const bufferPeriod = Number(epochLength) * blockPeriod * 100 // 100 epochs - rules = convertSubgraphBasedRulesToDeploymentBased( - rules, - subgraphsMatchingRules, - bufferPeriod, - ) - } - return rules + logger.trace('Fetching indexing rules', { + protocolNetwork: network.specification.networkIdentifier, }) + let rules = await operator.indexingRules(true) + const subgraphRuleIds = rules + .filter( + rule => rule.identifierType == SubgraphIdentifierType.SUBGRAPH, + ) + .map(rule => rule.identifier!) + const subgraphsMatchingRules = + await network.networkMonitor.subgraphs(subgraphRuleIds) + if (subgraphsMatchingRules.length >= 1) { + const epochLength = + await network.contracts.EpochManager.epochLength() + const blockPeriod = 15 + const bufferPeriod = Number(epochLength) * blockPeriod * 100 // 100 epochs + rules = convertSubgraphBasedRulesToDeploymentBased( + rules, + subgraphsMatchingRules, + bufferPeriod, + ) + } + return rules }, { onError: error => @@ -351,16 +280,15 @@ export class Agent { }, ) - const networkDeployments: Eventual> = + const networkDeployments: Eventual = sequentialTimerMap( { logger, milliseconds: requestIntervalSmall }, - async () => - await this.multiNetworks.map(({ network }) => { - logger.trace('Fetching network deployments', { - protocolNetwork: network.specification.networkIdentifier, - }) - return network.networkMonitor.subgraphDeployments() - }), + async () => { + logger.trace('Fetching network deployments', { + protocolNetwork: network.specification.networkIdentifier, + }) + return network.networkMonitor.subgraphDeployments() + }, { onError: error => logger.warn( @@ -370,52 +298,40 @@ export class Agent { }, ) - const networkDeploymentAllocationDecisions: Eventual< - NetworkMapped - > = join({ - networkDeployments, - indexingRules, - }).tryMap( - async ({ indexingRules, networkDeployments }) => { - return this.multiNetworks.mapNetworkMapped( - this.multiNetworks.zip(indexingRules, networkDeployments), - async ( - { network }: NetworkAndOperator, - [indexingRules, networkDeployments]: [ - IndexingRuleAttributes[], - SubgraphDeployment[], - ], - ) => { - // Skip evaluation entirely for networks in manual allocation mode - if ( - network.specification.indexerOptions.allocationManagementMode === - AllocationManagementMode.MANUAL - ) { - logger.trace( - `Skipping deployment evaluation since AllocationManagementMode = 'manual'`, - { - protocolNetwork: network.specification.networkIdentifier, - }, - ) - return [] - } + const networkDeploymentAllocationDecisions: Eventual = + join({ + networkDeployments, + indexingRules, + }).tryMap( + async ({ indexingRules, networkDeployments }) => { + // Skip evaluation entirely for networks in manual allocation mode + if ( + network.specification.indexerOptions.allocationManagementMode === + AllocationManagementMode.MANUAL + ) { + logger.trace( + `Skipping deployment evaluation since AllocationManagementMode = 'manual'`, + { + protocolNetwork: network.specification.networkIdentifier, + }, + ) + return [] + } - // Identify subgraph deployments on the network that are worth picking up; - // these may overlap with the ones we're already indexing - logger.trace('Evaluating which deployments are worth allocating to') - return indexingRules.length === 0 - ? [] - : evaluateDeployments(logger, networkDeployments, indexingRules) - }, - ) - }, - { - onError: error => - logger.warn(`Failed to evaluate deployments, trying again later`, { - error, - }), - }, - ) + // Identify subgraph deployments on the network that are worth picking up; + // these may overlap with the ones we're already indexing + logger.trace('Evaluating which deployments are worth allocating to') + return indexingRules.length === 0 + ? [] + : evaluateDeployments(logger, networkDeployments, indexingRules) + }, + { + onError: error => + logger.warn(`Failed to evaluate deployments, trying again later`, { + error, + }), + }, + ) // let targetDeployments be an union of targetAllocations // and offchain subgraphs. @@ -425,12 +341,14 @@ export class Agent { }).tryMap( async ({ indexingRules, networkDeploymentAllocationDecisions }) => { logger.trace('Resolving target deployments') - const targetDeploymentIDs: Set = - consolidateAllocationDecisions(networkDeploymentAllocationDecisions) + const targetDeploymentIDs: Set = new Set( + networkDeploymentAllocationDecisions + .filter(decision => decision.toAllocate === true) + .map(decision => decision.deployment), + ) // Add offchain subgraphs to the deployment list from rules - Object.values(indexingRules) - .flat() + indexingRules .filter( rule => rule?.decisionBasis === IndexingDecisionBasis.OFFCHAIN, ) @@ -451,23 +369,21 @@ export class Agent { }, ) - const activeAllocations: Eventual> = - sequentialTimerMap( - { logger, milliseconds: requestIntervalSmall }, - () => - this.multiNetworks.map(({ network }) => { - logger.trace('Fetching active allocations', { - protocolNetwork: network.specification.networkIdentifier, - }) - return network.networkMonitor.allocations(AllocationStatus.ACTIVE) - }), - { - onError: () => - logger.warn( - `Failed to obtain active allocations, trying again later`, - ), - }, - ) + const activeAllocations: Eventual = sequentialTimerMap( + { logger, milliseconds: requestIntervalSmall }, + () => { + logger.trace('Fetching active allocations', { + protocolNetwork: network.specification.networkIdentifier, + }) + return network.networkMonitor.allocations(AllocationStatus.ACTIVE) + }, + { + onError: () => + logger.warn( + `Failed to obtain active allocations, trying again later`, + ), + }, + ) // `activeAllocations` is used to trigger this Eventual, but not really needed // inside. @@ -477,20 +393,14 @@ export class Agent { }).tryMap( // eslint-disable-next-line @typescript-eslint/no-unused-vars async ({ activeAllocations: _, currentEpochNumber }) => { - const allocationsByNetwork = await this.multiNetworks.mapNetworkMapped( + logger.trace('Fetching recently closed allocations', { + protocolNetwork: network.specification.networkIdentifier, currentEpochNumber, - async ({ network }, epochNumber): Promise => { - logger.trace('Fetching recently closed allocations', { - protocolNetwork: network.specification.networkIdentifier, - currentEpochNumber, - }) - return network.networkMonitor.recentlyClosedAllocations( - epochNumber, - 1, - ) - }, + }) + return network.networkMonitor.recentlyClosedAllocations( + currentEpochNumber, + 1, ) - return Object.values(allocationsByNetwork).flat() }, { onError: () => @@ -500,26 +410,21 @@ export class Agent { }, ) - const disputableAllocations: Eventual> = join({ + const disputableAllocations: Eventual = join({ currentEpochNumber, activeDeployments, }).tryMap( - async ({ currentEpochNumber, activeDeployments }) => - this.multiNetworks.mapNetworkMapped( + async ({ currentEpochNumber, activeDeployments }) => { + logger.trace('Fetching disputable allocations', { + protocolNetwork: network.specification.networkIdentifier, currentEpochNumber, - ({ network }: NetworkAndOperator, currentEpochNumber: number) => { - logger.trace('Fetching disputable allocations', { - protocolNetwork: network.specification.networkIdentifier, - currentEpochNumber, - }) - return network.networkMonitor.disputableAllocations( - currentEpochNumber, - activeDeployments, - 0, - ) - }, - ), - + }) + return network.networkMonitor.disputableAllocations( + currentEpochNumber, + activeDeployments, + 0, + ) + }, { onError: () => logger.warn( @@ -554,31 +459,16 @@ export class Agent { }) try { - const disputableEpochs = await this.multiNetworks.mapNetworkMapped( - currentEpochNumber, - async ( - { network }: NetworkAndOperator, - currentEpochNumber: number, - ) => - currentEpochNumber - - (network.specification.indexerOptions - .poiDisputableEpochs as number), - ) + const disputableEpoch = + currentEpochNumber - + (network.specification.indexerOptions.poiDisputableEpochs as number) // Find disputable allocations - await this.multiNetworks.mapNetworkMapped( - this.multiNetworks.zip(disputableEpochs, disputableAllocations), - async ( - { network, operator }: NetworkAndOperator, - [disputableEpoch, disputableAllocations]: [number, Allocation[]], - ): Promise => { - await this.identifyPotentialDisputes( - disputableAllocations, - disputableEpoch, - operator, - network, - ) - }, + await this.identifyPotentialDisputes( + disputableAllocations, + disputableEpoch, + operator, + network, ) } catch (err) { logger.warn(`Failed POI dispute monitoring`, { err }) @@ -586,7 +476,7 @@ export class Agent { const eligibleAllocations: Allocation[] = [ ...recentlyClosedAllocations, - ...Object.values(activeAllocations).flat(), + ...activeAllocations, ] // Reconcile deployments @@ -783,8 +673,6 @@ export class Agent { }) } - // This function assumes that allocations and deployments passed to it have already - // been retrieved from multiple networks. async reconcileDeployments( activeDeployments: SubgraphDeploymentID[], targetDeployments: SubgraphDeploymentID[], @@ -796,15 +684,13 @@ export class Agent { // Ensure the network subgraph deployment is _always_ indexed // ---------------------------------------------------------------------------------------- let indexingNetworkSubgraph = false - await this.multiNetworks.map(async ({ network }) => { - if (network.networkSubgraph.deployment) { - const networkDeploymentID = network.networkSubgraph.deployment.id - if (!deploymentInList(targetDeployments, networkDeploymentID)) { - targetDeployments.push(networkDeploymentID) - indexingNetworkSubgraph = true - } + if (this.network.networkSubgraph.deployment) { + const networkDeploymentID = this.network.networkSubgraph.deployment.id + if (!deploymentInList(targetDeployments, networkDeploymentID)) { + targetDeployments.push(networkDeploymentID) + indexingNetworkSubgraph = true } - }) + } // ---------------------------------------------------------------------------------------- // Inspect Deployments and Networks @@ -1043,115 +929,92 @@ export class Agent { } async reconcileActions( - networkDeploymentAllocationDecisions: NetworkMapped, - epoch: NetworkMapped, - maxAllocationDuration: NetworkMapped, + allocationDecisions: AllocationDecision[], + epoch: number, + maxAllocationDuration: HorizonTransitionValue, ): Promise { + const network = this.network + const operator = this.operator + // -------------------------------------------------------------------------------- // Filter out networks set to `manual` allocation management mode, and ensure the // Network Subgraph is NEVER allocated towards // -------------------------------------------------------------------------------- - const validatedAllocationDecisions = - await this.multiNetworks.mapNetworkMapped( - networkDeploymentAllocationDecisions, - async ( - { network }: NetworkAndOperator, - allocationDecisions: AllocationDecision[], - ) => { - if ( - network.specification.indexerOptions.allocationManagementMode === - AllocationManagementMode.MANUAL - ) { - this.logger.trace( - `Skipping allocation reconciliation since AllocationManagementMode = 'manual'`, - { - protocolNetwork: network.specification.networkIdentifier, - targetDeployments: allocationDecisions - .filter(decision => decision.toAllocate) - .map(decision => decision.deployment.ipfsHash), - }, - ) - return [] as AllocationDecision[] - } - const networkSubgraphDeployment = network.networkSubgraph.deployment - if ( - networkSubgraphDeployment && - !network.specification.indexerOptions.allocateOnNetworkSubgraph - ) { - const networkSubgraphIndex = allocationDecisions.findIndex( - decision => - decision.deployment.bytes32 == - networkSubgraphDeployment.id.bytes32, - ) - if (networkSubgraphIndex >= 0) { - allocationDecisions[networkSubgraphIndex].toAllocate = false - } - } - return allocationDecisions + if ( + network.specification.indexerOptions.allocationManagementMode === + AllocationManagementMode.MANUAL + ) { + this.logger.trace( + `Skipping allocation reconciliation since AllocationManagementMode = 'manual'`, + { + protocolNetwork: network.specification.networkIdentifier, + targetDeployments: allocationDecisions + .filter(decision => decision.toAllocate) + .map(decision => decision.deployment.ipfsHash), }, ) + return + } + + const networkSubgraphDeployment = network.networkSubgraph.deployment + if ( + networkSubgraphDeployment && + !network.specification.indexerOptions.allocateOnNetworkSubgraph + ) { + const networkSubgraphIndex = allocationDecisions.findIndex( + decision => + decision.deployment.bytes32 == networkSubgraphDeployment.id.bytes32, + ) + if (networkSubgraphIndex >= 0) { + allocationDecisions[networkSubgraphIndex].toAllocate = false + } + } //---------------------------------------------------------------------------------------- - // For every network, loop through all deployments and queue allocation actions if needed + // Loop through all deployments and queue allocation actions if needed //---------------------------------------------------------------------------------------- - await this.multiNetworks.mapNetworkMapped( - this.multiNetworks.zip3( - validatedAllocationDecisions, - epoch, - maxAllocationDuration, - ), - async ( - { network, operator }: NetworkAndOperator, - [ - allocationDecisions, - epoch, - maxAllocationDuration, - ]: ActionReconciliationContext, - ) => { - // Do nothing if there are already approved actions in the queue awaiting execution - const approvedActions = await operator.fetchActions({ - status: ActionStatus.APPROVED, - protocolNetwork: network.specification.networkIdentifier, - }) - if (approvedActions.length > 0) { - this.logger.info( - `There are ${approvedActions.length} approved actions awaiting execution, will reconcile with the network once they are executed`, - { protocolNetwork: network.specification.networkIdentifier }, - ) - return - } + // Do nothing if there are already approved actions in the queue awaiting execution + const approvedActions = await operator.fetchActions({ + status: ActionStatus.APPROVED, + protocolNetwork: network.specification.networkIdentifier, + }) + if (approvedActions.length > 0) { + this.logger.info( + `There are ${approvedActions.length} approved actions awaiting execution, will reconcile with the network once they are executed`, + { protocolNetwork: network.specification.networkIdentifier }, + ) + return + } - // Accuracy check: re-fetch allocations to ensure that we have a fresh state since the - // start of the reconciliation loop. This means we don't use the allocations coming from - // the Eventual input. - const activeAllocations: Allocation[] = - await network.networkMonitor.allocations(AllocationStatus.ACTIVE) + // Accuracy check: re-fetch allocations to ensure that we have a fresh state since the + // start of the reconciliation loop. This means we don't use the allocations coming from + // the Eventual input. + const activeAllocations: Allocation[] = + await network.networkMonitor.allocations(AllocationStatus.ACTIVE) - this.logger.trace(`Reconcile allocation actions`, { - protocolNetwork: network.specification.networkIdentifier, - epoch, - maxAllocationDuration, - targetDeployments: allocationDecisions - .filter(decision => decision.toAllocate) - .map(decision => decision.deployment.ipfsHash), - activeAllocations: activeAllocations.map(allocation => ({ - id: allocation.id, - deployment: allocation.subgraphDeployment.id.ipfsHash, - createdAtEpoch: allocation.createdAtEpoch, - })), - }) + this.logger.trace(`Reconcile allocation actions`, { + protocolNetwork: network.specification.networkIdentifier, + epoch, + maxAllocationDuration, + targetDeployments: allocationDecisions + .filter(decision => decision.toAllocate) + .map(decision => decision.deployment.ipfsHash), + activeAllocations: activeAllocations.map(allocation => ({ + id: allocation.id, + deployment: allocation.subgraphDeployment.id.ipfsHash, + createdAtEpoch: allocation.createdAtEpoch, + })), + }) - return pMap(allocationDecisions, async decision => - this.reconcileDeploymentAllocationAction( - decision, - activeAllocations, - epoch, - maxAllocationDuration, - network, - operator, - ), - ) - }, + await pMap(allocationDecisions, async decision => + this.reconcileDeploymentAllocationAction( + decision, + activeAllocations, + epoch, + maxAllocationDuration, + network, + operator, + ), ) } @@ -1208,18 +1071,3 @@ export class Agent { } } } - -export interface AllocationDecisionInterface { - toAllocate: boolean - deployment: SubgraphDeploymentID -} -export function consolidateAllocationDecisions( - allocationDecisions: Record, -): Set { - return new Set( - Object.values(allocationDecisions) - .flat() - .filter(decision => decision.toAllocate === true) - .map(decision => decision.deployment), - ) -} diff --git a/packages/indexer-agent/src/commands/start.ts b/packages/indexer-agent/src/commands/start.ts index 58e0fea8f..52fd71584 100644 --- a/packages/indexer-agent/src/commands/start.ts +++ b/packages/indexer-agent/src/commands/start.ts @@ -19,7 +19,6 @@ import { GraphNode, indexerError, IndexerErrorCode, - MultiNetworks, Network, Operator, registerIndexerErrorMetrics, @@ -622,10 +621,8 @@ export async function run( // -------------------------------------------------------------------------------- // * Indexer Management (GraphQL) Server // -------------------------------------------------------------------------------- - const multiNetworks = new MultiNetworks( - networks, - (n: Network) => n.specification.networkIdentifier, - ) + // Single network mode: use the first (and only) network + const network = networks[0] const indexerManagementClient = await createIndexerManagementClient({ models: managementModels, @@ -638,7 +635,7 @@ export async function run( parallelAllocations: 1, }, }, - multiNetworks, + network, }) // -------------------------------------------------------------------------------- @@ -661,9 +658,8 @@ export async function run( await createSyncingServer({ logger, - networkSubgraphs: await multiNetworks.map( - async network => network.networkSubgraph, - ), + networkSubgraph: network.networkSubgraph, + networkIdentifier: network.specification.networkIdentifier, port: argv.syncingPort, }) logger.info(`Successfully launched syncing server`) @@ -671,10 +667,10 @@ export async function run( // -------------------------------------------------------------------------------- // * Operator // -------------------------------------------------------------------------------- - const operators: Operator[] = await pMap( - networkSpecifications, - async (spec: NetworkSpecification) => - new Operator(logger, indexerManagementClient, spec), + const operator = new Operator( + logger, + indexerManagementClient, + network.specification, ) // -------------------------------------------------------------------------------- @@ -684,9 +680,9 @@ export async function run( logger, metrics, graphNode, - operators, + operator, indexerManagement: indexerManagementClient, - networks, + network, deploymentManagement: argv.deploymentManagement, offchainSubgraphs: argv.offchainSubgraphs.map( (s: string) => new SubgraphDeploymentID(s), diff --git a/packages/indexer-agent/src/syncing-server.ts b/packages/indexer-agent/src/syncing-server.ts index 2c08da48b..37719a9e9 100644 --- a/packages/indexer-agent/src/syncing-server.ts +++ b/packages/indexer-agent/src/syncing-server.ts @@ -5,21 +5,19 @@ import bodyParser from 'body-parser' import morgan from 'morgan' import { Logger } from '@graphprotocol/common-ts' import { parse } from 'graphql' -import { - NetworkMapped, - SubgraphClient, - resolveChainId, -} from '@graphprotocol/indexer-common' +import { SubgraphClient, resolveChainId } from '@graphprotocol/indexer-common' export interface CreateSyncingServerOptions { logger: Logger - networkSubgraphs: NetworkMapped + networkSubgraph: SubgraphClient + networkIdentifier: string port: number } export const createSyncingServer = async ({ logger, - networkSubgraphs, + networkSubgraph, + networkIdentifier: configuredNetworkIdentifier, port, }: CreateSyncingServerOptions): Promise => { logger = logger.child({ component: 'SyncingServer' }) @@ -64,8 +62,7 @@ export const createSyncingServer = async ({ .send(`Unknown network identifier: '${unvalidatedNetworkIdentifier}'`) } - const networkSubgraph = networkSubgraphs[networkIdentifier] - if (!networkSubgraph) { + if (networkIdentifier !== configuredNetworkIdentifier) { return res .status(404) .send( diff --git a/packages/indexer-agent/src/types.ts b/packages/indexer-agent/src/types.ts index a93c50997..98f851e60 100644 --- a/packages/indexer-agent/src/types.ts +++ b/packages/indexer-agent/src/types.ts @@ -7,20 +7,13 @@ import { Operator, } from '@graphprotocol/indexer-common' -// Represents a pair of Network and Operator instances belonging to the same protocol -// network. Used when mapping over multiple protocol networks. -export type NetworkAndOperator = { - network: Network - operator: Operator -} - export interface AgentConfigs { logger: Logger metrics: Metrics graphNode: GraphNode - operators: Operator[] + operator: Operator indexerManagement: IndexerManagementClient - networks: Network[] + network: Network deploymentManagement: DeploymentManagementMode offchainSubgraphs: SubgraphDeploymentID[] pollingInterval: number From a351de6f6b11def11418397d1b76de2d14cbc49c Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 12:56:50 -0800 Subject: [PATCH 17/21] tests: update test utilities for single network - Rename setupMultiNetworks/setupSingleNetwork to setupNetwork - Remove MultiNetworks usage from test setup - Remove consolidateAllocationDecisions test (function removed) - Tests now use single network configuration --- packages/indexer-agent/src/__tests__/agent.ts | 44 +------------------ .../indexer-agent/src/__tests__/indexer.ts | 8 +--- .../indexer-cli/src/__tests__/cli.test.ts | 4 +- .../src/__tests__/indexer/actions.test.ts | 4 +- .../src/__tests__/indexer/cost.test.ts | 4 +- .../src/__tests__/indexer/rules.test.ts | 4 +- packages/indexer-cli/src/__tests__/util.ts | 24 +--------- .../__tests__/resolvers/actions.test.ts | 18 +++++--- .../src/indexer-management/__tests__/util.ts | 8 +--- 9 files changed, 25 insertions(+), 93 deletions(-) diff --git a/packages/indexer-agent/src/__tests__/agent.ts b/packages/indexer-agent/src/__tests__/agent.ts index 700e15f98..e1f764240 100644 --- a/packages/indexer-agent/src/__tests__/agent.ts +++ b/packages/indexer-agent/src/__tests__/agent.ts @@ -1,7 +1,4 @@ -import { - convertSubgraphBasedRulesToDeploymentBased, - consolidateAllocationDecisions, -} from '../agent' +import { convertSubgraphBasedRulesToDeploymentBased } from '../agent' import { INDEXING_RULE_GLOBAL, IndexingDecisionBasis, @@ -169,42 +166,3 @@ describe('Agent convenience function tests', () => { ).toEqual(inputRules) }) }) - -describe('consolidateAllocationDecisions function', () => { - it('produces a set with unique deployment ids', () => { - const a = new SubgraphDeploymentID( - 'QmXZiV6S13ha6QXq4dmaM3TB4CHcDxBMvGexSNu9Kc28EH', - ) - const b = new SubgraphDeploymentID( - 'QmRKs2ZfuwvmZA3QAWmCqrGUjV9pxtBUDP3wuc6iVGnjA2', - ) - const c = new SubgraphDeploymentID( - 'QmULAfA3eS5yojxeSR2KmbyuiwCGYPjymsFcpa6uYsu6CJ', - ) - - const allocationDecisions = { - 'eip155:0': [ - { deployment: a, toAllocate: false }, - { deployment: b, toAllocate: true }, - ], - 'eip155:1': [ - { deployment: b, toAllocate: true }, - { deployment: c, toAllocate: false }, - ], - 'eip155:2': [ - { deployment: c, toAllocate: true }, - { deployment: a, toAllocate: false }, - ], - } - - const expected = new Set([c, b]) - - const result = consolidateAllocationDecisions(allocationDecisions) - - expect(result).toStrictEqual(expected) - expect(result).toHaveProperty('size', 2) - expect(result).toContain(c) - expect(result).toContain(b) - expect(result).not.toContain(a) - }) -}) diff --git a/packages/indexer-agent/src/__tests__/indexer.ts b/packages/indexer-agent/src/__tests__/indexer.ts index c6b1ce463..7e7bcc6bf 100644 --- a/packages/indexer-agent/src/__tests__/indexer.ts +++ b/packages/indexer-agent/src/__tests__/indexer.ts @@ -18,7 +18,6 @@ import { specification, QueryFeeModels, defineQueryFeeModels, - MultiNetworks, loadTestYamlConfig, } from '@graphprotocol/indexer-common' import { Sequelize } from 'sequelize' @@ -153,11 +152,6 @@ const setup = async () => { metrics, ) - const multiNetworks = new MultiNetworks( - [network], - (n: Network) => n.specification.networkIdentifier, - ) - indexerManagementClient = await createIndexerManagementClient({ models, graphNode, @@ -168,7 +162,7 @@ const setup = async () => { parallelAllocations: 1, }, }, - multiNetworks, + network, }) operator = new Operator(logger, indexerManagementClient, networkSpecification) diff --git a/packages/indexer-cli/src/__tests__/cli.test.ts b/packages/indexer-cli/src/__tests__/cli.test.ts index 40fcda30c..d1b0beee5 100644 --- a/packages/indexer-cli/src/__tests__/cli.test.ts +++ b/packages/indexer-cli/src/__tests__/cli.test.ts @@ -1,10 +1,10 @@ -import { cliTest, setupMultiNetworks, teardown } from './util' +import { cliTest, setupNetwork, teardown } from './util' import path from 'path' const baseDir = path.join(__dirname) describe('Indexer cli tests', () => { - beforeEach(setupMultiNetworks) + beforeEach(setupNetwork) afterEach(teardown) describe('General', () => { diff --git a/packages/indexer-cli/src/__tests__/indexer/actions.test.ts b/packages/indexer-cli/src/__tests__/indexer/actions.test.ts index 6d432b3d4..5ceba9eba 100644 --- a/packages/indexer-cli/src/__tests__/indexer/actions.test.ts +++ b/packages/indexer-cli/src/__tests__/indexer/actions.test.ts @@ -2,7 +2,7 @@ import { cliTest, deleteFromAllTables, seedActions, - setupMultiNetworks, + setupNetwork, teardown, } from '../util' import path from 'path' @@ -10,7 +10,7 @@ import path from 'path' const baseDir = path.join(__dirname, '..') describe('Indexer actions tests', () => { describe('With indexer management server', () => { - beforeAll(setupMultiNetworks) + beforeAll(setupNetwork) afterAll(teardown) beforeEach(seedActions) afterEach(deleteFromAllTables) diff --git a/packages/indexer-cli/src/__tests__/indexer/cost.test.ts b/packages/indexer-cli/src/__tests__/indexer/cost.test.ts index f5f5e6443..f6678e1e6 100644 --- a/packages/indexer-cli/src/__tests__/indexer/cost.test.ts +++ b/packages/indexer-cli/src/__tests__/indexer/cost.test.ts @@ -4,7 +4,7 @@ import { connect, deleteFromAllTables, seedCostModels, - setupSingleNetwork, + setupNetwork, } from '../util' import path from 'path' @@ -12,7 +12,7 @@ const baseDir = path.join(__dirname, '..') describe('Indexer cost tests', () => { describe('With indexer management server', () => { - beforeAll(setupSingleNetwork) + beforeAll(setupNetwork) afterAll(teardown) beforeEach(seedCostModels) afterEach(deleteFromAllTables) diff --git a/packages/indexer-cli/src/__tests__/indexer/rules.test.ts b/packages/indexer-cli/src/__tests__/indexer/rules.test.ts index 67474c0a6..a4c0f79f0 100644 --- a/packages/indexer-cli/src/__tests__/indexer/rules.test.ts +++ b/packages/indexer-cli/src/__tests__/indexer/rules.test.ts @@ -4,7 +4,7 @@ import { teardown, deleteFromAllTables, seedIndexingRules, - setupMultiNetworks, + setupNetwork, } from '../util' import path from 'path' @@ -12,7 +12,7 @@ const baseDir = path.join(__dirname, '..') describe('Indexer rules tests', () => { describe('With indexer management server', () => { - beforeAll(setupMultiNetworks) + beforeAll(setupNetwork) afterAll(teardown) beforeEach(seedIndexingRules) afterEach(deleteFromAllTables) diff --git a/packages/indexer-cli/src/__tests__/util.ts b/packages/indexer-cli/src/__tests__/util.ts index 41df97e13..0639cff56 100644 --- a/packages/indexer-cli/src/__tests__/util.ts +++ b/packages/indexer-cli/src/__tests__/util.ts @@ -18,7 +18,6 @@ import { IndexerManagementModels, IndexingDecisionBasis, loadTestYamlConfig, - MultiNetworks, Network, QueryFeeModels, specification, @@ -33,7 +32,6 @@ import { parseGRT, SubgraphDeploymentID, } from '@graphprotocol/common-ts' -import cloneDeep from 'lodash.clonedeep' const INDEXER_SAVE_CLI_TEST_OUTPUT: boolean = !!process.env.INDEXER_SAVE_CLI_TEST_OUTPUT && @@ -71,15 +69,7 @@ function stripAnsi(str: string): string { return str.replace(new RegExp(pattern, 'g'), '') } -export const setupMultiNetworks = async () => { - return await setup(true) -} - -export const setupSingleNetwork = async () => { - return await setup(false) -} - -export const setup = async (multiNetworksEnabled: boolean) => { +export const setupNetwork = async () => { logger = createLogger({ name: 'Setup', async: false, @@ -113,16 +103,6 @@ export const setup = async (multiNetworksEnabled: boolean) => { metrics, ) - const fakeMainnetNetwork = cloneDeep(network) as Network - fakeMainnetNetwork.specification.networkIdentifier = 'eip155:1' - - const multiNetworks = multiNetworksEnabled - ? new MultiNetworks( - [network, fakeMainnetNetwork], - (n: Network) => n.specification.networkIdentifier, - ) - : new MultiNetworks([network], (n: Network) => n.specification.networkIdentifier) - const defaults: IndexerManagementDefaults = { globalIndexingRule: { allocationAmount: parseGRT('100'), @@ -137,7 +117,7 @@ export const setup = async (multiNetworksEnabled: boolean) => { graphNode, logger, defaults, - multiNetworks, + network, }) server = await createIndexerManagementServer({ diff --git a/packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.test.ts b/packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.test.ts index ae09ce91c..3ba5be594 100644 --- a/packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.test.ts +++ b/packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.test.ts @@ -711,7 +711,8 @@ describe.skip('Actions', () => { priority: 0, // When writing directly to the database, `protocolNetwork` must be in the CAIP2-ID format. protocolNetwork: 'eip155:421614', - } as ActionInput + isLegacy: false, + } const proposedAction = { status: ActionStatus.QUEUED, @@ -722,7 +723,8 @@ describe.skip('Actions', () => { reason: 'indexingRule', priority: 0, protocolNetwork: 'arbitrum-sepolia', - } as ActionInput + isLegacy: false, + } await managementModels.Action.create(failedAction, { validate: true, @@ -766,7 +768,8 @@ describe.skip('Actions', () => { priority: 0, // When writing directly to the database, `protocolNetwork` must be in the CAIP2-ID format. protocolNetwork: 'eip155:421614', - } as ActionInput + isLegacy: false, + } const proposedAction = { status: ActionStatus.QUEUED, @@ -777,7 +780,8 @@ describe.skip('Actions', () => { reason: 'indexingRule', priority: 0, protocolNetwork: 'arbitrum-sepolia', - } as ActionInput + isLegacy: false, + } await managementModels.Action.create(successfulAction, { validate: true, @@ -819,7 +823,8 @@ describe.skip('Actions', () => { priority: 0, // When writing directly to the database, `protocolNetwork` must be in the CAIP2-ID format. protocolNetwork: 'eip155:421614', - } as ActionInput + isLegacy: false, + } const queuedAllocateAction = { status: ActionStatus.QUEUED, @@ -831,7 +836,8 @@ describe.skip('Actions', () => { reason: 'indexingRule', priority: 0, protocolNetwork: 'arbitrum-sepolia', - } as ActionInput + isLegacy: false, + } await managementModels.Action.create(queuedUnallocateAction, { validate: true, diff --git a/packages/indexer-common/src/indexer-management/__tests__/util.ts b/packages/indexer-common/src/indexer-management/__tests__/util.ts index 83e6ee409..0fa6b98b0 100644 --- a/packages/indexer-common/src/indexer-management/__tests__/util.ts +++ b/packages/indexer-common/src/indexer-management/__tests__/util.ts @@ -9,7 +9,6 @@ import { IndexerManagementClient, IndexerManagementDefaults, loadTestYamlConfig, - MultiNetworks, Network, specification, } from '@graphprotocol/indexer-common' @@ -68,17 +67,12 @@ export const createTestManagementClient = async ( network.specification.networkIdentifier = networkIdentifierOverride } - const multiNetworks = new MultiNetworks( - [network], - (n: Network) => n.specification.networkIdentifier, - ) - return await createIndexerManagementClient({ models: managementModels, graphNode, logger, defaults, - multiNetworks, + network, }) } From 1991f95ef9456a2d88a7c151b2bcc719aaaa181d Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 12:56:50 -0800 Subject: [PATCH 18/21] common: remove MultiNetworks class and NetworkMapped type - Delete multi-networks.ts file - Remove export from index.ts - Multi-network support fully removed from codebase --- packages/indexer-common/src/index.ts | 1 - packages/indexer-common/src/multi-networks.ts | 111 ------------------ 2 files changed, 112 deletions(-) delete mode 100644 packages/indexer-common/src/multi-networks.ts diff --git a/packages/indexer-common/src/index.ts b/packages/indexer-common/src/index.ts index ab3eedd97..e22722403 100644 --- a/packages/indexer-common/src/index.ts +++ b/packages/indexer-common/src/index.ts @@ -15,5 +15,4 @@ export * from './types' export * from './utils' export * from './parsers' export * as specification from './network-specification' -export * from './multi-networks' export * from './sequential-timer' diff --git a/packages/indexer-common/src/multi-networks.ts b/packages/indexer-common/src/multi-networks.ts deleted file mode 100644 index f305d2087..000000000 --- a/packages/indexer-common/src/multi-networks.ts +++ /dev/null @@ -1,111 +0,0 @@ -import pReduce from 'p-reduce' -import isEqual from 'lodash.isequal' -import xor from 'lodash.xor' - -// A mapping of different values of type T keyed by their network identifiers -export type NetworkMapped = Record - -// Function to extract the network identifier from a value of type T -type NetworkIdentity = (element: T) => string - -// Wrapper type for performing calls over multiple values of any type, most notably -// Network and Operator instances. -// All public-facing methods should return a `NetworkMapped` or `void`. -export class MultiNetworks { - inner: NetworkMapped - constructor(elements: T[], networkIdentity: NetworkIdentity) { - if (elements.length === 0) { - throw new Error('MultiNetworks component was initialized with empty values') - } - - function reducer(accumulator: NetworkMapped, current: T): NetworkMapped { - const key = networkIdentity(current) - if (key in accumulator) { - throw new Error( - `Duplicate network identifier found while mapping value's network: ${key}`, - ) - } - // TODO: parse and validate network identifiers to standardize them - accumulator[key] = current - return accumulator - } - this.inner = elements.reduce(reducer, {}) - } - - private checkEqualKeys(a: NetworkMapped, b: NetworkMapped) { - const aKeys = Object.keys(a) - const bKeys = Object.keys(b) - if (!isEqual(aKeys, bKeys)) { - const differentKeys = xor(aKeys, bKeys) - throw new Error(`Network Mapped objects have different keys: ${differentKeys}`) - } - } - - async map(func: (value: T) => Promise): Promise> { - const entries: [string, T][] = Object.entries(this.inner) - return pReduce( - entries, - async (acc, pair) => { - const [networkIdentifier, element]: [string, T] = pair - const result = await func(element) - acc[networkIdentifier] = result - return acc - }, - {} as NetworkMapped, - ) - } - - zip(a: NetworkMapped, b: NetworkMapped): NetworkMapped<[U, V]> { - this.checkEqualKeys(a, b) - const result = {} as NetworkMapped<[U, V]> - for (const key in a) { - result[key] = [a[key], b[key]] - } - return result - } - - zip3( - a: NetworkMapped, - b: NetworkMapped, - c: NetworkMapped, - ): NetworkMapped<[U, V, W]> { - this.checkEqualKeys(a, b) - const result = {} as NetworkMapped<[U, V, W]> - for (const key in a) { - result[key] = [a[key], b[key], c[key]] - } - return result - } - - zip4( - a: NetworkMapped, - b: NetworkMapped, - c: NetworkMapped, - d: NetworkMapped, - ): NetworkMapped<[U, V, W, Y]> { - this.checkEqualKeys(a, b) - const result = {} as NetworkMapped<[U, V, W, Y]> - for (const key in a) { - result[key] = [a[key], b[key], c[key], d[key]] - } - return result - } - - async mapNetworkMapped( - nmap: NetworkMapped, - func: (inner: T, value: U) => Promise, - ): Promise> { - return pReduce( - Object.entries(nmap), - async (acc, [networkIdentifier, value]: [string, U]) => { - const inner = this.inner[networkIdentifier] - if (!inner) { - throw new Error(`Network identifier not found: ${networkIdentifier}`) - } - acc[networkIdentifier] = await func(inner, value) - return acc - }, - {} as NetworkMapped, - ) - } -} From d8844cdde7361d5039a44348f2a1591455cca9ed Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 12:56:50 -0800 Subject: [PATCH 19/21] chore: update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c80f8ddb5..2e4624642 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,4 @@ tap-contracts.json config/config.yaml commit-changes.sh +**/.claude From 2a1e5574324d7f8e67412e962c1c4f4264058fe3 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 14:28:48 -0800 Subject: [PATCH 20/21] ci: upgrade to Node.js 24 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - check-formatting.yml: Node 20 → 24 - ci.yml: test matrix [20, 22] → [22, 24] - indexer-agent-image.yml: Node 20 → 24 - indexer-cli-image.yml: Node 20 → 24 --- .../src/__tests__/indexer/rules.test.ts | 26 +++++++++---------- .../__tests__/references/indexer-cost.stdout | 9 ++++--- .../__tests__/references/indexer-help.stdout | 1 + .../indexer-rules-no-network.stderr | 1 - 4 files changed, 19 insertions(+), 18 deletions(-) delete mode 100644 packages/indexer-cli/src/__tests__/references/indexer-rules-no-network.stderr diff --git a/packages/indexer-cli/src/__tests__/indexer/rules.test.ts b/packages/indexer-cli/src/__tests__/indexer/rules.test.ts index a4c0f79f0..373e7a4ae 100644 --- a/packages/indexer-cli/src/__tests__/indexer/rules.test.ts +++ b/packages/indexer-cli/src/__tests__/indexer/rules.test.ts @@ -58,9 +58,9 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules start - no network', + 'Indexer rules start - no identifier', ['indexer', 'rules', 'start'], - 'references/indexer-rules-no-network', + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -125,9 +125,9 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules prepare - no network', + 'Indexer rules prepare - no identifier', ['indexer', 'rules', 'prepare'], - 'references/indexer-rules-no-network', + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -175,9 +175,9 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules stop - no network', + 'Indexer rules stop - no identifier', ['indexer', 'rules', 'stop'], - 'references/indexer-rules-no-network', + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -225,9 +225,9 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules maybe - no network', + 'Indexer rules maybe - no identifier', ['indexer', 'rules', 'maybe'], - 'references/indexer-rules-no-network', + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -275,9 +275,9 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules clear - no network', + 'Indexer rules clear - no identifier', ['indexer', 'rules', 'clear'], - 'references/indexer-rules-no-network', + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -342,9 +342,9 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules delete - no network', + 'Indexer rules delete - no identifier', ['indexer', 'rules', 'delete'], - 'references/indexer-rules-no-network', + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -520,7 +520,7 @@ describe('Indexer rules tests', () => { cliTest( 'Indexer rules set - no args', ['indexer', 'rules', 'set'], - 'references/indexer-rules-no-network', + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, diff --git a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout index 96fb64cb6..b64e990d1 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout @@ -1,6 +1,7 @@ Manage costing for subgraphs - indexer cost set model Update a cost model - indexer cost get Get cost models for one or all subgraphs - indexer cost delete Remove one or many cost models - indexer cost Manage costing for subgraphs + indexer cost set variables Update cost model variables + indexer cost set model Update a cost model + indexer cost get Get cost models for one or all subgraphs + indexer cost delete Remove one or many cost models + indexer cost Manage costing for subgraphs diff --git a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout index 8b6f5671d..f9818f08b 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout @@ -18,6 +18,7 @@ Manage indexer configuration indexer provision Manage indexer's provision indexer disputes get Cross-check POIs submitted in the network indexer disputes Configure allocation POI monitoring + indexer cost set variables Update cost model variables indexer cost set model Update a cost model indexer cost get Get cost models for one or all subgraphs indexer cost delete Remove one or many cost models diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rules-no-network.stderr b/packages/indexer-cli/src/__tests__/references/indexer-rules-no-network.stderr deleted file mode 100644 index 0c57fdb91..000000000 --- a/packages/indexer-cli/src/__tests__/references/indexer-rules-no-network.stderr +++ /dev/null @@ -1 +0,0 @@ -Error: The option '--network' is required From 6e979898add60bb0e2e785a9aa70ca7cf9c6ab42 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 14:47:42 -0800 Subject: [PATCH 21/21] test: update CLI references to remove cost set variables command - Remove 'indexer cost set variables' from indexer-cost.stdout - Remove 'indexer cost set variables' from indexer-help.stdout Part of refactor: remove multi-network support --- .../src/__tests__/references/indexer-cost.stdout | 9 ++++----- .../src/__tests__/references/indexer-help.stdout | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout index b64e990d1..96fb64cb6 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-cost.stdout @@ -1,7 +1,6 @@ Manage costing for subgraphs - indexer cost set variables Update cost model variables - indexer cost set model Update a cost model - indexer cost get Get cost models for one or all subgraphs - indexer cost delete Remove one or many cost models - indexer cost Manage costing for subgraphs + indexer cost set model Update a cost model + indexer cost get Get cost models for one or all subgraphs + indexer cost delete Remove one or many cost models + indexer cost Manage costing for subgraphs diff --git a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout index f9818f08b..8b6f5671d 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-help.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-help.stdout @@ -18,7 +18,6 @@ Manage indexer configuration indexer provision Manage indexer's provision indexer disputes get Cross-check POIs submitted in the network indexer disputes Configure allocation POI monitoring - indexer cost set variables Update cost model variables indexer cost set model Update a cost model indexer cost get Get cost models for one or all subgraphs indexer cost delete Remove one or many cost models