-
Notifications
You must be signed in to change notification settings - Fork 135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
indexer-common,indexer-agent: auto graft resolve with limit #481
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -126,6 +126,7 @@ const setup = async () => { | |
await sequelize.sync({ force: true }) | ||
|
||
const statusEndpoint = 'http://localhost:8030/graphql' | ||
const ipfsEndpoint = 'https://ipfs.network.thegraph.com' | ||
const indexingStatusResolver = new IndexingStatusResolver({ | ||
logger: logger, | ||
statusEndpoint: 'statusEndpoint', | ||
|
@@ -139,6 +140,7 @@ const setup = async () => { | |
}) | ||
|
||
const indexNodeIDs = ['node_1'] | ||
const autoGraftResolverLimit = 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider setting this value to zero to disable the feature in tests, except when testing the feature itself. |
||
indexerManagementClient = await createIndexerManagementClient({ | ||
models, | ||
address: toAddress(address), | ||
|
@@ -157,6 +159,8 @@ const setup = async () => { | |
features: { | ||
injectDai: false, | ||
}, | ||
ipfsEndpoint, | ||
autoGraftResolverLimit, | ||
}) | ||
|
||
indexer = new Indexer( | ||
|
@@ -168,6 +172,8 @@ const setup = async () => { | |
parseGRT('1000'), | ||
address, | ||
AllocationManagementMode.AUTO, | ||
ipfsEndpoint, | ||
autoGraftResolverLimit, | ||
) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -702,7 +702,11 @@ class Agent { | |
// Ensure the deployment is deployed to the indexer | ||
// Note: we're not waiting here, as sometimes indexing a subgraph | ||
// will block if the IPFS files cannot be retrieved | ||
this.indexer.ensure(name, deployment) | ||
try { | ||
this.indexer.ensure(name, deployment) | ||
} catch { | ||
this.indexer.resolveGrafting(name, deployment, 0) | ||
} | ||
Comment on lines
+705
to
+709
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's ensure we accurately identify and handle all potential errors here, considering that resolving grafts may introduce its own errors. |
||
}), | ||
) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -257,6 +257,18 @@ export default { | |
default: 100, | ||
group: 'Query Fees', | ||
}) | ||
.option('ipfs-endpoint', { | ||
description: `Endpoint to an ipfs node to quickly query subgraph manifest data`, | ||
type: 'string', | ||
default: 'https://ipfs.network.thegraph.com', | ||
group: 'Indexer Infrastructure', | ||
}) | ||
.option('auto-graft-resolver-limit', { | ||
description: `Maximum depth of grafting dependency to automatically resolve`, | ||
type: 'number', | ||
default: 0, | ||
group: 'Indexer Infrastructure', | ||
}) | ||
.option('inject-dai', { | ||
description: | ||
'Inject the GRT to DAI/USDC conversion rate into cost model variables', | ||
|
@@ -370,6 +382,12 @@ export default { | |
) { | ||
return 'Invalid --rebate-claim-max-batch-size provided. Must be > 0 and an integer.' | ||
} | ||
if ( | ||
!Number.isInteger(argv['auto-graft-resolver-limit']) || | ||
argv['auto-graft-resolver-limit'] < 0 | ||
) { | ||
return 'Invalid --auto-graft-resolver-limit provided. Must be >= 0 and an integer.' | ||
} | ||
Comment on lines
+385
to
+390
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could rely on yargs' coercion instead of manually implementing this validation. |
||
return true | ||
}) | ||
.option('collect-receipts-endpoint', { | ||
|
@@ -693,6 +711,8 @@ export default { | |
networkMonitor, | ||
allocationManagementMode, | ||
autoAllocationMinBatchSize: argv.autoAllocationMinBatchSize, | ||
ipfsEndpoint: argv.ipfsEndpoint, | ||
autoGraftResolverLimit: argv.autoGraftResolverLimit, | ||
}) | ||
|
||
await createIndexerManagementServer({ | ||
|
@@ -711,6 +731,8 @@ export default { | |
argv.defaultAllocationAmount, | ||
indexerAddress, | ||
allocationManagementMode, | ||
argv.ipfsEndpoint, | ||
argv.autoGraftResolverLimit, | ||
) | ||
|
||
if (networkSubgraphDeploymentId !== undefined) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ import { | |
} from '@graphprotocol/indexer-common' | ||
import { CombinedError } from '@urql/core' | ||
import pMap from 'p-map' | ||
import yaml from 'yaml' | ||
|
||
const POI_DISPUTES_CONVERTERS_FROM_GRAPHQL: Record< | ||
keyof POIDisputeAttributes, | ||
|
@@ -84,6 +85,8 @@ export class Indexer { | |
defaultAllocationAmount: BigNumber | ||
indexerAddress: string | ||
allocationManagementMode: AllocationManagementMode | ||
ipfsEndpoint: string | ||
autoGraftResolverLimit: number | ||
|
||
constructor( | ||
logger: Logger, | ||
|
@@ -94,12 +97,16 @@ export class Indexer { | |
defaultAllocationAmount: BigNumber, | ||
indexerAddress: string, | ||
allocationManagementMode: AllocationManagementMode, | ||
ipfsUrl: string, | ||
autoGraftResolverLimit: number, | ||
) { | ||
this.indexerManagement = indexerManagement | ||
this.statusResolver = statusResolver | ||
this.logger = logger | ||
this.indexerAddress = indexerAddress | ||
this.allocationManagementMode = allocationManagementMode | ||
this.autoGraftResolverLimit = autoGraftResolverLimit | ||
this.ipfsEndpoint = ipfsUrl + '/api/v0/cat?arg=' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer using the URL type when constructing URLs like this. |
||
|
||
if (adminEndpoint.startsWith('https')) { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
|
@@ -735,6 +742,78 @@ export class Indexer { | |
} | ||
} | ||
|
||
// Simple fetch for subgraph manifest | ||
async subgraphManifest(targetDeployment: SubgraphDeploymentID) { | ||
const ipfsFile = await fetch( | ||
this.ipfsEndpoint + targetDeployment.ipfsHash, | ||
{ | ||
method: 'POST', | ||
redirect: 'follow', | ||
}, | ||
) | ||
return yaml.parse(await ipfsFile.text()) | ||
} | ||
|
||
// Recursive function for targetDeployment resolve grafting, add depth until reached to resolverDepth | ||
async resolveGrafting( | ||
name: string, | ||
targetDeployment: SubgraphDeploymentID, | ||
depth: number, | ||
): Promise<void> { | ||
// Matches "/depth-" followed by one or more digits | ||
const depthRegex = /\/depth-\d+/ | ||
const manifest = await this.subgraphManifest(targetDeployment) | ||
|
||
// No grafting dependency | ||
if (!manifest.features || !manifest.features.includes('grafting')) { | ||
// Ensure sync if at root of dependency | ||
if (depth) { | ||
await this.ensure(name, targetDeployment) | ||
} | ||
return | ||
} | ||
|
||
// Default autoGraftResolverLimit is 0, essentially disabling auto-resolve | ||
if (depth >= this.autoGraftResolverLimit) { | ||
throw indexerError( | ||
IndexerErrorCode.IE074, | ||
`Grafting depth reached limit for auto resolve`, | ||
) | ||
} | ||
|
||
try { | ||
const baseDeployment = new SubgraphDeploymentID(manifest.graft.base) | ||
let baseName = name.replace(depthRegex, `/depth-${depth}`) | ||
if (baseName === name) { | ||
// add depth suffix if didn't have one from targetDeployment | ||
baseName += `/depth-${depth}` | ||
} | ||
await this.resolveGrafting(baseName, baseDeployment, depth + 1) | ||
|
||
// If base deployment has synced upto the graft block, then ensure the target deployment | ||
// Otherwise just log to come back later | ||
const graftStatus = await this.statusResolver.indexingStatus([ | ||
baseDeployment, | ||
]) | ||
// If base deployment synced to required block, try to sync the target and | ||
// turn off syncing for the base deployment | ||
if ( | ||
graftStatus[0].chains[0].latestBlock && | ||
graftStatus[0].chains[0].latestBlock.number >= manifest.graft.block | ||
) { | ||
await this.ensure(name, targetDeployment) | ||
} else { | ||
this.logger.debug( | ||
`Graft base deployment has yet to reach the graft block, try again later`, | ||
) | ||
} | ||
} catch { | ||
throw indexerError( | ||
IndexerErrorCode.IE074, | ||
`Base deployment hasn't synced to the graft block, try again later`, | ||
) | ||
} | ||
} | ||
async deploy( | ||
name: string, | ||
deployment: SubgraphDeploymentID, | ||
|
@@ -751,7 +830,7 @@ export class Indexer { | |
node_id: node_id, | ||
}) | ||
if (response.error) { | ||
throw response.error | ||
throw indexerError(IndexerErrorCode.IE026, response.error) | ||
} | ||
this.logger.info(`Successfully deployed subgraph deployment`, { | ||
name, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -457,6 +457,8 @@ export interface IndexerManagementClientOptions { | |
networkMonitor?: NetworkMonitor | ||
allocationManagementMode?: AllocationManagementMode | ||
autoAllocationMinBatchSize?: number | ||
ipfsEndpoint?: string | ||
autoGraftResolverLimit?: number | ||
} | ||
|
||
export class IndexerManagementClient extends Client { | ||
|
@@ -522,6 +524,8 @@ export const createIndexerManagementClient = async ( | |
networkMonitor, | ||
allocationManagementMode, | ||
autoAllocationMinBatchSize, | ||
ipfsEndpoint, | ||
autoGraftResolverLimit, | ||
} = options | ||
const schema = buildSchema(print(SCHEMA_SDL)) | ||
const resolvers = { | ||
|
@@ -535,7 +539,13 @@ export const createIndexerManagementClient = async ( | |
|
||
const dai: WritableEventual<string> = mutable() | ||
|
||
const subgraphManager = new SubgraphManager(deploymentManagementEndpoint, indexNodeIDs) | ||
const subgraphManager = new SubgraphManager( | ||
deploymentManagementEndpoint, | ||
indexNodeIDs, | ||
indexingStatusResolver, | ||
ipfsEndpoint, | ||
autoGraftResolverLimit, | ||
) | ||
Comment on lines
-538
to
+548
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest injecting the SubgraphManager dependency instead of delegating its creation to the IndexerManagementClient. This decoupling would make it easier to use mocks on tests. |
||
let allocationManager: AllocationManager | undefined = undefined | ||
let actionManager: ActionManager | undefined = undefined | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -483,6 +483,14 @@ export default { | |
subgraphDeploymentID.ipfsHash, | ||
) | ||
|
||
// Ensure grafting dependencies are resolved | ||
await subgraphManager.resolveGrafting( | ||
logger, | ||
models, | ||
subgraphDeploymentID, | ||
indexNode, | ||
0, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we set grafting depth to zero here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC the depth is set to 0 as the initialization to the recursive |
||
) | ||
// Ensure subgraph is deployed before allocating | ||
await subgraphManager.ensure( | ||
logger, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be configurable with the current value as the default.