From 099e52a71d9ba8f777c4a111dd99f814829d995e Mon Sep 17 00:00:00 2001 From: Henry Tsai <17891086+thehenrytsai@users.noreply.github.com> Date: Fri, 18 Dec 2020 16:46:48 -0800 Subject: [PATCH] feat(ref-imp): #989 - added event emitter support --- lib/bitcoin/BitcoinClient.ts | 2 +- lib/common/EventEmitter.ts | 31 ++++++++++++++++++++++++++ lib/common/Logger.ts | 1 + lib/common/interfaces/IEventEmitter.ts | 9 ++++++++ lib/common/interfaces/ILogger.ts | 2 +- lib/core/BatchScheduler.ts | 4 ++++ lib/core/Core.ts | 6 +++-- lib/core/EventCode.ts | 7 ++++++ lib/core/Observer.ts | 6 ++++- lib/index.ts | 2 ++ package.json | 3 ++- tests/core/Core.spec.ts | 20 ++++++++++++----- 12 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 lib/common/EventEmitter.ts create mode 100644 lib/common/interfaces/IEventEmitter.ts create mode 100644 lib/core/EventCode.ts diff --git a/lib/bitcoin/BitcoinClient.ts b/lib/bitcoin/BitcoinClient.ts index 0d67a8995..34797ab83 100644 --- a/lib/bitcoin/BitcoinClient.ts +++ b/lib/bitcoin/BitcoinClient.ts @@ -718,6 +718,7 @@ export default class BitcoinClient { private static createBitcoinOutputModel (bitcoreOutput: Transaction.Output): BitcoinOutputModel { return { satoshis: bitcoreOutput.satoshis, + // Some transaction outputs do not have a script, such as coinbase transactions. scriptAsmAsString: bitcoreOutput.script ? bitcoreOutput.script.toASM() : '' }; } @@ -742,7 +743,6 @@ export default class BitcoinClient { private async getUnspentOutputs (address: Address): Promise { - // Retrieve all transactions by addressToSearch via BCoin Node API /tx/address/$address endpoint const addressToSearch = address.toString(); Logger.info(`Getting unspent coins for ${addressToSearch}`); const request = { diff --git a/lib/common/EventEmitter.ts b/lib/common/EventEmitter.ts new file mode 100644 index 000000000..b0c2c4ad4 --- /dev/null +++ b/lib/common/EventEmitter.ts @@ -0,0 +1,31 @@ +import IEventEmitter from './interfaces/IEventEmitter'; +import LogColor from './LogColor'; + +/** + * Event emitter used in Sidetree. + * Intended to be machine readable for triggering custom handlers. + */ +export default class EventEmitter { + // Default to basic console log. + private static singleton: IEventEmitter = { + emit: async (eventCode) => { + console.log(LogColor.lightBlue(`Event emitted: ${LogColor.green(eventCode)}`)); + } + }; + + /** + * Overrides the default event emitter if given. + */ + static initialize (customEventEmitter?: IEventEmitter) { + if (customEventEmitter !== undefined) { + EventEmitter.singleton = customEventEmitter; + } + } + + /** + * Emits an event. + */ + public static async emit (eventName: string, eventData?: {[property: string]: any}): Promise { + await EventEmitter.singleton.emit(eventName, eventData); + } +} diff --git a/lib/common/Logger.ts b/lib/common/Logger.ts index e6976e25f..c9456c29f 100644 --- a/lib/common/Logger.ts +++ b/lib/common/Logger.ts @@ -3,6 +3,7 @@ import ILogger from './interfaces/ILogger'; /** * Logger used in Sidetree. + * Intended to be human readable for debugging. */ export default class Logger { private static singleton: ILogger = new ConsoleLogger(); diff --git a/lib/common/interfaces/IEventEmitter.ts b/lib/common/interfaces/IEventEmitter.ts new file mode 100644 index 000000000..40a6466f2 --- /dev/null +++ b/lib/common/interfaces/IEventEmitter.ts @@ -0,0 +1,9 @@ +/** + * Custom event emitter interface. + */ +export default interface IEventEmitter { + /** + * Emits an event. + */ + emit (eventName: string, eventData?: {[property: string]: any}): Promise; +} diff --git a/lib/common/interfaces/ILogger.ts b/lib/common/interfaces/ILogger.ts index 7c20761c9..5c5351161 100644 --- a/lib/common/interfaces/ILogger.ts +++ b/lib/common/interfaces/ILogger.ts @@ -1,5 +1,5 @@ /** - * Logging interface used in Sidetree. + * Custom logger interface. */ export default interface ILogger { /** diff --git a/lib/core/BatchScheduler.ts b/lib/core/BatchScheduler.ts index 7ea9ef8a5..d44ffabfe 100644 --- a/lib/core/BatchScheduler.ts +++ b/lib/core/BatchScheduler.ts @@ -1,4 +1,6 @@ import * as timeSpan from 'time-span'; +import EventCode from './EventCode'; +import EventEmitter from '../common/EventEmitter'; import IBlockchain from './interfaces/IBlockchain'; import IVersionManager from './interfaces/IVersionManager'; import Logger from '../common/Logger'; @@ -50,6 +52,8 @@ export default class BatchScheduler { const batchWriter = this.versionManager.getBatchWriter(currentTime); await batchWriter.write(); + + EventEmitter.emit(EventCode.BatchWriterProcessingLoopSuccess); } catch (error) { Logger.error('Unexpected and unhandled error during batch writing, investigate and fix:'); Logger.error(error); diff --git a/lib/core/Core.ts b/lib/core/Core.ts index 4dfbdb5ef..8be82ce2f 100644 --- a/lib/core/Core.ts +++ b/lib/core/Core.ts @@ -1,9 +1,10 @@ import * as timeSpan from 'time-span'; -import { ISidetreeCas, ISidetreeLogger } from '..'; +import { ISidetreeCas, ISidetreeEventEmitter, ISidetreeLogger } from '..'; import BatchScheduler from './BatchScheduler'; import Blockchain from './Blockchain'; import Config from './models/Config'; import DownloadManager from './DownloadManager'; +import EventEmitter from '../common/EventEmitter'; import LogColor from '../common/LogColor'; import Logger from '../common/Logger'; import MongoDbOperationStore from './MongoDbOperationStore'; @@ -66,8 +67,9 @@ export default class Core { * The initialization method that must be called before consumption of this core object. * The method starts the Observer and Batch Writer. */ - public async initialize (customLogger?: ISidetreeLogger) { + public async initialize (customLogger?: ISidetreeLogger, customEventEmitter?: ISidetreeEventEmitter) { Logger.initialize(customLogger); + EventEmitter.initialize(customEventEmitter); // DB initializations. await this.serviceStateStore.initialize(); diff --git a/lib/core/EventCode.ts b/lib/core/EventCode.ts new file mode 100644 index 000000000..1834224b4 --- /dev/null +++ b/lib/core/EventCode.ts @@ -0,0 +1,7 @@ +/** + * Event codes used by Sidetree core service. + */ +export default { + BatchWriterProcessingLoopSuccess: 'batch_writer_processing_loop_success', + ObserverProcessingLoopSuccess: 'observer_processing_loop_success' +}; diff --git a/lib/core/Observer.ts b/lib/core/Observer.ts index 8a18c324d..985823f01 100644 --- a/lib/core/Observer.ts +++ b/lib/core/Observer.ts @@ -1,5 +1,7 @@ import * as timeSpan from 'time-span'; import TransactionUnderProcessingModel, { TransactionProcessingStatus } from './models/TransactionUnderProcessingModel'; +import EventCode from './EventCode'; +import EventEmitter from '../common/EventEmitter'; import IBlockchain from './interfaces/IBlockchain'; import IOperationStore from './interfaces/IOperationStore'; import ITransactionProcessor from './interfaces/ITransactionProcessor'; @@ -121,7 +123,7 @@ export default class Observer { // NOTE: Blockchain reorg has happened for sure only if `invalidTransactionNumberOrTimeHash` AND // latest transaction time is less or equal to blockchain service time. - // This check will prevent Core from reverting transactions if/when blockchain service is reinitializing its data itself. + // This check will prevent Core from reverting transactions if/when blockchain service is re-initializing its data itself. let blockReorganizationDetected = false; if (invalidTransactionNumberOrTimeHash) { if (lastKnownTransactionTime <= this.blockchain.approximateTime.time) { @@ -161,6 +163,8 @@ export default class Observer { // Continue onto processing unresolvable transactions if any. await this.processUnresolvableTransactions(); + + EventEmitter.emit(EventCode.ObserverProcessingLoopSuccess); } catch (error) { Logger.error(`Encountered unhandled and possibly fatal Observer error, must investigate and fix:`); Logger.error(error); diff --git a/lib/index.ts b/lib/index.ts index 1bdaf7916..6b900c627 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -3,6 +3,7 @@ import ISidetreeBitcoinConfig from './bitcoin/IBitcoinConfig'; import ISidetreeBitcoinWallet from './bitcoin/interfaces/IBitcoinWallet'; import ISidetreeCas from './core/interfaces/ICas'; +import ISidetreeEventEmitter from './common/interfaces/IEventEmitter'; import ISidetreeLogger from './common/interfaces/ILogger'; import SidetreeBitcoinProcessor from './bitcoin/BitcoinProcessor'; import SidetreeBitcoinVersionModel from './bitcoin/models/BitcoinVersionModel'; @@ -32,5 +33,6 @@ export { // Common exports. export { + ISidetreeEventEmitter, ISidetreeLogger }; diff --git a/package.json b/package.json index 34fa36aa1..e549621f3 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,8 @@ "lib/bitcoin/versions/[0-9]**/**", "lib/core/versions/[0-9]**/**", "lib/core/versions/**/VersionMetadata.ts", - "lib/**/**ErrorCode.ts" + "lib/**/**ErrorCode.ts", + "lib/**/**EventCode.ts" ], "reporter": [ "text", diff --git a/tests/core/Core.spec.ts b/tests/core/Core.spec.ts index 5a191d017..78e274638 100644 --- a/tests/core/Core.spec.ts +++ b/tests/core/Core.spec.ts @@ -1,5 +1,6 @@ import Config from '../../lib/core/models/Config'; import Core from '../../lib/core/Core'; +import EventEmitter from '../../lib/common/EventEmitter'; import IRequestHandler from '../../lib/core/interfaces/IRequestHandler'; import Logger from '../../lib/common/Logger'; import MockCas from '../mocks/MockCas'; @@ -58,7 +59,7 @@ describe('Core', async () => { expect(downloadManagerStartSpy).toHaveBeenCalled(); }); - it('should override the default logger if custom logger is given.', async () => { + it('should override the default logger/event emitter if custom logger/event emitter is given.', async () => { const core = new Core(testConfig, testVersionConfig, mockCas); spyOn(core['serviceStateStore'], 'initialize'); @@ -75,17 +76,26 @@ describe('Core', async () => { let customLoggerInvoked = false; const customLogger = { - info: (_data: any) => { customLoggerInvoked = true; }, - warn: (_data: any) => { }, - error: (_data: any) => { } + info: () => { customLoggerInvoked = true; }, + warn: () => { }, + error: () => { } }; - await core.initialize(customLogger); + let customEvenEmitterInvoked = false; + const customEvenEmitter = { + emit: async () => { customEvenEmitterInvoked = true; } + }; + + await core.initialize(customLogger, customEvenEmitter); // Invoke logger to trigger the custom logger's method defined above. Logger.info('anything'); + // Invoke event emitter to trigger the custom emitter's method defined above. + await EventEmitter.emit('anything'); + expect(customLoggerInvoked).toBeTruthy(); + expect(customEvenEmitterInvoked).toBeTruthy(); }); it('should not start the Batch Writer and Observer if they are disabled.', async () => {