Skip to content

Commit

Permalink
chore: more work on dips, still wip
Browse files Browse the repository at this point in the history
  • Loading branch information
pcarranzav committed Feb 7, 2025
1 parent 842d2ff commit 355932e
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/indexer-agent/src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ export async function createNetworkSpecification(
enableDips: argv.enableDips,
dipperEndpoint: argv.dipperEndpoint,
dipsAllocationAmount: argv.dipsAllocationAmount,
dipsEpochsMargin: argv.dipsEpochsMargin,
}

const transactionMonitoring = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
AllocationStatus,
CloseAllocationResult,
CreateAllocationResult,
DipsManager,
fetchIndexingRules,
GraphNode,
indexerError,
Expand Down Expand Up @@ -98,12 +99,15 @@ export type TransactionResult =
| ActionFailure[]

export class AllocationManager {
private dipsManager: DipsManager
constructor(
private logger: Logger,
private models: IndexerManagementModels,
private graphNode: GraphNode,
private network: Network,
) {}
) {
this.dipsManager = new DipsManager(this.logger, this.models, this.graphNode, this.network, this)
}

async executeBatch(actions: Action[]): Promise<AllocationResult[]> {
const logger = this.logger.child({ function: 'executeBatch' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IndexingRuleModels, defineIndexingRuleModels } from './indexing-rule'
import { CostModelModels, defineCostModelModels } from './cost-model'
import { POIDisputeModels, definePOIDisputeModels } from './poi-dispute'
import { ActionModels, defineActionModels } from './action'
import { defineIndexingFeesModels, IndexingFeesModels } from './indexing-agreement'

export * from './cost-model'
export * from './indexing-rule'
Expand All @@ -13,7 +14,8 @@ export * from './action'
export type IndexerManagementModels = IndexingRuleModels &
CostModelModels &
POIDisputeModels &
ActionModels
ActionModels &
IndexingFeesModels

export const defineIndexerManagementModels = (
sequelize: Sequelize,
Expand All @@ -24,4 +26,5 @@ export const defineIndexerManagementModels = (
defineIndexingRuleModels(sequelize),
definePOIDisputeModels(sequelize),
defineActionModels(sequelize),
defineIndexingFeesModels(sequelize),
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,25 @@ export class IndexingAgreement extends Model<
declare signed_payload: Buffer;
declare protocol_network: string;
declare chain_id: string;
declare price_per_block: string;
declare base_price_per_epoch: string;
declare price_per_entity: string;
declare subgraph_deployment_id: string;
declare service: string;
declare payee: string;
declare payer: string;
declare deadline: Date;
declare duration_epochs: bigint;
declare max_initial_amount: string;
declare max_ongoing_amount_per_epoch: string;
declare min_epochs_per_collection: bigint;
declare max_epochs_per_collection: bigint;
declare created_at: Date;
declare updated_at: Date;
declare cancelled_at: Date | null;
declare signed_cancellation_payload: Buffer | null;
declare current_allocation_id: string | null;
declare last_allocation_id: string | null;
declare last_payment_collected_at: Date | null;
}

export interface IndexingFeesModels {
Expand All @@ -37,11 +44,12 @@ export const defineIndexingFeesModels = (sequelize: Sequelize): IndexingFeesMode
primaryKey: true,
},
signature: {
type: DataTypes.BLOB, // == BYTEA in postgres
type: DataTypes.BLOB,
allowNull: false,
unique: true,
},
signed_payload: {
type: DataTypes.BLOB, // == BYTEA in postgres
type: DataTypes.BLOB,
allowNull: false,
},
protocol_network: {
Expand All @@ -52,7 +60,7 @@ export const defineIndexingFeesModels = (sequelize: Sequelize): IndexingFeesMode
type: DataTypes.STRING(255),
allowNull: false,
},
price_per_block: {
base_price_per_epoch: {
type: DataTypes.DECIMAL(39),
allowNull: false,
},
Expand All @@ -76,6 +84,30 @@ export const defineIndexingFeesModels = (sequelize: Sequelize): IndexingFeesMode
type: DataTypes.CHAR(40),
allowNull: false,
},
deadline: {
type: DataTypes.DATE,
allowNull: false,
},
duration_epochs: {
type: DataTypes.BIGINT,
allowNull: false,
},
max_initial_amount: {
type: DataTypes.DECIMAL(39),
allowNull: false,
},
max_ongoing_amount_per_epoch: {
type: DataTypes.DECIMAL(39),
allowNull: false,
},
min_epochs_per_collection: {
type: DataTypes.BIGINT,
allowNull: false,
},
max_epochs_per_collection: {
type: DataTypes.BIGINT,
allowNull: false,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
Expand All @@ -100,6 +132,10 @@ export const defineIndexingFeesModels = (sequelize: Sequelize): IndexingFeesMode
type: DataTypes.CHAR(40),
allowNull: true,
},
last_payment_collected_at: {
type: DataTypes.DATE,
allowNull: true,
},
},
{
modelName: 'IndexingAgreement',
Expand Down
110 changes: 110 additions & 0 deletions packages/indexer-common/src/indexing-fees/dips.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { formatGRT, Logger, SubgraphDeploymentID } from "@graphprotocol/common-ts";
import { AllocationManager, GraphNode, IndexerManagementModels, IndexingDecisionBasis, IndexingRuleAttributes, Network, SubgraphIdentifierType, upsertIndexingRule } from '@graphprotocol/indexer-common'
import { Op } from "sequelize";

export class DipsManager {
constructor(
private logger: Logger,
private models: IndexerManagementModels,
private graphNode: GraphNode,
private network: Network,
private parent: AllocationManager | null,
) {}
// Cancel an agreement associated to an allocation if it exists
async tryCancelAgreement(allocationId: string) {
const agreement = await this.models.IndexingAgreement.findOne({
where: {
current_allocation_id: allocationId,
cancelled_at: null,
},
})
if (agreement) {
// TODO use dips-proto to cancel agreement via grpc

// Mark the agreement as cancelled
}
}
// Update the current and last allocation ids for an agreement if it exists
async tryUpdateAgreementAllocation(oldAllocationId: string, newAllocationId: string | null) {
const agreement = await this.models.IndexingAgreement.findOne({
where: {
current_allocation_id: oldAllocationId,
cancelled_at: null,
},
})
if (agreement) {
agreement.current_allocation_id = newAllocationId
agreement.last_allocation_id = oldAllocationId
agreement.last_payment_collected_at = null
await agreement.save()
}
}
// Collect payments for all outstanding agreements
async collectAllPayments() {
const outstandingAgreements = await this.models.IndexingAgreement.findAll({
where: {
last_payment_collected_at: null,
last_allocation_id: {
[Op.ne]: null,
},
},
})
for (const agreement of outstandingAgreements) {
if (agreement.last_allocation_id) {
await this.tryCollectPayment(agreement.last_allocation_id)
} else {
// This should never happen as we check for this in the query
this.logger.error(`Agreement ${agreement.id} has no last allocation id`)
}
}
}
async tryCollectPayment(lastAllocationId: string) {
// TODO: use dips-proto to collect payment via grpc

// TODO: store the receipt in the database
// (tap-agent will take care of aggregating it into a RAV)

// Mark the agreement as having had a payment collected
await this.models.IndexingAgreement.update({
last_payment_collected_at: new Date(),
}, {
where: {
last_allocation_id: lastAllocationId,
},
})
}
async ensureAgreementRules() {
if (!this.parent) {
this.logger.error('DipsManager has no parent AllocationManager, cannot ensure agreement rules')
return
}
// Get all the indexing agreements that are not cancelled
const indexingAgreements = await this.models.IndexingAgreement.findAll({
where: {
cancelled_at: null,
},
})
// For each agreement, check that there is an indexing rule to always
// allocate to the agreement's subgraphDeploymentId, and if not, create one
for (const agreement of indexingAgreements) {
const subgraphDeploymentID = new SubgraphDeploymentID(agreement.subgraph_deployment_id)
// If there is not yet an indexingRule that deems this deployment worth allocating to, make one
if (!(await this.parent.matchingRuleExists(this.logger, subgraphDeploymentID))) {
this.logger.debug(
`Creating indexing rule for agreement ${agreement.id}`,
)
const indexingRule = {
identifier: agreement.subgraph_deployment_id,
allocationAmount: formatGRT(this.network.specification.indexerOptions.dipsAllocationAmount),
identifierType: SubgraphIdentifierType.DEPLOYMENT,
decisionBasis: IndexingDecisionBasis.ALWAYS,
protocolNetwork: this.network.specification.networkIdentifier,
autoRenewal: true,
allocationLifetime: Math.max(Number(agreement.min_epochs_per_collection), Number(agreement.max_epochs_per_collection) - this.network.specification.indexerOptions.dipsEpochsMargin),
} as Partial<IndexingRuleAttributes>

await upsertIndexingRule(this.logger, this.models, indexingRule)
}
}
}
}
2 changes: 1 addition & 1 deletion packages/indexer-common/src/indexing-fees/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './models'
export * from './dips'
1 change: 1 addition & 0 deletions packages/indexer-common/src/network-specification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const IndexerOptions = z
enableDips: z.boolean().default(false),
dipperEndpoint: z.string().url().optional(),
dipsAllocationAmount: GRT().default(1),
dipsEpochsMargin: positiveNumber().default(1),
})
.strict()
export type IndexerOptions = z.infer<typeof IndexerOptions>
Expand Down

0 comments on commit 355932e

Please sign in to comment.