From 1dc1a9399854961c3524605c8f3751c200b6b031 Mon Sep 17 00:00:00 2001 From: Daniel Werner Date: Thu, 11 Dec 2025 09:59:04 -0800 Subject: [PATCH 01/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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