From 5c0ec499d6b219fb20ec63784af71f5319f73a75 Mon Sep 17 00:00:00 2001 From: Kristine Robison Date: Fri, 7 Dec 2018 11:02:10 -0800 Subject: [PATCH] feat(Health): roll transactionAnchorRetryInfo into health collection (#878) add a health check on the number of times it takes to get a transaction into the blockchain References #878, #832 --- src/Health/Health.ts | 9 +++++++ src/Health/HealthController.ts | 32 ++++++++++++++++++++++++ src/Health/HealthDAO.ts | 15 ++++++++++++ src/Health/HealthService.ts | 3 ++- src/Health/IPFSDirectoryHashDAO.ts | 39 ++++++++++++++++++++++++++++-- src/Health/Router.ts | 1 + 6 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/Health/Health.ts b/src/Health/Health.ts index 1b16bef2..d1958e12 100644 --- a/src/Health/Health.ts +++ b/src/Health/Health.ts @@ -12,6 +12,7 @@ import { HealthController, HealthControllerConfiguration } from './HealthControl import { HealthDAO } from './HealthDAO' import { HealthService, HealthServiceConfiguration } from './HealthService' import { IPFS, IPFSConfiguration } from './IPFS' +import { IPFSDirectoryHashDAO } from './IPFSDirectoryHashDAO' import { Router } from './Router' export interface HealthConfiguration @@ -60,6 +61,13 @@ export class Health { }, }) + const ipfsDirectoryHasDAO = new IPFSDirectoryHashDAO({ + dependencies: { + ipfsDirectoryHashInfoCollection: this.ipfsDirectoryHashInfoCollection, + }, + }) + await ipfsDirectoryHasDAO.start() + const bitcoinCore = new BitcoinCore({ host: this.configuration.bitcoinUrl, port: this.configuration.bitcoinPort, @@ -78,6 +86,7 @@ export class Health { dependencies: { logger: this.logger, healthDAO, + ipfsDirectoryHasDAO, bitcoinCore, ipfs, }, diff --git a/src/Health/HealthController.ts b/src/Health/HealthController.ts index d5116dd5..0f6a92aa 100644 --- a/src/Health/HealthController.ts +++ b/src/Health/HealthController.ts @@ -6,6 +6,7 @@ import { childWithFileName } from 'Helpers/Logging' import { IPFSHashFailure, ClaimIPFSHashPair } from 'Interfaces' import { BlockchainInfo, WalletInfo, NetworkInfo, IPFSInfo, EstimatedSmartFeeInfo, HealthDAO } from './HealthDAO' +import { TransactionAnchorRetryInfo, IPFSDirectoryHashDAO } from './IPFSDirectoryHashDAO' import { IPFS } from './IPFS' @@ -15,6 +16,10 @@ enum LogTypes { error = 'error', } +export interface HealthError { + readonly error: string +} + export interface HealthControllerConfiguration { readonly lowWalletBalanceInBitcoin: number readonly feeEstimateMinTargetBlock: number @@ -40,6 +45,7 @@ export const isFailureHard = (failureType: string) => failureType === 'HARD' export interface Dependencies { readonly healthDAO: HealthDAO + readonly ipfsDirectoryHasDAO: IPFSDirectoryHashDAO readonly bitcoinCore: BitcoinCore readonly logger: Pino.Logger readonly ipfs: IPFS @@ -53,6 +59,7 @@ export interface Arguments { export class HealthController { private readonly configuration: HealthControllerConfiguration private readonly healthDAO: HealthDAO + private readonly ipfsDirectoryHashDAO: IPFSDirectoryHashDAO private readonly bitcoinCore: BitcoinCore private readonly logger: Pino.Logger private readonly ipfs: IPFS @@ -61,6 +68,7 @@ export class HealthController { dependencies: { logger, healthDAO, + ipfsDirectoryHasDAO, bitcoinCore, ipfs, }, @@ -119,6 +127,14 @@ export class HealthController { return estimatedSmartFeeInfo } + private async getTransactionAnchorRetryInfo(): Promise { + try { + return await this.ipfsDirectoryHashDAO.getTransactionAnchorRetryInfo() + } catch (e) { + return { error: 'Error retrieving transactionAnchorRetryReport' } + } + } + private async checkIPFSConnection(): Promise { try { const ipfsConnection = await this.ipfs.getVersion() @@ -134,6 +150,13 @@ export class HealthController { return ipfsInfo } + private async updateTransactionAnchorRetryInfo( + transactionAnchorRetryInfo: TransactionAnchorRetryInfo, + ): Promise { + await this.healthDAO.updateTransactionAnchorRetryInfo(transactionAnchorRetryInfo) + return transactionAnchorRetryInfo + } + public async updateIPFSFailures(ipfsHashFailures: ReadonlyArray) { this.logger.debug({ ipfsHashFailures }, 'Updating IPFS Failure Count by failureType') await ipfsHashFailures.map( @@ -185,4 +208,13 @@ export class HealthController { this.updateIPFSInfo, this.log(LogTypes.trace)('refreshed IPFS info'), ) + + public refreshTransactionAnchorRetryInfo = pipeP( + this.log(LogTypes.trace)('refreshing transaction anchor retry info'), + this.getTransactionAnchorRetryInfo, + this.log(LogTypes.trace)('new info gathered, saving transaction anchor retry info'), + this.updateTransactionAnchorRetryInfo, + this.log(LogTypes.trace)('refreshed transaction anchor retry info'), + ) + } diff --git a/src/Health/HealthDAO.ts b/src/Health/HealthDAO.ts index 774872bc..e66c95f3 100644 --- a/src/Health/HealthDAO.ts +++ b/src/Health/HealthDAO.ts @@ -1,4 +1,5 @@ import { Collection, Db } from 'mongodb' +import { TransactionAnchorRetryInfo } from './IPFSDirectoryHashDAO' export interface BlockchainInfo { readonly blocks: number @@ -42,6 +43,8 @@ type updateIPFSInfo = (x: IPFSInfo) => Promise type updateEstimatedSmartFeeInfo = (x: EstimatedSmartFeeInfo) => Promise +type updateTransactionAnchorRetryInfo = (x: TransactionAnchorRetryInfo) => Promise + export interface Dependencies { readonly db: Db } @@ -121,6 +124,18 @@ export class HealthDAO { ) } + readonly updateTransactionAnchorRetryInfo: updateTransactionAnchorRetryInfo = async transactionAnchorRetryInfo => { + await this.collection.updateOne( + { name: 'transactionAnchorRetryInfo' }, + { + $set: { + transactionAnchorRetryInfo, + }, + }, + { upsert: true }, + ) + } + readonly increaseHardIPFSFailure = async (): Promise => { await this.collection.updateOne( { name: 'ipfsDownloadRetries' }, diff --git a/src/Health/HealthService.ts b/src/Health/HealthService.ts index 8ffaf65c..f6b49d68 100644 --- a/src/Health/HealthService.ts +++ b/src/Health/HealthService.ts @@ -2,6 +2,7 @@ import { Interval } from '@po.et/poet-js' import * as Pino from 'pino' import { childWithFileName } from 'Helpers/Logging' +import { secondsToMiliseconds } from 'Helpers/Time' import { Messaging } from 'Messaging/Messaging' import { ExchangeConfiguration } from './ExchangeConfiguration' @@ -39,7 +40,7 @@ export class HealthService { this.logger = childWithFileName(logger, __filename) this.configuration = configuration this.messaging = messaging - this.interval = new Interval(this.getHealth, this.configuration.healthIntervalInSeconds * 1000) + this.interval = new Interval(this.getHealth, secondsToMiliseconds(this.configuration.healthIntervalInSeconds)) this.exchange = exchange } diff --git a/src/Health/IPFSDirectoryHashDAO.ts b/src/Health/IPFSDirectoryHashDAO.ts index 94db6d44..c6b4d489 100644 --- a/src/Health/IPFSDirectoryHashDAO.ts +++ b/src/Health/IPFSDirectoryHashDAO.ts @@ -1,4 +1,5 @@ import { Collection } from 'mongodb' +import { isNil } from 'ramda' export interface UpdateAnchorAttemptInfo { readonly ipfsDirectoryHash: string @@ -7,6 +8,19 @@ export interface UpdateAnchorAttemptInfo { type updateAnchorAttemptsInfo = (x: UpdateAnchorAttemptInfo) => Promise +export interface AnchorRetryDAOResult { + readonly _id: number + readonly count: number +} + +export interface TransactionAnchorRetryEntry { + readonly attempts: number + readonly count: number +} + +export type TransactionAnchorRetryInfo = ReadonlyArray +export type getTransactionAnchorRetryInfo = () => Promise + export interface Dependencies { readonly ipfsDirectoryHashInfoCollection: Collection } @@ -26,6 +40,11 @@ export class IPFSDirectoryHashDAO { this.ipfsDirectoryHashInfoCollection = ipfsDirectoryHashInfoCollection } + readonly start = async (): Promise => { + await this.ipfsDirectoryHashInfoCollection.createIndex({ ipofsDirectoryHash: 1 }, { unique: true }) + await this.ipfsDirectoryHashInfoCollection.createIndex({ txId: 1, attempts: 1 }) + } + readonly updateAnchorAttemptsInfo: updateAnchorAttemptsInfo = async anchorAttemptInfo => { await this.ipfsDirectoryHashInfoCollection.updateOne( { @@ -35,9 +54,25 @@ export class IPFSDirectoryHashDAO { { $set: { txId: anchorAttemptInfo.txId }, $inc: { attempts: 1 }, - $setOnInsert: { attempts: 1 }, }, { upsert: true }, ) } -} + + readonly getTransactionAnchorRetryInfo: getTransactionAnchorRetryInfo = async () => { + const cursorArray = await this.ipfsDirectoryHashInfoCollection.aggregate([ + { $match: { attempts: { $ne: 1 } } }, + { $group: { _id: '$attempts', count: { $sum: 1} } }, + ]).toArray() + + if (isNil(cursorArray)) return [] + + return cursorArray.map( + (item: AnchorRetryDAOResult) => ( + { + attempts: item._id, + count: item.count, + } + ), + ) + }} diff --git a/src/Health/Router.ts b/src/Health/Router.ts index 9bc000a3..d8f18b34 100644 --- a/src/Health/Router.ts +++ b/src/Health/Router.ts @@ -51,6 +51,7 @@ export class Router { await this.controller.refreshNetworkInfo() await this.controller.refreshIPFSInfo() await this.controller.refreshEstimatedSmartFee() + await this.controller.refreshTransactionAnchorRetryInfo() } catch (error) { logger.error({ error }, 'Failed to getHealthInfo') }