From 2511a463ddbe7f6d6f256d27b71c107eeb8014e3 Mon Sep 17 00:00:00 2001 From: Jonasz Date: Thu, 28 Jul 2022 09:24:01 +0200 Subject: [PATCH] feat: save commands even if they're rejected by aggregate (#102) --- .../offchain-certificate.service.ts | 13 ++-- .../validators/transfer-command.validator.ts | 1 - packages/origin-247-certificate/src/types.ts | 6 -- .../offchain-certificate.spec.ts | 59 ++++++++++++++++++- 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/packages/origin-247-certificate/src/offchain-certificate/offchain-certificate.service.ts b/packages/origin-247-certificate/src/offchain-certificate/offchain-certificate.service.ts index 1aa5626e..005e9545 100644 --- a/packages/origin-247-certificate/src/offchain-certificate/offchain-certificate.service.ts +++ b/packages/origin-247-certificate/src/offchain-certificate/offchain-certificate.service.ts @@ -217,8 +217,7 @@ export class OffChainCertificateService { return; } await validateBatchClaimCommands(commands); - await this.validateBatchClaim(commands); - await this.handleBatch(commands); + await this.handleBatch(commands, (commands) => this.validateBatchClaim(commands)); } public async batchTransfer(commands: ITransferCommand[]): Promise { @@ -227,14 +226,18 @@ export class OffChainCertificateService { } await validateBatchTransferCommands(commands); - await this.validateBatchTransfer(commands); - await this.handleBatch(commands); + await this.handleBatch(commands, (commands) => this.validateBatchTransfer(commands)); } - private async handleBatch(commands: (IClaimCommand | ITransferCommand)[]) { + private async handleBatch( + commands: CommandType[], + commandValidator: (commands: CommandType[]) => Promise + ) { const savedCommands = await this.certCommandRepo.saveMany( commands.map((command) => ({ payload: command })) ); + + await commandValidator(commands); const events = commands.map((command) => createEventFromCommand(command)); const eventsByCertificate = groupByInternalCertificateId(events); diff --git a/packages/origin-247-certificate/src/offchain-certificate/validators/transfer-command.validator.ts b/packages/origin-247-certificate/src/offchain-certificate/validators/transfer-command.validator.ts index a13e5ed9..550be4f1 100644 --- a/packages/origin-247-certificate/src/offchain-certificate/validators/transfer-command.validator.ts +++ b/packages/origin-247-certificate/src/offchain-certificate/validators/transfer-command.validator.ts @@ -23,7 +23,6 @@ class TransferCommandDto implements ITransferCommand { @IsNumber() @Min(0) certificateId: number; - off; @IsEthereumAddress() fromAddress: string; diff --git a/packages/origin-247-certificate/src/types.ts b/packages/origin-247-certificate/src/types.ts index 5bc26107..cdd6b622 100644 --- a/packages/origin-247-certificate/src/types.ts +++ b/packages/origin-247-certificate/src/types.ts @@ -46,9 +46,3 @@ export interface IBatchClaimCommand { export interface IBatchTransferCommand { transfers: ITransferCommand[]; } - -export interface IVolumeDistribution { - publicVolume: string; - privateVolume: string; - claimedVolume: string; -} diff --git a/packages/origin-247-certificate/test/offchain-certificate/offchain-certificate.spec.ts b/packages/origin-247-certificate/test/offchain-certificate/offchain-certificate.spec.ts index 4b234718..4b8d902a 100644 --- a/packages/origin-247-certificate/test/offchain-certificate/offchain-certificate.spec.ts +++ b/packages/origin-247-certificate/test/offchain-certificate/offchain-certificate.spec.ts @@ -12,8 +12,16 @@ import { } from '../../src'; import { CertificateEventType } from '../../src/offchain-certificate/events/Certificate.events'; import { CertificateEventRepository } from '../../src/offchain-certificate/repositories/CertificateEvent/CertificateEvent.repository'; -import { CERTIFICATE_EVENT_REPOSITORY } from '../../src/offchain-certificate/repositories/repository.keys'; +import { + CERTIFICATE_COMMAND_REPOSITORY, + CERTIFICATE_EVENT_REPOSITORY +} from '../../src/offchain-certificate/repositories/repository.keys'; import { CertificateForUnitTestsService } from '../../src/onchain-certificate/onchain-certificateForUnitTests.service'; +import { CertificateErrors } from '../../src/offchain-certificate/errors'; +import BatchError = CertificateErrors.BatchError; +import { CertificateCommandRepository } from '../../src/offchain-certificate/repositories/CertificateCommand/CertificateCommand.repository'; +import { isEqual } from 'lodash'; +import { ethers } from 'ethers'; describe('OffchainCertificateService + BlockchainSynchronizeService', () => { let app: TestingModule; @@ -21,6 +29,7 @@ describe('OffchainCertificateService + BlockchainSynchronizeService', () => { let synchronizeService: BlockchainSynchronizeService; let onChainCertificateService: OnChainCertificateService; let certificateEventRepository: CertificateEventRepository; + let certificateCommandRepository: CertificateCommandRepository; describe('Working OnChain module', () => { beforeEach(async () => { @@ -32,6 +41,7 @@ describe('OffchainCertificateService + BlockchainSynchronizeService', () => { synchronizeService = await app.resolve(BlockchainSynchronizeService); onChainCertificateService = await app.resolve(ONCHAIN_CERTIFICATE_SERVICE_TOKEN); certificateEventRepository = await app.resolve(CERTIFICATE_EVENT_REPOSITORY); + certificateCommandRepository = await app.resolve(CERTIFICATE_COMMAND_REPOSITORY); await app.init(); }); @@ -65,6 +75,53 @@ describe('OffchainCertificateService + BlockchainSynchronizeService', () => { expect(events).toHaveLength(6); }); + it('should issue certificate and then save transfer commands even when they are not further accepted by aggregate', async () => { + const [certificateId] = await offChainCertificateService.batchIssue([issueCommand]); + const expectedOriginalError = new Error( + `Transfer for: ${certificateId} failed. Address: ${transferAddress} has not enough balance.` + ); + const expectedBatchError = new BatchError(expectedOriginalError); + const invalidTransferCommandPayload = { + certificateId, + ...transferCommand, + fromAddress: transferAddress, + toAddress: issueAddress + }; + + await expect(() => + offChainCertificateService.batchTransfer([invalidTransferCommandPayload]) + ).rejects.toEqual(expectedBatchError); + + const commands = await certificateCommandRepository.getAll(); + const savedTransferCommand = commands.find((c) => + isEqual(invalidTransferCommandPayload, c.payload) + ); + expect(savedTransferCommand).toBeDefined(); + }); + + it('should issue certificate and then save claim commands even when they are not further accepted by aggregate', async () => { + const [certificateId] = await offChainCertificateService.batchIssue([issueCommand]); + const expectedOriginalError = new Error( + `Claim for internalCertificateId: ${certificateId} failed. Transfer is for zero address(0x0).` + ); + const expectedBatchError = new BatchError(expectedOriginalError); + const invalidClaimCommandPayload = { + certificateId, + ...claimCommand, + forAddress: ethers.constants.AddressZero + }; + + await expect(() => + offChainCertificateService.batchClaim([invalidClaimCommandPayload]) + ).rejects.toEqual(expectedBatchError); + + const commands = await certificateCommandRepository.getAll(); + const savedClaimCommand = commands.find((c) => + isEqual(invalidClaimCommandPayload, c.payload) + ); + expect(savedClaimCommand).toBeDefined(); + }); + it('should batch issue, batch transfer, and batch claim certificates, and then synchronize it', async () => { const [id1, id2] = await offChainCertificateService.batchIssue([ issueCommand,