Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ const mockGraphNode = {
proofOfIndexing: jest.fn(),
blockHashFromNumber: jest.fn(),
subgraphFeatures: jest.fn().mockResolvedValue({ network: 'mainnet' }),
indexingStatus: jest.fn().mockResolvedValue([
{
chains: [
{
network: 'mainnet',
latestBlock: { number: '500', hash: '0x' + '00'.repeat(32) },
},
],
},
]),
} as any

const mockNetwork = {
Expand Down Expand Up @@ -141,6 +151,44 @@ describe('DipsManager.collectAgreementPayments', () => {
expect(mockExecuteTransaction).toHaveBeenCalledTimes(1)
})

test("builds the POI reference from the deployment chain's indexed head, not the protocol chain", async () => {
mockQuery.mockResolvedValueOnce({
data: { indexingAgreements: [makeReadyAgreement()] },
})

// Protocol chain (mock provider) sits at block 1000; the deployment indexes
// a chain whose indexed head is 500. The POI must reference the latter.
mockGraphNode.entityCount.mockResolvedValueOnce([500])
mockGraphNode.blockHashFromNumber.mockResolvedValueOnce('0x' + 'ab'.repeat(32))
mockGraphNode.proofOfIndexing.mockResolvedValueOnce('0x' + 'cd'.repeat(32))
mockExecuteTransaction.mockResolvedValueOnce({ hash: '0xtxhash', status: 1 })

const dm = createDipsManager()
await dm.collectAgreementPayments()

expect(mockGraphNode.blockHashFromNumber).toHaveBeenCalledWith('mainnet', 490)
expect(mockGraphNode.proofOfIndexing).toHaveBeenCalledWith(
expect.anything(),
{ number: 490, hash: '0x' + 'ab'.repeat(32) },
expect.anything(),
)
})

test('does not attempt collection when the deployment has no indexed block', async () => {
mockQuery.mockResolvedValueOnce({
data: { indexingAgreements: [makeReadyAgreement()] },
})

mockGraphNode.entityCount.mockResolvedValueOnce([500])
mockGraphNode.indexingStatus.mockResolvedValueOnce([{ chains: [] }])

const dm = createDipsManager()
await dm.collectAgreementPayments()

expect(mockExecuteTransaction).not.toHaveBeenCalled()
expect(logger.warn).toHaveBeenCalled()
})

test('updates tracker after successful collection', async () => {
mockQuery
.mockResolvedValueOnce({ data: { indexingAgreements: [makeReadyAgreement()] } })
Expand Down
30 changes: 25 additions & 5 deletions packages/indexer-common/src/indexing-fees/__tests__/dips.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,11 @@ describe('DipsManager', () => {
// Mock block number and graph node methods for collect
network.networkProvider.getBlockNumber = jest.fn().mockResolvedValue(100)
graphNode.entityCount = jest.fn().mockResolvedValue([250000])
graphNode.subgraphFeatures = jest.fn().mockResolvedValue({ network: 'mainnet' })
graphNode.indexingStatus = jest.fn().mockResolvedValue([
{
chains: [{ network: 'mainnet', latestBlock: { number: '90', hash: '0xh' } }],
},
])
graphNode.blockHashFromNumber = jest.fn().mockResolvedValue('0xblockhash')
graphNode.proofOfIndexing = jest
.fn()
Expand Down Expand Up @@ -940,7 +944,11 @@ describe('DipsManager', () => {

network.networkProvider.getBlockNumber = jest.fn().mockResolvedValue(100)
graphNode.entityCount = jest.fn().mockResolvedValue([250000])
graphNode.subgraphFeatures = jest.fn().mockResolvedValue({ network: 'mainnet' })
graphNode.indexingStatus = jest.fn().mockResolvedValue([
{
chains: [{ network: 'mainnet', latestBlock: { number: '90', hash: '0xh' } }],
},
])
graphNode.blockHashFromNumber = jest.fn().mockResolvedValue('0xblockhash')
graphNode.proofOfIndexing = jest
.fn()
Expand Down Expand Up @@ -968,7 +976,11 @@ describe('DipsManager', () => {
// Mock block number and graph node methods
network.networkProvider.getBlockNumber = jest.fn().mockResolvedValue(100)
graphNode.entityCount = jest.fn().mockResolvedValue([250000])
graphNode.subgraphFeatures = jest.fn().mockResolvedValue({ network: 'mainnet' })
graphNode.indexingStatus = jest.fn().mockResolvedValue([
{
chains: [{ network: 'mainnet', latestBlock: { number: '90', hash: '0xh' } }],
},
])
graphNode.blockHashFromNumber = jest.fn().mockResolvedValue('0xblockhash')
graphNode.proofOfIndexing = jest
.fn()
Expand All @@ -995,7 +1007,11 @@ describe('DipsManager', () => {

network.networkProvider.getBlockNumber = jest.fn().mockResolvedValue(100)
graphNode.entityCount = jest.fn().mockResolvedValue([250000])
graphNode.subgraphFeatures = jest.fn().mockResolvedValue({ network: 'mainnet' })
graphNode.indexingStatus = jest.fn().mockResolvedValue([
{
chains: [{ network: 'mainnet', latestBlock: { number: '90', hash: '0xh' } }],
},
])
graphNode.blockHashFromNumber = jest.fn().mockResolvedValue('0xblockhash')
graphNode.proofOfIndexing = jest
.fn()
Expand All @@ -1021,7 +1037,11 @@ describe('DipsManager', () => {

network.networkProvider.getBlockNumber = jest.fn().mockResolvedValue(100)
graphNode.entityCount = jest.fn().mockResolvedValue([250000])
graphNode.subgraphFeatures = jest.fn().mockResolvedValue({ network: 'mainnet' })
graphNode.indexingStatus = jest.fn().mockResolvedValue([
{
chains: [{ network: 'mainnet', latestBlock: { number: '90', hash: '0xh' } }],
},
])
graphNode.blockHashFromNumber = jest.fn().mockResolvedValue('0xblockhash')
graphNode.proofOfIndexing = jest
.fn()
Expand Down
24 changes: 17 additions & 7 deletions packages/indexer-common/src/indexing-fees/dips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -795,8 +795,7 @@ export class DipsManager {

// Step 1: Best-effort final collection BEFORE cancelling.
try {
const blockNumber = await this.network.networkProvider.getBlockNumber()
await this.tryCollectAgreement(agreement, blockNumber, logger)
await this.tryCollectAgreement(agreement, logger)
logger.info('Final collection succeeded before cancel')
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err)
Expand Down Expand Up @@ -939,7 +938,7 @@ export class DipsManager {

for (const agreement of readyAgreements) {
try {
const result = await this.tryCollectAgreement(agreement, blockNumber, logger)
const result = await this.tryCollectAgreement(agreement, logger)
if (result === 'collected') {
this.collectionTracker.updateAfterCollection(agreement.id, nowSeconds)
this.cleanupFinishedAgreement(agreement, nowSeconds, logger)
Expand Down Expand Up @@ -995,16 +994,27 @@ export class DipsManager {

private async tryCollectAgreement(
agreement: SubgraphIndexingAgreement,
blockNumber: number,
logger: Logger,
): Promise<'collected' | 'paused' | 'unauthorized'> {
const deploymentId = new SubgraphDeploymentID(agreement.subgraphDeploymentId)
const entityCounts = await this.graphNode.entityCount([deploymentId])
const entities = entityCounts[0]

const recentBlock = blockNumber - RECENT_BLOCK_OFFSET
const { network: networkAlias } = await this.graphNode.subgraphFeatures(deploymentId)
const blockHash = await this.graphNode.blockHashFromNumber(networkAlias!, recentBlock)
// The POI block must belong to the chain the deployment indexes, which need
// not be the protocol chain, so the deployment's indexed head is the base.
const [status] = await this.graphNode.indexingStatus([deploymentId])
const chain = status?.chains?.[0]
if (!chain?.network || !chain?.latestBlock) {
throw new Error(
`Deployment ${deploymentId.ipfsHash} has no indexed block to reference a POI against`,
)
}
const networkAlias = chain.network
const recentBlock = Math.max(
Number(chain.latestBlock.number) - RECENT_BLOCK_OFFSET,
0,
)
const blockHash = await this.graphNode.blockHashFromNumber(networkAlias, recentBlock)
const poi = await this.graphNode.proofOfIndexing(
deploymentId,
{ number: recentBlock, hash: blockHash },
Expand Down
Loading