diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index 931fd4811..a3d53c24e 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -10,11 +10,11 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v2 - - name: Set up Node.js v20 - uses: actions/setup-node@v2.1.5 + uses: actions/checkout@v4 + - 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 a7827622d..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 }} @@ -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/ 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 diff --git a/.github/workflows/indexer-agent-image.yml b/.github/workflows/indexer-agent-image.yml index aee116c5a..03dc9add9 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 + - 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@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..979609d59 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 + - 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@v2 + uses: docker/build-push-action@v6 with: context: . file: Dockerfile.indexer-cli 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..8f8621d32 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,38 @@ 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 +1134,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 +1170,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 +1231,57 @@ 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,17 @@ 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..2e5186c36 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,9 @@ 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 +1139,7 @@ export default { force: boolean protocolNetwork: string }, - { logger, models, multiNetworks, graphNode }: IndexerManagementResolverContext, + { logger, models, multiNetworks }: IndexerManagementResolverContext, ): Promise => { logger = logger.child({ component: 'reallocateAllocationResolver', @@ -1750,57 +1197,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