From e037443d57aa591c4b0d3b14586c7ab46634c71b Mon Sep 17 00:00:00 2001 From: hopeyen Date: Thu, 7 Dec 2023 12:50:32 -0600 Subject: [PATCH] feat: rav metrics --- .../src/allocations/query-fees.ts | 167 +++++++++++++++--- 1 file changed, 139 insertions(+), 28 deletions(-) diff --git a/packages/indexer-common/src/allocations/query-fees.ts b/packages/indexer-common/src/allocations/query-fees.ts index d8c1ebeda..4652a0569 100644 --- a/packages/indexer-common/src/allocations/query-fees.ts +++ b/packages/indexer-common/src/allocations/query-fees.ts @@ -60,6 +60,11 @@ interface ReceiptMetrics { vouchersRedeemDuration: Histogram vouchersBatchRedeemSize: Gauge voucherCollectedFees: Gauge + successRavRedeems: Counter + invalidRavRedeems: Counter + failedRavRedeems: Counter + ravsRedeemDuration: Histogram + ravCollectedFees: Gauge } export interface AllocationPartialVouchers { @@ -147,7 +152,9 @@ export class AllocationReceiptCollector implements ReceiptCollector { // flag during startup. collector.startReceiptCollecting() collector.startVoucherProcessing() - collector.startRAVProcessing() + if (collector.escrow) { + collector.startRAVProcessing() + } await collector.queuePendingReceiptsFromDatabase() return collector } @@ -735,16 +742,23 @@ export class AllocationReceiptCollector implements ReceiptCollector { private async submitRAVs(ravs: SignedRav[]): Promise { const logger = this.logger.child({ - function: 'submitVouchers()', + function: 'submitRAVs()', voucherBatchSize: ravs.length, }) + if (this.escrow == null) { + logger.error( + `No escrow contracts, but this shouldn't happen as RAV process is only triggered when escrow is not null`, + { + ravs, + }, + ) + return + } + const escrow = this.escrow logger.info(`Redeem query voucher batch on chain`, { ravs, }) - const stopTimer = this.metrics.vouchersRedeemDuration.startTimer({ - allocation: ravs[0].message.allocationId, - }) const hexPrefix = (bytes: string): string => bytes.startsWith('0x') ? bytes : `0x${bytes}` @@ -757,33 +771,95 @@ export class AllocationReceiptCollector implements ReceiptCollector { // Redeem RAV one-by-one as no plual version available for (const rav of onchainRAVs) { - const allocationId = rav.message.allocationId - // Look up allocation - const allocation = (await this.allocations.value()).find( - (a) => a.id == allocationId, - ) - // Fail query outright if we have no signer for this allocation - if (allocation === undefined) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const error = Error(`Unable to match allocation`) as any - error.status = 500 - throw error + const stopTimer = this.metrics.ravsRedeemDuration.startTimer({ + allocation: rav.message.allocationId, + }) + try { + // Look up allocation + const allocation = (await this.allocations.value()).find( + (a) => a.id == rav.message.allocationId, + ) + // Fail query outright if we have no signer for this allocation + if (allocation === undefined) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const error = Error(`Unable to match allocation`) as any + error.status = 500 + throw error + } + // compute allocation id proof + const proof = await allocationIdProof( + allocationSigner(this.transactionManager.wallet, allocation), + this.transactionManager.wallet.address, + rav.message.allocationId, + ) + this.logger.debug(`Computed allocationIdProof`, { + allocationId: rav.message.allocationId, + proof, + }) + // Submit the signed RAV on chain + const txReceipt = await escrow.executeTransaction( + () => escrow.estimateGas.redeem(rav, proof), + async (gasLimit: BigNumberish) => + escrow.redeem(rav, proof, { + gasLimit, + }), + logger.child({ action: 'redeem' }), + ) + + // get tx receipt and post process + if (txReceipt === 'paused' || txReceipt === 'unauthorized') { + this.metrics.invalidRavRedeems.inc({ allocation: rav.message.allocationId }) + return + } + this.metrics.ravCollectedFees.set( + { allocation: rav.message.allocationId }, + parseFloat(rav.message.valueAggregate.toString()), + ) + } catch (err) { + this.metrics.failedRavRedeems.inc({ allocation: rav.message.allocationId }) + logger.error(`Failed to redeem RAV`, { + err: indexerError(IndexerErrorCode.IE055, err), + }) + return } - const signer = allocationSigner(this.transactionManager.wallet, allocation) - // compute allocation id proof - const proof = await allocationIdProof( - signer, - this.transactionManager.wallet.address, - allocationId, + stopTimer() + } + + // Postprocess obsolete RAVs from the database + logger.info(`Successfully redeemed RAV, delete local copy`) + try { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await this.models.allocationSummaries.sequelize!.transaction( + async (transaction) => { + for (const rav of ravs) { + const [summary] = await ensureAllocationSummary( + this.models, + toAddress(rav.message.allocationId), + transaction, + this.protocolNetwork, + ) + summary.withdrawnFees = BigNumber.from(summary.withdrawnFees) + .add(rav.message.valueAggregate) + .toString() + await summary.save({ transaction }) + } + }, ) - this.logger.debug(`Got allocationIdProof`, { - proof, - }) - // submit escrow redeem with signed rav and proof - // get tx receipt and post process + await this.models.receiptAggregateVouchers.destroy({ + where: { + allocationId: ravs.map((rav) => rav.message.allocationId), + }, + }) + ravs.map((rav) => + this.metrics.successRavRedeems.inc({ allocation: rav.message.allocationId }), + ) + logger.info(`Successfully deleted local RAV copy`) + } catch (err) { + logger.warn(`Failed to delete local RAV copy, will try again later`, { + err, + }) } - stopTimer() } public async queuePendingReceiptsFromDatabase(): Promise { @@ -931,6 +1007,41 @@ const registerReceiptMetrics = (metrics: Metrics, networkIdentifier: string) => registers: [metrics.registry], labelNames: ['allocation'], }), + + successRavRedeems: new metrics.client.Counter({ + name: `indexer_agent_rav_exchanges_ok_${networkIdentifier}`, + help: 'Successfully redeemed ravs', + registers: [metrics.registry], + labelNames: ['allocation'], + }), + + invalidRavRedeems: new metrics.client.Counter({ + name: `indexer_agent_rav_exchanges_invalid_${networkIdentifier}`, + help: 'Invalid ravs redeems - tx paused or unauthorized', + registers: [metrics.registry], + labelNames: ['allocation'], + }), + + failedRavRedeems: new metrics.client.Counter({ + name: `indexer_agent_rav_redeems_failed_${networkIdentifier}`, + help: 'Failed redeems for ravs', + registers: [metrics.registry], + labelNames: ['allocation'], + }), + + ravsRedeemDuration: new metrics.client.Histogram({ + name: `indexer_agent_ravs_redeem_duration_${networkIdentifier}`, + help: 'Duration of redeeming ravs', + registers: [metrics.registry], + labelNames: ['allocation'], + }), + + ravCollectedFees: new metrics.client.Gauge({ + name: `indexer_agent_rav_collected_fees_${networkIdentifier}`, + help: 'Amount of query fees collected for a rav', + registers: [metrics.registry], + labelNames: ['allocation'], + }), }) interface GatewayRoutes {