From 13f9bd7681b0d48b903f9526ef83b4f96e8cc8db Mon Sep 17 00:00:00 2001 From: Eric Richardson Date: Wed, 17 Jan 2024 16:58:29 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20handle=20when=20artemis?= =?UTF-8?q?=20is=20unconfigured?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adds checks for when artemis is not configured + add some helpful fields to user exposed models --- README.md | 7 ++++++- src/artemis/artemis.service.spec.ts | 12 ++++++++++++ src/artemis/artemis.service.ts | 8 ++++++++ .../models/offline-receipt.model.ts | 5 +++++ .../offline-starter.service.spec.ts | 15 +++++++++++---- src/offline-starter/offline-starter.service.ts | 6 ++++++ .../offline-submitter.service.ts | 11 +++++++++-- src/transactions/transactions.service.spec.ts | 1 + 8 files changed, 58 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e56ce91a..511f0796 100644 --- a/README.md +++ b/README.md @@ -91,11 +91,16 @@ NOTIFICATIONS_LEGITIMACY_SECRET=## A secret used to create HMAC signatures ## AUTH_STRATEGY=## list of comma separated auth strategies to use e.g. (`apiKey,open`) ## API_KEYS=## list of comma separated api keys to initialize the `apiKey` strategy with ## # Datastore: -REST_POSTGRES_HOST=## Domain or IP indicating of the DB ## +REST_POSTGRES_HOST=## Domain or IP of DB instance ## REST_POSTGRES_PORT=## Port the DB is listening (usually 5432) ## REST_POSTGRES_USER=## DB user to use## REST_POSTGRES_PASSWORD=## Password of the user ## REST_POSTGRES_DATABASE=## Database to use ## +# Artemis: +ARTEMIS_HOST=localhost## Domain or IP of artemis instance ## +ARTEMIS_USERNAME=artemis ## Artemis user ## +ARTEMIS_PASSWORD=artemis ## Artemis password ## +ARTEMIS_PORT=5672 ## Port of AMQP acceptor ## ``` ## Signing Transactions diff --git a/src/artemis/artemis.service.spec.ts b/src/artemis/artemis.service.spec.ts index 8eb4fd52..df959fed 100644 --- a/src/artemis/artemis.service.spec.ts +++ b/src/artemis/artemis.service.spec.ts @@ -71,6 +71,7 @@ jest.mock('rhea-promise', () => { describe('ArtemisService', () => { let service: ArtemisService; let logger: DeepMocked; + let configSpy: jest.SpyInstance; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -78,6 +79,8 @@ describe('ArtemisService', () => { }).compile(); service = module.get(ArtemisService); + configSpy = jest.spyOn(service, 'isConfigured'); + configSpy.mockReturnValue(true); logger = module.get(PolymeshLogger); }); @@ -166,5 +169,14 @@ describe('ArtemisService', () => { expect(logger.error).toHaveBeenCalled(); }); + + it('should do no work if service is not configured', async () => { + configSpy.mockReturnValue(false); + + const initialCallCount = mockConnectionClose.mock.calls.length; + await service.onApplicationShutdown(); + + expect(mockConnectionClose.mock.calls.length).toEqual(initialCallCount); + }); }); }); diff --git a/src/artemis/artemis.service.ts b/src/artemis/artemis.service.ts index ee6f105d..c8da350e 100644 --- a/src/artemis/artemis.service.ts +++ b/src/artemis/artemis.service.ts @@ -37,11 +37,19 @@ export class ArtemisService implements OnApplicationShutdown { this.logger.setContext(ArtemisService.name); } + public isConfigured(): boolean { + return !!process.env.ARTEMIS_HOST; + } + public async onApplicationShutdown(signal?: string | undefined): Promise { this.logger.debug( `artemis service received application shutdown request, sig: ${signal} - now closing connections` ); + if (!this.isConfigured()) { + return; + } + const closePromises = [ ...this.receivers.map(receiver => receiver.close()), ...this.addressEntries().map(entry => entry.sender.close()), diff --git a/src/offline-starter/models/offline-receipt.model.ts b/src/offline-starter/models/offline-receipt.model.ts index 4d8c73d2..e448738e 100644 --- a/src/offline-starter/models/offline-receipt.model.ts +++ b/src/offline-starter/models/offline-receipt.model.ts @@ -7,6 +7,11 @@ import { TransactionPayload } from '@polymeshassociation/polymesh-sdk/types'; import { FromBigNumber } from '~/common/decorators/transformation'; export class OfflineReceiptModel { + @ApiProperty({ + description: 'The internal ID of the transaction', + }) + readonly id: string; + @ApiProperty({ description: 'The AMQP delivery ID', type: BigNumber, diff --git a/src/offline-starter/offline-starter.service.spec.ts b/src/offline-starter/offline-starter.service.spec.ts index e1bdd27a..29eb7fce 100644 --- a/src/offline-starter/offline-starter.service.spec.ts +++ b/src/offline-starter/offline-starter.service.spec.ts @@ -3,6 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { GenericPolymeshTransaction } from '@polymeshassociation/polymesh-sdk/types'; import { ArtemisService } from '~/artemis/artemis.service'; +import { AppConfigError } from '~/common/errors'; import { AddressName } from '~/common/utils/amqp'; import { mockPolymeshLoggerProvider } from '~/logger/mock-polymesh-logger'; import { OfflineStarterService } from '~/offline-starter/offline-starter.service'; @@ -27,11 +28,10 @@ describe('OfflineStarterService', () => { }); describe('method: beginTransaction', () => { + const mockTx = new MockPolymeshTransaction(); + mockTx.toSignablePayload.mockReturnValue('mockPayload'); + const tx = mockTx as unknown as GenericPolymeshTransaction; it('should submit the transaction to the queue', async () => { - const mockTx = new MockPolymeshTransaction(); - mockTx.toSignablePayload.mockReturnValue('mockPayload'); - const tx = mockTx as unknown as GenericPolymeshTransaction; - await service.beginTransaction(tx, { clientId: 'someId' }); expect(mockArtemisService.sendMessage).toHaveBeenCalledWith( @@ -39,5 +39,12 @@ describe('OfflineStarterService', () => { expect.objectContaining({ id: expect.any(String), payload: 'mockPayload' }) ); }); + + it('should throw a config error if artemis is not active', async () => { + mockArtemisService.isConfigured.mockReturnValue(false); + const expectedError = new AppConfigError('artemis', 'service is not configured'); + + expect(service.beginTransaction(tx, { clientId: 'someId' })).rejects.toThrow(expectedError); + }); }); }); diff --git a/src/offline-starter/offline-starter.service.ts b/src/offline-starter/offline-starter.service.ts index 2a8aa858..b4bb791e 100644 --- a/src/offline-starter/offline-starter.service.ts +++ b/src/offline-starter/offline-starter.service.ts @@ -4,6 +4,7 @@ import { GenericPolymeshTransaction } from '@polymeshassociation/polymesh-sdk/ty import { randomUUID } from 'crypto'; import { ArtemisService } from '~/artemis/artemis.service'; +import { AppConfigError } from '~/common/errors'; import { AddressName } from '~/common/utils/amqp'; import { PolymeshLogger } from '~/logger/polymesh-logger.service'; import { OfflineReceiptModel } from '~/offline-starter/models/offline-receipt.model'; @@ -23,6 +24,10 @@ export class OfflineStarterService { transaction: GenericPolymeshTransaction, metadata?: Record ): Promise { + if (!this.artemisService.isConfigured()) { + throw new AppConfigError('artemis', 'service is not configured'); + } + const internalTxId = this.generateTxId(); const payload = await transaction.toSignablePayload({ ...metadata, internalTxId }); @@ -37,6 +42,7 @@ export class OfflineStarterService { const delivery = await this.artemisService.sendMessage(topicName, request); const model = new OfflineReceiptModel({ + id: internalTxId, deliveryId: new BigNumber(delivery.id), payload: payload.payload, metadata: payload.metadata, diff --git a/src/offline-submitter/offline-submitter.service.ts b/src/offline-submitter/offline-submitter.service.ts index 465e3a27..7bad7f59 100644 --- a/src/offline-submitter/offline-submitter.service.ts +++ b/src/offline-submitter/offline-submitter.service.ts @@ -53,9 +53,16 @@ export class OfflineSubmitterService { ); this.logger.log(`transaction finalized: ${id}`); - const msg = JSON.parse(JSON.stringify(result)); // make sure its serializes properly + const resultData = JSON.parse(JSON.stringify(result)); // make sure its serializes properly - await this.artemisService.sendMessage(AddressName.Finalizations, msg); + const finalizationMsg = { + ...resultData, + id, + address, + nonce, + }; + + await this.artemisService.sendMessage(AddressName.Finalizations, finalizationMsg); transaction.blockHash = result.blockHash as string; transaction.txIndex = result.txIndex as string; diff --git a/src/transactions/transactions.service.spec.ts b/src/transactions/transactions.service.spec.ts index 2e6794b7..687a1117 100644 --- a/src/transactions/transactions.service.spec.ts +++ b/src/transactions/transactions.service.spec.ts @@ -223,6 +223,7 @@ describe('TransactionsService', () => { describe('submit (with AMQP)', () => { const fakeReceipt = new OfflineReceiptModel({ + id: 'someId', deliveryId: new BigNumber(1), topicName: AddressName.Requests, payload: {} as SignerPayloadJSON,