diff --git a/apps/account-api/src/main.ts b/apps/account-api/src/main.ts index 5d0eb25e..6e2429c0 100644 --- a/apps/account-api/src/main.ts +++ b/apps/account-api/src/main.ts @@ -23,7 +23,10 @@ BigInt.prototype['toJSON'] = function () { * can't connect to Redis at startup) */ function startShutdownTimer() { - setTimeout(() => process.exit(1), 10_000); + setTimeout(() => { + logger.log('Shutdown timer expired'); + process.exit(0); + }, 10_000); } async function bootstrap() { @@ -60,6 +63,7 @@ async function bootstrap() { logger.warn('Received shutdown event'); startShutdownTimer(); await app.close(); + logger.warn('app closed'); }); const config = app.get(apiConfig.KEY); diff --git a/apps/account-api/src/services/accounts.service.ts b/apps/account-api/src/services/accounts.service.ts index 59d4cf19..38458547 100644 --- a/apps/account-api/src/services/accounts.service.ts +++ b/apps/account-api/src/services/accounts.service.ts @@ -19,6 +19,7 @@ import { import { TransactionType } from '#types/account-webhook'; import apiConfig, { IAccountApiConfig } from '#account-api/api.config'; import blockchainConfig, { IBlockchainConfig } from '#blockchain/blockchain.config'; +import { ApiPromise } from '@polkadot/api'; @Injectable() export class AccountsService { @@ -70,7 +71,7 @@ export class AccountsService { // eslint-disable-next-line class-methods-use-this async signInWithFrequency(request: WalletLoginRequestDto): Promise { - const api = await this.blockchainService.getApi(); + const api = (await this.blockchainService.getApi()) as ApiPromise; const { providerId } = this.blockchainConf; if (request.signUp) { try { diff --git a/apps/account-api/src/services/delegation.service.ts b/apps/account-api/src/services/delegation.service.ts index 13c31dc9..0ba299e1 100644 --- a/apps/account-api/src/services/delegation.service.ts +++ b/apps/account-api/src/services/delegation.service.ts @@ -104,8 +104,8 @@ export class DelegationService { throw new NotFoundException(`Invalid MSA Id ${providerId}`); } - const providerInfo = await this.blockchainService.api.query.msa.providerToRegistryEntry(providerId); - if (providerInfo.isNone) { + const providerInfo = await this.blockchainService.getProviderToRegistryEntry(providerId); + if (!providerInfo) { throw new BadRequestException(`Supplied ID not a Provider ${providerId}`); } diff --git a/apps/account-api/src/services/keys.service.ts b/apps/account-api/src/services/keys.service.ts index 165e834f..761e95de 100644 --- a/apps/account-api/src/services/keys.service.ts +++ b/apps/account-api/src/services/keys.service.ts @@ -5,9 +5,9 @@ import { HexString } from '@polkadot/util/types'; import { AddNewPublicKeyAgreementPayloadRequest, AddNewPublicKeyAgreementRequestDto, - ItemActionType, ItemizedSignaturePayloadDto, } from '#types/dtos/account/graphs.request.dto'; +import { ItemActionType } from '#types/enums/item-action-type.enum'; import { u8aToHex, u8aWrapBytes } from '@polkadot/util'; import * as BlockchainConstants from '#types/constants/blockchain-constants'; import apiConfig, { IAccountApiConfig } from '#account-api/api.config'; diff --git a/apps/account-api/src/services/siwfV2.service.spec.ts b/apps/account-api/src/services/siwfV2.service.spec.ts index 8b7555d5..4d7d1243 100644 --- a/apps/account-api/src/services/siwfV2.service.spec.ts +++ b/apps/account-api/src/services/siwfV2.service.spec.ts @@ -1,3 +1,4 @@ +import { expect, it, jest } from '@jest/globals'; import { Test, TestingModule } from '@nestjs/testing'; import { BadRequestException } from '@nestjs/common'; import { decodeSignedRequest } from '@projectlibertylabs/siwfv2'; @@ -14,12 +15,31 @@ import { validSiwfNewUserResponse, } from './siwfV2.mock.spec'; import { EnqueueService } from '#account-lib/services/enqueue-request.service'; +import { ApiPromise } from '@polkadot/api'; +import { mockApiPromise } from '#testlib/polkadot-api.mock.spec'; + +jest.mock('#blockchain/blockchain-rpc-query.service'); +jest.mock( + '#account-lib/services/enqueue-request.service', +); +jest.mock('@polkadot/api', () => { + const originalModule = jest.requireActual('@polkadot/api'); + return { + __esModules: true, + WsProvider: jest.fn().mockImplementation(() => originalModule.WsProvider), + ApiPromise: jest.fn().mockImplementation(() => ({ + ...originalModule.ApiPromise, + ...mockApiPromise, + })), + }; +}); const mockBlockchainConfigProvider = GenerateMockConfigProvider('blockchain', { capacityLimit: { serviceLimit: { type: 'percentage', value: 80n } }, providerId: 1n, providerSeedPhrase: '//Alice', frequencyApiWsUrl: new URL('ws://localhost:9944'), + frequencyTimeoutSecs: 10, isDeployedReadOnly: false, }); @@ -44,40 +64,24 @@ const exampleCredentials = [ describe('SiwfV2Service', () => { let siwfV2Service: SiwfV2Service; - - const mockBlockchainService = { - getNetworkType: jest.fn(), - publicKeyToMsaId: jest.fn(), - getApi: jest.fn(), - }; - - const mockBlockchainServiceProvider = { - provide: BlockchainRpcQueryService, - useValue: mockBlockchainService, - }; - - const mockEnqueueService = { - enqueueRequest: jest.fn(), - }; - - const mockEnqueueServiceProvider = { - provide: EnqueueService, - useValue: mockEnqueueService, - }; + let blockchainService: BlockchainRpcQueryService; + let enqueueService: EnqueueService; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [], providers: [ SiwfV2Service, - mockBlockchainServiceProvider, + BlockchainRpcQueryService, mockAccountApiConfigProvider, mockBlockchainConfigProvider, - mockEnqueueServiceProvider, + EnqueueService, ], }).compile(); siwfV2Service = module.get(SiwfV2Service); + blockchainService = module.get(BlockchainRpcQueryService); + enqueueService = module.get(EnqueueService); }); it('should be defined', () => { @@ -151,7 +155,7 @@ describe('SiwfV2Service', () => { jest.spyOn(siwfV2Service as any, 'swifV2Endpoint').mockReturnValue('https://siwf.example.com'); // Mock the global fetch function - global.fetch = jest.fn().mockResolvedValue({ + jest.spyOn(global, 'fetch').mockResolvedValue({ ok: true, status: 200, json: async () => validSiwfAddDelegationResponsePayload, @@ -193,7 +197,7 @@ describe('SiwfV2Service', () => { jest.spyOn(siwfV2Service as any, 'swifV2Endpoint').mockReturnValue('https://siwf.example.com'); // Mock the global fetch function - global.fetch = jest.fn().mockResolvedValue({ + jest.spyOn(global, 'fetch').mockResolvedValue({ ok: false, status: 400, json: async () => ({ error: 'Invalid authorization code' }), @@ -225,7 +229,7 @@ describe('SiwfV2Service', () => { jest.spyOn(siwfV2Service as any, 'swifV2Endpoint').mockReturnValue('https://siwf.example.com'); // Mock the global fetch function - global.fetch = jest.fn().mockResolvedValue({ + jest.spyOn(global, 'fetch').mockResolvedValue({ ok: true, status: 200, json: async () => validSiwfLoginResponsePayload, @@ -280,24 +284,24 @@ describe('SiwfV2Service', () => { }); it('Should parse MSA Id', async () => { - mockBlockchainService.publicKeyToMsaId.mockReturnValueOnce('123456'); + jest.spyOn(blockchainService, 'publicKeyToMsaId').mockResolvedValueOnce('123456'); const result = await siwfV2Service.getSiwfV2LoginResponse(validSiwfAddDelegationResponsePayload); expect(result).toBeDefined(); expect(result.msaId).toEqual('123456'); - expect(mockBlockchainService.publicKeyToMsaId).toHaveBeenCalledWith( + expect(blockchainService.publicKeyToMsaId).toHaveBeenCalledWith( 'f6akufkq9Lex6rT8RCEDRuoZQRgo5pWiRzeo81nmKNGWGNJdJ', ); }); it('Should NOT return an MSA Id if there is none', async () => { - mockBlockchainService.publicKeyToMsaId.mockReturnValueOnce(null); + jest.spyOn(blockchainService, 'publicKeyToMsaId').mockResolvedValueOnce(null); const result = await siwfV2Service.getSiwfV2LoginResponse(validSiwfAddDelegationResponsePayload); expect(result).toBeDefined(); expect(result.msaId).toBeUndefined(); - expect(mockBlockchainService.publicKeyToMsaId).toHaveBeenCalledWith( + expect(blockchainService.publicKeyToMsaId).toHaveBeenCalledWith( 'f6akufkq9Lex6rT8RCEDRuoZQRgo5pWiRzeo81nmKNGWGNJdJ', ); }); @@ -340,21 +344,27 @@ describe('SiwfV2Service', () => { ); it('should do nothing if there are no chain submissions', async () => { + const enqueueSpy = jest.spyOn(enqueueService, 'enqueueRequest'); const result = await siwfV2Service.queueChainActions(validSiwfLoginResponsePayload); expect(result).toBeNull(); - expect(mockEnqueueService.enqueueRequest).not.toHaveBeenCalled(); + expect(enqueueSpy).not.toHaveBeenCalled(); }); it('should return correctly for the add delegation setup', async () => { + const enqueueSpy = jest.spyOn(enqueueService, 'enqueueRequest'); const correctGrantDelegationHash = '0xed01043c038eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4801bac399831b9e3ad464a16e62ad1252cc8344a2c52f80252b2aa450a06ae2362f6f4afcaca791a81f28eaa99080e2654bdbf1071a276213242fc153cca43cfa8e01000000000000001405000700080009000a0018000000'; - mockBlockchainService.getApi.mockReturnValue( - mockApiTxHashes([{ pallet: 'msa', extrinsic: 'grantDelegation', hash: correctGrantDelegationHash }]), - ); + jest + .spyOn(blockchainService, 'getApi') + .mockResolvedValue( + mockApiTxHashes([ + { pallet: 'msa', extrinsic: 'grantDelegation', hash: correctGrantDelegationHash }, + ]) as ApiPromise, + ); await expect(siwfV2Service.queueChainActions(validSiwfAddDelegationResponsePayload)).resolves.not.toThrow(); - expect(mockEnqueueService.enqueueRequest).toHaveBeenCalledWith({ + expect(enqueueSpy).toHaveBeenCalledWith({ calls: [ { encodedExtrinsic: correctGrantDelegationHash, @@ -367,22 +377,30 @@ describe('SiwfV2Service', () => { }); it('should return correctly for the new user setup', async () => { + const enqueueSpy = jest.spyOn(enqueueService, 'enqueueRequest'); const correctGrantDelegationHash = '0xed01043c018eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48011a27cb6d79b508e1ffc8d6ae70af78d5b3561cdc426124a06f230d7ce70e757e1947dd1bac8f9e817c30676a5fa6b06510bae1201b698b044ff0660c60f18c8a01000000000000001405000700080009000a0018000000'; const correctStatefulStorageHash = '0xf501043f068eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48019eb338773b386ded2e3731ba68ba734c80408b3ad24f92ed3c60342d374a32293851fa8e41d722c72a5a4e765a9e401c68570a8c666ab678e4e5d94aa6825d851c0014000000040040eea1e39d2f154584c4b1ca8f228bb49a'; const correctClaimHandleHash = '0xd9010442008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4801b004140fd8ba3395cf5fcef49df8765d90023c293fde4eaf2e932cc24f74fc51b006c0bebcf31d85565648b4881fa22115e0051a3bdb95ab5bf7f37ac66f798f344578616d706c6548616e646c6518000000'; - mockBlockchainService.getApi.mockReturnValue( + jest.spyOn(blockchainService, 'getApi').mockResolvedValue( mockApiTxHashes([ { pallet: 'msa', extrinsic: 'createSponsoredAccountWithDelegation', hash: correctGrantDelegationHash }, { pallet: 'statefulStorage', extrinsic: 'applyItemActionsWithSignatureV2', hash: correctStatefulStorageHash }, { pallet: 'handles', extrinsic: 'claimHandle', hash: correctClaimHandleHash }, - ]), + ]) as ApiPromise, ); - await expect(siwfV2Service.queueChainActions(validSiwfNewUserResponse)).resolves.not.toThrow(); - expect(mockEnqueueService.enqueueRequest).toHaveBeenCalledWith({ + try { + await siwfV2Service.queueChainActions(validSiwfNewUserResponse); + } catch (err: any) { + console.error(err); + throw err; + } + + // await expect(siwfV2Service.queueChainActions(validSiwfNewUserResponse)).resolves.not.toThrow(); + expect(enqueueSpy).toHaveBeenCalledWith({ calls: [ { encodedExtrinsic: correctGrantDelegationHash, diff --git a/apps/account-api/src/services/siwfV2.service.ts b/apps/account-api/src/services/siwfV2.service.ts index 07155a9a..041f98dc 100644 --- a/apps/account-api/src/services/siwfV2.service.ts +++ b/apps/account-api/src/services/siwfV2.service.ts @@ -28,6 +28,7 @@ import { PublishSIWFSignupRequestDto, SIWFEncodedExtrinsic, TransactionResponse import { TransactionType } from '#types/account-webhook'; import { isNotNull } from '#utils/common/common.utils'; import { chainSignature, statefulStoragePayload } from '#utils/common/signature.util'; +import { ApiPromise } from '@polkadot/api'; @Injectable() export class SiwfV2Service { @@ -107,7 +108,7 @@ export class SiwfV2Service { return { pallet, extrinsicName, - encodedExtrinsic: api.tx.statefulStorage + encodedExtrinsic: (api as ApiPromise).tx.statefulStorage .applyItemActionsWithSignatureV2( userPublicKey, chainSignature(payload.signature), diff --git a/apps/account-api/test/handles.controller.e2e-spec.ts b/apps/account-api/test/handles.controller.e2e-spec.ts index 55a79f1c..2bacb872 100644 --- a/apps/account-api/test/handles.controller.e2e-spec.ts +++ b/apps/account-api/test/handles.controller.e2e-spec.ts @@ -1,6 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable no-undef */ -import { HttpStatus, INestApplication, ValidationPipe } from '@nestjs/common'; +import { HttpStatus, ValidationPipe, VersioningType } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { EventEmitter2 } from '@nestjs/event-emitter'; import request from 'supertest'; @@ -9,11 +9,15 @@ import { uniqueNamesGenerator, colors, names } from 'unique-names-generator'; import { ApiModule } from '../src/api.module'; import { setupProviderAndUsers } from './e2e-setup.mock.spec'; import { CacheMonitorService } from '#cache/cache-monitor.service'; +import { TimeoutInterceptor } from '#utils/interceptors/timeout.interceptor'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import apiConfig, { IAccountApiConfig } from '#account-api/api.config'; +import { BlockchainRpcQueryService } from '#blockchain/blockchain-rpc-query.service'; let HTTP_SERVER: any; describe('Handles Controller', () => { - let app: INestApplication; + let app: NestExpressApplication; let module: TestingModule; let users: ChainUser[]; let provider: ChainUser; @@ -62,18 +66,29 @@ describe('Handles Controller', () => { imports: [ApiModule], }).compile(); - app = module.createNestApplication(); + app = module.createNestApplication({ logger: ['error', 'warn', 'log', 'verbose', 'debug'], rawBody: true }); + + // Uncomment below to see logs when debugging tests + // module.useLogger(new Logger()); + + const config = app.get(apiConfig.KEY); + app.enableVersioning({ type: VersioningType.URI }); app.enableShutdownHooks(); - app.useGlobalPipes(); + app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true, enableDebugMessages: true })); + app.useGlobalInterceptors(new TimeoutInterceptor(config.apiTimeoutMs)); + app.useBodyParser('json', { limit: config.apiBodyJsonLimit }); + const eventEmitter = app.get(EventEmitter2); eventEmitter.on('shutdown', async () => { await app.close(); }); - app.useGlobalPipes(new ValidationPipe()); - app.enableShutdownHooks(); - await app.init(); + + // Make sure we're connected to the chain before running tests + const blockchainService = app.get(BlockchainRpcQueryService); + await blockchainService.isReady(); + HTTP_SERVER = app.getHttpServer(); // Redis timeout keeping test suite alive for too long; disable diff --git a/apps/account-api/test/keys.controller.e2e-spec.ts b/apps/account-api/test/keys.controller.e2e-spec.ts index bd8722c2..a23706c7 100644 --- a/apps/account-api/test/keys.controller.e2e-spec.ts +++ b/apps/account-api/test/keys.controller.e2e-spec.ts @@ -2,7 +2,7 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable no-undef */ -import { HttpStatus, INestApplication, ValidationPipe } from '@nestjs/common'; +import { HttpStatus, ValidationPipe, VersioningType } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { EventEmitter2 } from '@nestjs/event-emitter'; import request from 'supertest'; @@ -17,11 +17,15 @@ import { setupProviderAndUsers, } from './e2e-setup.mock.spec'; import { CacheMonitorService } from '#cache/cache-monitor.service'; +import apiConfig, { IAccountApiConfig } from '#account-api/api.config'; +import { TimeoutInterceptor } from '#utils/interceptors/timeout.interceptor'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { BlockchainRpcQueryService } from '#blockchain/blockchain-rpc-query.service'; let HTTP_SERVER: any; describe('Keys Controller', () => { - let app: INestApplication; + let app: NestExpressApplication; let module: TestingModule; let users: ChainUser[]; let provider: ChainUser; @@ -55,14 +59,29 @@ describe('Keys Controller', () => { imports: [ApiModule], }).compile(); - app = module.createNestApplication(); + app = module.createNestApplication({ logger: ['error', 'warn', 'log', 'verbose', 'debug'], rawBody: true }); + + // Uncomment below to see logs when debugging tests + // module.useLogger(new Logger()); + + const config = app.get(apiConfig.KEY); + app.enableVersioning({ type: VersioningType.URI }); + app.enableShutdownHooks(); + app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true, enableDebugMessages: true })); + app.useGlobalInterceptors(new TimeoutInterceptor(config.apiTimeoutMs)); + app.useBodyParser('json', { limit: config.apiBodyJsonLimit }); + const eventEmitter = app.get(EventEmitter2); eventEmitter.on('shutdown', async () => { await app.close(); }); - app.useGlobalPipes(new ValidationPipe()); - app.enableShutdownHooks(); + await app.init(); + + // Make sure we're connected to the chain before running tests + const blockchainService = app.get(BlockchainRpcQueryService); + await blockchainService.isReady(); + HTTP_SERVER = app.getHttpServer(); // Redis timeout keeping test suite alive for too long; disable diff --git a/apps/account-worker/src/transaction_notifier/notifier.service.ts b/apps/account-worker/src/transaction_notifier/notifier.service.ts index 514e1dd0..370cb045 100644 --- a/apps/account-worker/src/transaction_notifier/notifier.service.ts +++ b/apps/account-worker/src/transaction_notifier/notifier.service.ts @@ -109,14 +109,13 @@ export class TxnNotifierService let pipeline = this.cacheManager.multi({ pipeline: true }); if (extrinsicIndices.length > 0) { - const at = await this.blockchainService.api.at(currentBlock.block.header.hash); - const epoch = (await at.query.capacity.currentEpoch()).toNumber(); + const epoch = await this.blockchainService.getCurrentCapacityEpoch(); const events: FrameSystemEventRecord[] = blockEvents.filter( ({ phase }) => phase.isApplyExtrinsic && extrinsicIndices.some((index) => phase.asApplyExtrinsic.eq(index)), ); const totalCapacityWithdrawn: bigint = events - .filter(({ event }) => at.events.capacity.CapacityWithdrawn.is(event)) + .filter(({ event }) => this.blockchainService.events.capacity.CapacityWithdrawn.is(event)) .reduce((sum, { event }) => (event as unknown as any).data.amount.toBigInt() + sum, 0n); // eslint-disable-next-line no-restricted-syntax @@ -130,10 +129,12 @@ export class TxnNotifierService ({ event }) => event.section === txStatus.successEvent.section && event.method === txStatus.successEvent.method, )?.event; - const failureEvent = extrinsicEvents.find(({ event }) => at.events.system.ExtrinsicFailed.is(event))?.event; + const failureEvent = extrinsicEvents.find(({ event }) => + this.blockchainService.events.system.ExtrinsicFailed.is(event), + )?.event; // TODO: Should the webhook provide for reporting failure? - if (failureEvent && at.events.system.ExtrinsicFailed.is(failureEvent)) { + if (failureEvent && this.blockchainService.events.system.ExtrinsicFailed.is(failureEvent)) { const { dispatchError } = failureEvent.data; const moduleThatErrored = dispatchError.asModule; const moduleError = dispatchError.registry.findMetaError(moduleThatErrored); @@ -219,7 +220,9 @@ export class TxnNotifierService } } } else { - this.logger.error(`Watched transaction ${txHash} found, but neither success nor error???`); + this.logger.error( + `Watched transaction ${txHash} found in block ${currentBlockNumber}, but did not find event '${txStatus.successEvent.section}.${txStatus.successEvent.method}' in block`, + ); } pipeline = pipeline.hdel(TXN_WATCH_LIST_KEY, txHash); // Remove txn from watch list diff --git a/apps/account-worker/src/transaction_publisher/publisher.service.ts b/apps/account-worker/src/transaction_publisher/publisher.service.ts index 26aaacbe..36e8dc2a 100644 --- a/apps/account-worker/src/transaction_publisher/publisher.service.ts +++ b/apps/account-worker/src/transaction_publisher/publisher.service.ts @@ -85,7 +85,7 @@ export class TransactionPublisherService extends BaseConsumer implements OnAppli } case TransactionType.SIWF_SIGNUP: { // eslint-disable-next-line prettier/prettier - const txns = job.data.calls?.map((x) => this.blockchainService.api.tx(x.encodedExtrinsic)); + const txns = job.data.calls?.map((x) => this.blockchainService.createTxFromEncoded(x.encodedExtrinsic)); const callVec = this.blockchainService.createType('Vec', txns); [tx, txHash] = await this.processBatchTxn(callVec); targetEvent = { section: 'utility', method: 'BatchCompleted' }; @@ -108,12 +108,12 @@ export class TransactionPublisherService extends BaseConsumer implements OnAppli } case TransactionType.RETIRE_MSA: { const trx = this.blockchainService.decodeTransaction(job.data.encodedExtrinsic); - targetEvent = { section: 'msa', method: 'retireMsa' }; + targetEvent = { section: 'msa', method: 'MsaRetired' }; [tx, txHash] = await this.processProxyTxn(trx, job.data.accountId, job.data.signature); break; } case TransactionType.REVOKE_DELEGATION: { - const trx = await this.blockchainService.decodeTransaction(job.data.encodedExtrinsic); + const trx = this.blockchainService.decodeTransaction(job.data.encodedExtrinsic); targetEvent = { section: 'msa', method: 'DelegationRevoked' }; [tx, txHash] = await this.processProxyTxn(trx, job.data.accountId, job.data.signature); this.logger.debug(`tx: ${tx}`); @@ -188,7 +188,7 @@ export class TransactionPublisherService extends BaseConsumer implements OnAppli try { const prefixedSignature: SignerResult = { id: 1, signature }; const signer: Signer = getSignerForRawSignature(prefixedSignature); - const { nonce } = await this.blockchainService.api.query.system.account(accountId); + const nonce = await this.blockchainService.getNonce(accountId); const submittableExtrinsic = await ext.signAsync(accountId, { nonce, signer }); const txHash = (await submittableExtrinsic.send()).toHex(); diff --git a/apps/content-publishing-worker/src/monitor/tx.status.monitor.service.ts b/apps/content-publishing-worker/src/monitor/tx.status.monitor.service.ts index df090c0b..d040130b 100644 --- a/apps/content-publishing-worker/src/monitor/tx.status.monitor.service.ts +++ b/apps/content-publishing-worker/src/monitor/tx.status.monitor.service.ts @@ -76,14 +76,13 @@ export class TxStatusMonitoringService extends BlockchainScannerService { let pipeline = this.cacheManager.multi({ pipeline: true }); if (extrinsicIndices.length > 0) { - const at = await this.blockchainService.api.at(currentBlock.block.header.hash); - const epoch = (await at.query.capacity.currentEpoch()).toNumber(); + const epoch = await this.blockchainService.getCurrentCapacityEpoch(currentBlock.block.header.hash); const events: FrameSystemEventRecord[] = blockEvents.filter( ({ phase }) => phase.isApplyExtrinsic && extrinsicIndices.some((index) => phase.asApplyExtrinsic.eq(index)), ); const totalCapacityWithdrawn: bigint = events.reduce((sum, { event }) => { - if (at.events.capacity.CapacityWithdrawn.is(event)) { + if (this.blockchainService.events.capacity.CapacityWithdrawn.is(event)) { return sum + event.data.amount.toBigInt(); } return sum; @@ -101,10 +100,12 @@ export class TxStatusMonitoringService extends BlockchainScannerService { ({ event }) => event.section === txStatus.successEvent.section && event.method === txStatus.successEvent.method, )?.event; - const failureEvent = extrinsicEvents.find(({ event }) => at.events.system.ExtrinsicFailed.is(event))?.event; + const failureEvent = extrinsicEvents.find(({ event }) => + this.blockchainService.events.system.ExtrinsicFailed.is(event), + )?.event; // TODO: Should the webhook provide for reporting failure? - if (failureEvent && at.events.system.ExtrinsicFailed.is(failureEvent)) { + if (failureEvent && this.blockchainService.events.system.ExtrinsicFailed.is(failureEvent)) { const { dispatchError } = failureEvent.data; const moduleThatErrored = dispatchError.asModule; const moduleError = dispatchError.registry.findMetaError(moduleThatErrored); diff --git a/apps/content-watcher/src/ipfs/ipfs.processor.ts b/apps/content-watcher/src/ipfs/ipfs.processor.ts index 66f70cbf..f2155898 100644 --- a/apps/content-watcher/src/ipfs/ipfs.processor.ts +++ b/apps/content-watcher/src/ipfs/ipfs.processor.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, Logger } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { Job, Queue } from 'bullmq'; import { InjectQueue, Processor } from '@nestjs/bullmq'; import { hexToString } from '@polkadot/util'; @@ -24,8 +24,6 @@ import { IpfsService } from '#storage/ipfs/ipfs.service'; concurrency: 2, }) export class IPFSContentProcessor extends BaseConsumer { - public logger: Logger; - constructor( @InjectQueue(QueueConstants.WATCHER_BROADCAST_QUEUE_NAME) private broadcastQueue: Queue, @InjectQueue(QueueConstants.WATCHER_TOMBSTONE_QUEUE_NAME) private tombstoneQueue: Queue, diff --git a/apps/graph-worker/src/graph_notifier/graph.monitor.service.ts b/apps/graph-worker/src/graph_notifier/graph.monitor.service.ts index 8a5a19aa..e8774f5e 100644 --- a/apps/graph-worker/src/graph_notifier/graph.monitor.service.ts +++ b/apps/graph-worker/src/graph_notifier/graph.monitor.service.ts @@ -9,7 +9,7 @@ import { BlockchainService } from '#blockchain/blockchain.service'; import { BlockchainScannerService } from '#graph-lib/utils/blockchain-scanner.service'; import { SchedulerRegistry } from '@nestjs/schedule'; import { SignedBlock } from '@polkadot/types/interfaces'; -import { FrameSystemEventRecord, PalletSchemasSchemaVersionId } from '@polkadot/types/lookup'; +import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { HexString } from '@polkadot/util/types'; import { IGraphTxStatus } from '#types/interfaces'; import { CapacityCheckerService } from '#blockchain/capacity-checker.service'; @@ -31,13 +31,13 @@ export class GraphMonitorService extends BlockchainScannerService { async onApplicationBootstrap() { await this.blockchainService.isReady(); - const schemaResponse: PalletSchemasSchemaVersionId[] = - await this.blockchainService.api.query.schemas.schemaNameToIds.multi([ + const schemaResponse: { name: [string, string]; ids: number[] }[] = + await this.blockchainService.getSchemaNamesToIds([ ['dsnp', 'public-follows'], ['dsnp', 'private-follows'], ['dsnp', 'private-connections'], ]); - this.graphSchemaIds = schemaResponse.flatMap(({ ids }) => ids.map((id) => id.toNumber())); + this.graphSchemaIds = schemaResponse.flatMap(({ ids }) => ids); this.logger.log('Monitoring schemas for graph updates: ', this.graphSchemaIds); const pendingTxns = await this.cacheManager.hgetall(TXN_WATCH_LIST_KEY); // If no transactions pending, skip to end of chain at startup, else, skip to earliest @@ -121,14 +121,13 @@ export class GraphMonitorService extends BlockchainScannerService { const statusesToReport: StatusUpdate[] = []; if (extrinsicIndices.length > 0) { - const at = await this.blockchainService.api.at(currentBlock.block.header.hash); - const epoch = (await at.query.capacity.currentEpoch()).toNumber(); + const epoch = await this.blockchainService.getCurrentCapacityEpoch(currentBlock.block.header.hash); const events: FrameSystemEventRecord[] = blockEvents.filter( ({ phase }) => phase.isApplyExtrinsic && extrinsicIndices.some((index) => phase.asApplyExtrinsic.eq(index)), ); const totalCapacityWithdrawn: bigint = events.reduce((sum, { event }) => { - if (at.events.capacity.CapacityWithdrawn.is(event)) { + if (this.blockchainService.events.capacity.CapacityWithdrawn.is(event)) { return sum + event.data.amount.toBigInt(); } return sum; @@ -146,10 +145,12 @@ export class GraphMonitorService extends BlockchainScannerService { ({ event }) => event.section === txStatus.successEvent.section && event.method === txStatus.successEvent.method, )?.event; - const failureEvent = extrinsicEvents.find(({ event }) => at.events.system.ExtrinsicFailed.is(event))?.event; + const failureEvent = extrinsicEvents.find(({ event }) => + this.blockchainService.events.system.ExtrinsicFailed.is(event), + )?.event; // TODO: Should the webhook provide for reporting failure? - if (failureEvent && at.events.system.ExtrinsicFailed.is(failureEvent)) { + if (failureEvent && this.blockchainService.events.system.ExtrinsicFailed.is(failureEvent)) { const { dispatchError } = failureEvent.data; const moduleThatErrored = dispatchError.asModule; const moduleError = dispatchError.registry.findMetaError(moduleThatErrored); @@ -169,7 +170,9 @@ export class GraphMonitorService extends BlockchainScannerService { this.logger.verbose(`Successfully found transaction ${txHash} in block ${currentBlockNumber}`); statusesToReport.push({ ...txStatus, status: 'succeeded' }); } else { - this.logger.error(`Watched transaction ${txHash} found, but neither success nor error???`); + this.logger.error( + `Watched transaction ${txHash} found in block ${currentBlockNumber}, but did not find event '${txStatus.successEvent.section}.${txStatus.successEvent.method}' in block`, + ); } pipeline = pipeline.hdel(TXN_WATCH_LIST_KEY, txHash); // Remove txn from watch list @@ -271,8 +274,8 @@ export class GraphMonitorService extends BlockchainScannerService { public async monitorAllGraphUpdates(block: SignedBlock, { event }: FrameSystemEventRecord) { // Don't need this check logically, but it's a type guard to be able to access the specific event type data if ( - this.blockchainService.api.events.statefulStorage.PaginatedPageUpdated.is(event) || - this.blockchainService.api.events.statefulStorage.PaginatedPageDeleted.is(event) + this.blockchainService.events.statefulStorage.PaginatedPageUpdated.is(event) || + this.blockchainService.events.statefulStorage.PaginatedPageDeleted.is(event) ) { const schemaId = event.data.schemaId.toNumber(); if (!this.graphSchemaIds.some((id) => id === schemaId)) { @@ -287,7 +290,7 @@ export class GraphMonitorService extends BlockchainScannerService { updateType: 'GraphPageDeleted', }; - if (this.blockchainService.api.events.statefulStorage.PaginatedPageUpdated.is(event)) { + if (this.blockchainService.events.statefulStorage.PaginatedPageUpdated.is(event)) { graphUpdateNotification.currContentHash = event.data.currContentHash.toNumber(); graphUpdateNotification.updateType = 'GraphPageUpdated'; } diff --git a/deployment/k8s/frequency-gateway/templates/deployment.yaml b/deployment/k8s/frequency-gateway/templates/deployment.yaml index 362fbb6c..7f09600c 100644 --- a/deployment/k8s/frequency-gateway/templates/deployment.yaml +++ b/deployment/k8s/frequency-gateway/templates/deployment.yaml @@ -48,6 +48,8 @@ spec: port: {{ .Values.account.env.API_PORT }} initialDelaySeconds: 5 periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} args: ["{{ .Values.account.image.mode.api.args}}"] - name: account-service-worker image: "{{ .Values.account.image.repository }}:{{ .Values.account.image.tag }}" @@ -96,6 +98,8 @@ spec: port: {{ .Values.contentPublishing.env.API_PORT }} initialDelaySeconds: 5 periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} args: ["{{ .Values.contentPublishing.image.mode.api.args}}"] - name: content-publishing-service-worker image: "{{ .Values.contentPublishing.image.repository }}:{{ .Values.contentPublishing.image.tag }}" @@ -144,6 +148,8 @@ spec: port: {{ .Values.contentWatcher.env.API_PORT }} initialDelaySeconds: 5 periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} {{- end }} {{- if .Values.service.graph.deploy }} @@ -178,6 +184,8 @@ spec: port: {{ .Values.graph.env.API_PORT }} initialDelaySeconds: 5 periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} args: ["{{ .Values.graph.image.mode.api.args}}"] - name: graph-service-worker image: "{{ .Values.graph.image.repository }}:{{ .Values.graph.image.tag }}" diff --git a/deployment/k8s/frequency-gateway/values.yaml b/deployment/k8s/frequency-gateway/values.yaml index 8000a48b..982cb6dc 100644 --- a/deployment/k8s/frequency-gateway/values.yaml +++ b/deployment/k8s/frequency-gateway/values.yaml @@ -35,7 +35,7 @@ resources: {} autoscaling: enabled: false minReplicas: 1 - maxReplicas: 100 + maxReplicas: 5 targetCPUUtilizationPercentage: 80 # Additional volumes and volume mounts diff --git a/developer-docs/account/ENVIRONMENT.md b/developer-docs/account/ENVIRONMENT.md index d9b2e0be..8982c589 100644 --- a/developer-docs/account/ENVIRONMENT.md +++ b/developer-docs/account/ENVIRONMENT.md @@ -11,6 +11,7 @@ This application recognizes the following environment variables: | `CAPACITY_LIMIT` | Maximum amount of provider capacity this app is allowed to use (per epoch) type: 'percentage' 'amount' value: number (may be percentage, ie '80', or absolute amount of capacity) | JSON [(example)](https://github.com/ProjectLibertyLabs/gateway/blob/main/env-files/account.template.env) | Y | | | `SIWF_NODE_RPC_URL` | Blockchain node address resolvable from the client browser, used for SIWF | http(s): URL | Y | | | `FREQUENCY_API_WS_URL` | Blockchain API Websocket URL | ws(s): URL | Y | | +| `FREQUENCY_TIMEOUT_SECS` | Frequency chain connection timeout limit; app will terminate if disconnected longer | integer | | 10 | | `HEALTH_CHECK_MAX_RETRIES` | Number of `/health` endpoint failures allowed before marking the provider webhook service down | >= 0 | | 20 | | `HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS` | Number of seconds to retry provider webhook `/health` endpoint when failing | > 0 | | 64 | | `HEALTH_CHECK_SUCCESS_THRESHOLD` | Minimum number of consecutive successful calls to the provider webhook `/health` endpoint before it is marked up again | > 0 | | 10 | diff --git a/developer-docs/content-publishing/ENVIRONMENT.md b/developer-docs/content-publishing/ENVIRONMENT.md index 337479cf..f82e3a05 100644 --- a/developer-docs/content-publishing/ENVIRONMENT.md +++ b/developer-docs/content-publishing/ENVIRONMENT.md @@ -15,6 +15,7 @@ This application recognizes the following environment variables: | `FILE_UPLOAD_MAX_SIZE_IN_BYTES` | Max file size (in bytes) allowed for asset upload | > 0 | Y | | | `FILE_UPLOAD_COUNT_LIMIT` | Max number of files to be able to upload at the same time via one upload call | > 0 | Y | | | `FREQUENCY_API_WS_URL` | Blockchain API Websocket URL | ws(s): URL | Y | | +| `FREQUENCY_TIMEOUT_SECS` | Frequency chain connection timeout limit; app will terminate if disconnected longer | integer | | 10 | | `IPFS_BASIC_AUTH_SECRET` | If using Infura, put auth token here, or leave blank for Kubo RPC | string | | blank | | `IPFS_BASIC_AUTH_USER` | If using Infura, put Project ID here, or leave blank for Kubo RPC | string | | blank | | `IPFS_ENDPOINT` | URL to IPFS endpoint | URL | Y | | diff --git a/developer-docs/content-publishing/README.md b/developer-docs/content-publishing/README.md index 11b37f9c..216ca90c 100644 --- a/developer-docs/content-publishing/README.md +++ b/developer-docs/content-publishing/README.md @@ -142,7 +142,7 @@ Ensure you have the following installed: Run the test: ```bash -npm test:content-publising +npm test:content-publishing ``` Run E2E tests: diff --git a/developer-docs/content-watcher/ENVIRONMENT.md b/developer-docs/content-watcher/ENVIRONMENT.md index 9db14cc0..6b6656e8 100644 --- a/developer-docs/content-watcher/ENVIRONMENT.md +++ b/developer-docs/content-watcher/ENVIRONMENT.md @@ -9,6 +9,7 @@ This application recognizes the following environment variables: | `BLOCKCHAIN_SCAN_INTERVAL_SECONDS` | How many seconds to delay between successive scans of the chain for new content (after end of chain is reached) | > 0 | | 12 | | `CACHE_KEY_PREFIX` | Prefix to use for Redis cache keys | string | | content-watcher: | | `FREQUENCY_API_WS_URL` | Blockchain API Websocket URL | ws(s): URL | Y | | +| `FREQUENCY_TIMEOUT_SECS` | Frequency chain connection timeout limit; app will terminate if disconnected longer | integer | | 10 | | `IPFS_BASIC_AUTH_SECRET` | If required for read requests, put Infura auth token here, or leave blank for default Kubo RPC | string | N | blank | | `IPFS_BASIC_AUTH_USER` | If required for read requests, put Infura Project ID here, or leave blank for default Kubo RPC | string | N | blank | | `IPFS_ENDPOINT` | URL to IPFS endpoint | URL | Y | | diff --git a/developer-docs/graph/ENVIRONMENT.md b/developer-docs/graph/ENVIRONMENT.md index 3011da88..a8fffaf0 100644 --- a/developer-docs/graph/ENVIRONMENT.md +++ b/developer-docs/graph/ENVIRONMENT.md @@ -11,6 +11,7 @@ This application recognizes the following environment variables: | `CAPACITY_LIMIT` | Maximum amount of provider capacity this app is allowed to use (per epoch) type: 'percentage' 'amount' value: number (may be percentage, ie '80', or absolute amount of capacity) | JSON [(example)](https://github.com/ProjectLibertyLabs/gateway/blob/main/env-files/graph.template.env) | Y | | | `DEBOUNCE_SECONDS` | Number of seconds to retain pending graph updates in the Redis cache to avoid redundant fetches from the chain | >= 0 | | | | `FREQUENCY_API_WS_URL` | Blockchain API Websocket URL | ws(s): URL | Y | | +| `FREQUENCY_TIMEOUT_SECS` | Frequency chain connection timeout limit; app will terminate if disconnected longer | integer | | 10 | | `GRAPH_ENVIRONMENT_TYPE` | Graph environment type. | Mainnet\|TestnetPaseo | Y | | | `WEBHOOK_FAILURE_THRESHOLD` | Number of retry attempts to make when sending a webhook notification | > 0 | | 3 | | `WEBHOOK_RETRY_INTERVAL_SECONDS` | Number of seconds between provider webhook retry attempts when failing | > 0 | | 10 | diff --git a/docs/src/Run/Performance.md b/docs/src/Run/Performance.md deleted file mode 100644 index 3aa563d0..00000000 --- a/docs/src/Run/Performance.md +++ /dev/null @@ -1,3 +0,0 @@ -# Performance - -Coming soon... diff --git a/docs/src/Run/Scalability.md b/docs/src/Run/Scalability.md index b4d3f96d..197fea8c 100644 --- a/docs/src/Run/Scalability.md +++ b/docs/src/Run/Scalability.md @@ -1,3 +1,169 @@ -# Scalability +# **Scalability Guide for Frequency Gateway** -Coming soon... +This guide explains how to configure and manage scalability using Kubernetes Horizontal Pod Autoscaler (HPA) for Frequency Gateway to ensure services scale dynamically based on resource usage. + +--- + +## **Table of Contents** + +- [**Scalability Guide for Frequency Gateway**](#scalability-guide-for-frequency-gateway) + - [**Table of Contents**](#table-of-contents) + - [**Introduction**](#introduction) + - [**Prerequisites**](#prerequisites) + - [**Configuring Horizontal Pod Autoscaler**](#configuring-horizontal-pod-autoscaler) + - [**Default Autoscaling Settings**](#default-autoscaling-settings) + - [**Metrics for Autoscaling**](#metrics-for-autoscaling) + - [**Sample Configuration**](#sample-configuration) + - [**Resource Limits**](#resource-limits) + - [**Verifying and Monitoring Autoscaling**](#verifying-and-monitoring-autoscaling) + - [**Troubleshooting**](#troubleshooting) + - [HPA is Not Scaling Pods](#hpa-is-not-scaling-pods) + - [Scaling Too Slowly or Too Aggressively](#scaling-too-slowly-or-too-aggressively) + +--- + +## **Introduction** + +Kubernetes Horizontal Pod Autoscaler (HPA) helps scale your deployment based on real-time resource usage (such as CPU and memory). By configuring HPA for the Frequency Gateway, you ensure your services remain available and responsive under varying loads--scaling out when demand increases and scaling down when resources aren't needed. + +--- + +## **Prerequisites** + +Before implementing autoscaling, ensure that: + +- Kubernetes metrics server (or another resource metrics provider) is enabled and running. +- [**Helm**](https://helm.sh/docs/intro/install/) installed for managing Kubernetes applications. +- The deployment for [**Frequency Gateway**](https://github.com/ProjectLibertyLabs/gateway/blob/main/deployment/k8s) is running in your Kubernetes cluster. + +--- + +## **Configuring Horizontal Pod Autoscaler** + +### **Default Autoscaling Settings** + +In `values.yaml`, autoscaling is controlled with the following parameters: + +```yaml +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 70 +``` + +- **enabled**: Enable or disable autoscaling. +- **minReplicas**: Minimum number of pod replicas. +- **maxReplicas**: Maximum number of pod replicas. +- **targetCPUUtilizationPercentage**: Average CPU utilization target for triggering scaling. +- **targetMemoryUtilizationPercentage**: Average memory utilization target for triggering scaling. + +--- + +### **Metrics for Autoscaling** + +The Kubernetes HPA uses real-time resource consumption to determine whether to increase or decrease the number of pods. Metrics commonly used include: + +- **CPU utilization**: Scaling based on CPU usage. +- **Memory utilization**: Scaling based on memory consumption. + +You can configure one or both, depending on your resource needs. + +--- + +### **Sample Configuration** + +Here is an example `values.yaml` configuration for enabling autoscaling with CPU and memory targets: + +```yaml +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 75 +``` + +This setup will ensure the following: + +- The number of pod replicas will never go below 2 or above 10. +- Kubernetes will attempt to keep CPU usage around 70% across all pods. +- Kubernetes will attempt to keep memory usage around 75% across all pods. + +--- + +## **Resource Limits** + +Setting resource limits ensures your pods are scheduled appropriately and have the necessary resources to function efficiently. Define limits and requests in the `values.yaml` like this: + +```yaml +resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" +``` + +- **requests**: The minimum CPU and memory a pod needs. +- **limits**: The maximum CPU and memory a pod can use. + +Setting these values ensures that the HPA scales the pods without overloading the system. + +--- + +## **Verifying and Monitoring Autoscaling** + +Once you've enabled autoscaling, you can monitor it using `kubectl`: + +```bash +kubectl get hpa +``` + +This will output the current state of the HPA, including current replicas, target utilization, and actual resource usage. + +To see the pods scaling in real-time: + +```bash +kubectl get pods -w +``` + +You can also inspect specific metrics with: + +```bash +kubectl top pods +``` + +--- + +## **Troubleshooting** + +### HPA is Not Scaling Pods + +If the HPA doesn't seem to be scaling as expected, check the following: + +1. **Metrics Server**: Ensure the metrics server is running properly by checking: + + ```bash + kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" + ``` + + If this command fails, the metrics server might not be installed or working correctly. + +2. **HPA Status**: Describe the HPA resource to inspect events and scaling behavior: + + ```bash + kubectl describe hpa frequency-gateway + ``` + +3. **Resource Requests**: Ensure that the `resources.requests` are defined in your deployment configuration. HPA relies on these to scale based on resource consumption. + +### Scaling Too Slowly or Too Aggressively + +If your services are scaling too slowly or too aggressively, consider adjusting the `targetCPUUtilizationPercentage` or `targetMemoryUtilizationPercentage` values. + +--- + +By following this guide, you will have a solid understanding of how to configure Kubernetes autoscaling for your Frequency Gateway services, ensuring they adapt dynamically to workload demands. diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index c60ef224..3372feb1 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -33,7 +33,6 @@ - [Ingress](./Run/Nginx.md) - [Secret Management](./Run/Vault.md) - [Security](./Run/Security.md) - - [Performance](./Run/Performance.md) - [Scalability](./Run/Scalability.md) --- diff --git a/env-files/account.template.env b/env-files/account.template.env index 84118663..fc96b88d 100644 --- a/env-files/account.template.env +++ b/env-files/account.template.env @@ -7,6 +7,9 @@ API_PORT=3000 FREQUENCY_API_WS_URL=ws://0.0.0.0:9944 #FREQUENCY_API_WS_URL=ws://frequency:9944 #docker dev +# Timeout value (seconds) for connection to chain +FREQUENCY_TIMEOUT_SECS=10 + # Blockchain node address resolvable from the client browser, used for SIWF SIWF_NODE_RPC_URL=http://0.0.0.0:9944 #SIWF_NODE_RPC_URL=http://frequency:9944 #docker dev diff --git a/env-files/content-publishing.template.env b/env-files/content-publishing.template.env index 21b51ee7..90e5d382 100644 --- a/env-files/content-publishing.template.env +++ b/env-files/content-publishing.template.env @@ -24,6 +24,9 @@ IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" # Blockchain node address, used to access the blockchain API FREQUENCY_API_WS_URL=ws://0.0.0.0:9944 +# Timeout value (seconds) for connection to chain +FREQUENCY_TIMEOUT_SECS=10 + PROVIDER_ID=1 # Redis URL REDIS_URL=redis://0.0.0.0:6379 diff --git a/env-files/content-watcher.template.env b/env-files/content-watcher.template.env index e2fce6a8..0e464946 100644 --- a/env-files/content-watcher.template.env +++ b/env-files/content-watcher.template.env @@ -17,6 +17,9 @@ IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" # Blockchain node address, used to access the blockchain API FREQUENCY_API_WS_URL=ws://0.0.0.0:9944 +# Timeout value (seconds) for connection to chain +FREQUENCY_TIMEOUT_SECS=10 + # Redis URL REDIS_URL=redis://0.0.0.0:6379 @@ -44,4 +47,4 @@ CACHE_KEY_PREFIX=content-watcher: API_TIMEOUT_MS=5000 # Api json body size limit in string (some examples: 100kb or 5mb or etc) -API_BODY_JSON_LIMIT=1mb \ No newline at end of file +API_BODY_JSON_LIMIT=1mb diff --git a/env-files/graph.template.env b/env-files/graph.template.env index 402e2b6a..d362bea7 100644 --- a/env-files/graph.template.env +++ b/env-files/graph.template.env @@ -6,6 +6,9 @@ API_PORT=3000 # Blockchain node address, used to access the blockchain API FREQUENCY_API_WS_URL=ws://0.0.0.0:9944 +# Timeout value (seconds) for connection to chain +FREQUENCY_TIMEOUT_SECS=10 + # Specifies the provider ID PROVIDER_ID=1 diff --git a/libs/account-lib/src/utils/blockchain-scanner.service.spec.ts b/libs/account-lib/src/utils/blockchain-scanner.service.spec.ts index 785801e9..40d4acf5 100644 --- a/libs/account-lib/src/utils/blockchain-scanner.service.spec.ts +++ b/libs/account-lib/src/utils/blockchain-scanner.service.spec.ts @@ -42,20 +42,8 @@ const mockBlockchainService = { getBlock: jest.fn((_blockHash?: string | Hash) => mockSignedBlock as unknown as SignedBlock), getBlockHash: jest.fn((blockNumber: number) => (blockNumber > 1 ? mockEmptyBlockHash : mockBlockHash)), getLatestFinalizedBlockNumber: jest.fn(), + getEvents: jest.fn(() => []), }; -Object.defineProperty(mockBlockchainService, 'api', { - get: jest.fn(() => ({ - at: jest.fn(() => ({ - query: { - system: { - events: jest.fn(() => ({ - toArray: jest.fn(() => []), - })), - }, - }, - })), - })), -}); const mockBlockchainServiceProvider = { provide: BlockchainService, diff --git a/libs/account-lib/src/utils/blockchain-scanner.service.ts b/libs/account-lib/src/utils/blockchain-scanner.service.ts index 1698101a..f7f4567c 100644 --- a/libs/account-lib/src/utils/blockchain-scanner.service.ts +++ b/libs/account-lib/src/utils/blockchain-scanner.service.ts @@ -79,8 +79,7 @@ export abstract class BlockchainScannerService { while (true) { await this.checkScanParameters(currentBlockNumber, currentBlockHash); // throws when end-of-chain reached const block = await this.blockchainService.getBlock(currentBlockHash); - const at = await this.blockchainService.api.at(currentBlockHash); - const blockEvents = (await at.query.system.events()).toArray(); + const blockEvents = await this.blockchainService.getEvents(currentBlockHash); await this.handleChainEvents(block, blockEvents); await this.processCurrentBlock(block, blockEvents); await this.setLastSeenBlockNumber(currentBlockNumber); diff --git a/libs/blockchain/src/blockchain-rpc-query.service.ts b/libs/blockchain/src/blockchain-rpc-query.service.ts index e1b9d9a5..af555350 100644 --- a/libs/blockchain/src/blockchain-rpc-query.service.ts +++ b/libs/blockchain/src/blockchain-rpc-query.service.ts @@ -1,17 +1,17 @@ /* eslint-disable new-cap */ /* eslint-disable no-underscore-dangle */ -import { Inject, Injectable, Logger, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common'; -import { options } from '@frequency-chain/api-augment'; -import { ApiPromise, HttpProvider, WsProvider } from '@polkadot/api'; +import { Inject, Injectable } from '@nestjs/common'; import { AccountId, AccountId32, BlockHash, BlockNumber, Event, SignedBlock } from '@polkadot/types/interfaces'; -import { SubmittableExtrinsic } from '@polkadot/api/types'; +import { ApiDecoration, SubmittableExtrinsic } from '@polkadot/api/types'; import { AnyNumber, Codec, DetectCodec, ISubmittableResult, SignerPayloadRaw } from '@polkadot/types/types'; import { Bytes, Option, u128, u16, Vec } from '@polkadot/types'; import { CommonPrimitivesMsaDelegation, CommonPrimitivesMsaProviderRegistryEntry, + FrameSystemEventRecord, PalletCapacityCapacityDetails, PalletSchemasSchemaInfo, + PalletSchemasSchemaVersionId, } from '@polkadot/types/lookup'; import { ItemizedStoragePageResponse, @@ -36,7 +36,10 @@ import { hexToU8a } from '@polkadot/util'; import { decodeAddress } from '@polkadot/util-crypto'; import { chainDelegationToNative } from '#types/interfaces/account/delegations.interface'; import { TransactionType } from '#types/account-webhook'; -import blockchainConfig, { IBlockchainNonProviderConfig } from './blockchain.config'; +import { IBlockchainNonProviderConfig, noProviderBlockchainConfig } from './blockchain.config'; +import { PolkadotApiService } from './polkadot-api.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ApiPromise } from '@polkadot/api'; export type Sr25519Signature = { Sr25519: HexString }; export type NetworkType = 'mainnet' | 'testnet-paseo' | 'unknown'; @@ -69,62 +72,23 @@ export interface ICapacityInfo { currentEpoch: number; } -@Injectable() -export class BlockchainRpcQueryService implements OnApplicationBootstrap, OnApplicationShutdown { - public api: ApiPromise; - - protected readonly logger: Logger; - - private baseReadyResolve: (arg: boolean) => void; - - private baseReadyReject: (reason: any) => void; - - protected readonly baseIsReadyPromise = new Promise((resolve, reject) => { - this.baseReadyResolve = resolve; - this.baseReadyReject = reject; - }); - - public async onApplicationBootstrap() { - const providerUrl = this.baseConfig.frequencyApiWsUrl.toString(); - let provider: WsProvider | HttpProvider; - try { - if (/^ws/.test(providerUrl)) { - provider = new WsProvider(providerUrl); - } else if (/^http/.test(providerUrl)) { - provider = new HttpProvider(providerUrl); - } else { - this.logger.error(`Unrecognized chain URL type: ${providerUrl}`); - throw new Error('Unrecognized chain URL type'); - } - this.api = await ApiPromise.create({ provider, ...options }).then((api) => api.isReady); - this.baseReadyResolve(!!(await this.api.isReady)); - this.logger.log('Blockchain API ready.'); - } catch (err) { - this.baseReadyReject(err); - throw err; - } - } - - public async isReady(): Promise { - return (await this.baseIsReadyPromise) && !!(await this.api.isReady); - } - - public async getApi(): Promise { - await this.api.isReady; - return this.api; - } +export interface IBlockPaginationRequest { + readonly from_block: number; + readonly from_index: number; + readonly to_block: number; + readonly page_size: number; +} - public async onApplicationShutdown(_signal?: string | undefined) { - const promises: Promise[] = []; - if (this.api) { - promises.push(this.api.disconnect()); - } - await Promise.all(promises); +@Injectable() +export class BlockchainRpcQueryService extends PolkadotApiService { + constructor( + @Inject(noProviderBlockchainConfig.KEY) baseConfig: IBlockchainNonProviderConfig, + eventEmitter: EventEmitter2, + ) { + super(baseConfig, eventEmitter); } - constructor(@Inject(blockchainConfig.KEY) private readonly baseConfig: IBlockchainNonProviderConfig) { - this.logger = new Logger(this.constructor.name); - } + // ******************************** RPC methods ******************************** public getBlockHash(block: BlockNumber | AnyNumber): Promise { return this.api.rpc.chain.getBlockHash(block); @@ -171,23 +135,123 @@ export class BlockchainRpcQueryService implements OnApplicationBootstrap, OnAppl return undefined; } - public createType( - type: K, - ...params: unknown[] - ): DetectCodec { - return this.api.createType(type, ...params); - } - public async getNonce(account: string | Uint8Array | AccountId): Promise { return (await this.api.rpc.system.accountNextIndex(account)).toNumber(); } - public async getSchema(schemaId: AnyNumber): Promise { - return this.handleOptionResult(this.api.query.schemas.schemaInfos(schemaId)); + public async getKeysByMsa(msaId: string): Promise { + return this.handleOptionResult(this.api.rpc.msa.getKeysByMsaId(msaId), `No keys found for msaId: ${msaId}`); + } + + public async getHandleForMsa(msaId: AnyNumber): Promise { + const handleResponse = await this.handleOptionResult( + this.api.rpc.handles.getHandleForMsa(msaId), + `getHandleForMsa: No handle found for msaId: ${msaId}`, + ); + + return handleResponse + ? { + base_handle: handleResponse.base_handle.toString(), + canonical_base: handleResponse.canonical_base.toString(), + suffix: handleResponse.suffix.toNumber(), + } + : null; + } + + public async getCommonPrimitivesMsaDelegation( + msaId: AnyNumber, + providerId: AnyNumber, + ): Promise { + return this.handleOptionResult(this.api.query.msa.delegatorAndProviderToDelegation(msaId, providerId)); + } + + public async getProviderDelegationForMsa(msaId: AnyNumber, providerId: AnyNumber): Promise { + const response = await this.handleOptionResult( + this.api.query.msa.delegatorAndProviderToDelegation(msaId, providerId), + ); + return response ? chainDelegationToNative(providerId, response) : null; + } + + public async getDelegationsForMsa(msaId: AnyNumber): Promise { + const response = (await this.api.query.msa.delegatorAndProviderToDelegation.entries(msaId)) + .filter(([_, entry]) => entry.isSome) + .map(([key, value]) => chainDelegationToNative(key.args[1], value.unwrap())); + + return response; + } + + public async getProviderToRegistryEntry( + providerId: AnyNumber, + ): Promise { + const providerRegistry = await this.api.query.msa.providerToRegistryEntry(providerId); + if (providerRegistry.isSome) return providerRegistry.unwrap(); + return null; + } + + public async publicKeyToMsaId(publicKey: string | Uint8Array | AccountId32): Promise { + const response = await this.handleOptionResult(this.api.query.msa.publicKeyToMsaId(publicKey)); + return response ? response.toString() : null; + } + + public async capacityInfo(providerId: AnyNumber, blockHash?: Uint8Array | string): Promise { + await this.isReady(); + const api = await this.getApi(blockHash); + const epochStart = await this.getCurrentCapacityEpochStart(blockHash); + const epochBlockLength = await this.getCurrentEpochLength(blockHash); + const capacityDetailsOption: Option = + await api.query.capacity.capacityLedger(providerId); + const { remainingCapacity, totalCapacityIssued } = capacityDetailsOption.unwrapOr({ + remainingCapacity: new u128(this.api.registry, 0), + totalCapacityIssued: new u128(this.api.registry, 0), + }); + const currentBlockNumber = (await api.query.system.number()).toNumber(); + const currentEpoch = await this.getCurrentCapacityEpoch(); + return { + currentEpoch, + providerId: providerId.toString(), + currentBlockNumber, + nextEpochStart: epochStart + epochBlockLength, + remainingCapacity: remainingCapacity.toBigInt(), + totalCapacityIssued: totalCapacityIssued.toBigInt(), + }; + } + + public async getCurrentCapacityEpoch(blockHash?: Uint8Array | string): Promise { + const api = await this.getApi(blockHash); + return (await api.query.capacity.currentEpoch()).toNumber(); + } + + public async getCurrentCapacityEpochStart(blockHash?: Uint8Array | string): Promise { + const api = await this.getApi(blockHash); + return (await api.query.capacity.currentEpochInfo()).epochStart.toNumber(); } - public async getSchemaIdByName(schemaNamespace: string, schemaDescriptor: string): Promise { - const { ids }: { ids: Vec } = await this.api.query.schemas.schemaNameToIds(schemaNamespace, schemaDescriptor); + public async getCurrentEpochLength(blockHash?: Uint8Array | string): Promise { + const api = await this.getApi(blockHash); + return (await api.query.capacity.epochLength()).toNumber(); + } + + public async getMessagesBySchemaId(schemaId: AnyNumber, pagination: IBlockPaginationRequest) { + return this.api.rpc.messages.getBySchemaId(schemaId, pagination); + } + + // ********************************* Query methods (accept optional block hash for "at" queries) ********************************* + + public async getSchema( + schemaId: AnyNumber, + blockHash?: Uint8Array | string, + ): Promise { + const api = await this.getApi(blockHash); + return this.handleOptionResult(api.query.schemas.schemaInfos(schemaId)); + } + + public async getSchemaIdByName( + schemaNamespace: string, + schemaDescriptor: string, + blockHash?: Uint8Array | string, + ): Promise { + const api = await this.getApi(blockHash); + const { ids }: { ids: Vec } = await api.query.schemas.schemaNameToIds(schemaNamespace, schemaDescriptor); const schemaId = ids.toArray().pop()?.toNumber(); if (!schemaId) { throw new Error(`Unable to determine schema ID for "${schemaNamespace}.${schemaDescriptor}"`); @@ -196,8 +260,24 @@ export class BlockchainRpcQueryService implements OnApplicationBootstrap, OnAppl return schemaId; } - public async getSchemaPayload(schemaId: AnyNumber): Promise { - return this.handleOptionResult(this.api.query.schemas.schemaPayloads(schemaId)); + public async getSchemaPayload(schemaId: AnyNumber, blockHash?: Uint8Array | string): Promise { + const api = await this.getApi(blockHash); + return this.handleOptionResult(api.query.schemas.schemaPayloads(schemaId)); + } + + public async getSchemaNamesToIds(args: [namespace: string, name: string][]) { + const versions = await this.api.query.schemas.schemaNameToIds.multi(args); + return versions.map((schemaVersions: PalletSchemasSchemaVersionId, index) => ({ + name: args[index], + ids: schemaVersions.ids.map((version) => version.toNumber()), + })); + } + + public async getMessageKeysInBlock(blockNumber: AnyNumber): Promise<[number, number][]> { + return (await this.api.query.messages.messagesV2.keys(blockNumber)).map((key) => { + const [_, schemaId, index] = key.args; + return [schemaId.toNumber(), index.toNumber()]; + }); } /** @@ -210,17 +290,30 @@ export class BlockchainRpcQueryService implements OnApplicationBootstrap, OnAppl * * @returns {bigint} The current maximum MSA Id from the chain */ - public async getMsaIdMax(): Promise { - return (await this.api.query.msa.currentMsaIdentifierMaximum()).toBigInt(); + public async getMsaIdMax(blockHash?: Uint8Array | string): Promise { + const api = await this.getApi(blockHash); + return (await api.query.msa.currentMsaIdentifierMaximum()).toBigInt(); } - public async isValidMsaId(msaId: string): Promise { - const msaIdMax = await this.getMsaIdMax(); + public async isValidMsaId(msaId: string, blockHash?: Uint8Array | string): Promise { + const msaIdMax = await this.getMsaIdMax(blockHash); return BigInt(msaId) > 0n && BigInt(msaId) <= msaIdMax; } - public async getKeysByMsa(msaId: string): Promise { - return this.handleOptionResult(this.api.rpc.msa.getKeysByMsaId(msaId), `No keys found for msaId: ${msaId}`); + // ********************************* Transaction/Payload/Type methods ********************************* + + public get events() { + return this.api.events; + } + + public async getEvents(blockHash?: Uint8Array | string): Promise { + // eslint-disable-next-line prefer-destructuring + let api: ApiPromise | ApiDecoration<'promise'> = this.api; + if (blockHash) { + api = await this.api.at(blockHash); + } + + return (await api.query.system.events()).toArray(); } public async addPublicKeyToMsa(keysRequest: KeysRequestDto): Promise> { @@ -362,90 +455,6 @@ export class BlockchainRpcQueryService implements OnApplicationBootstrap, OnAppl } } - public async getHandleForMsa(msaId: AnyNumber): Promise { - const handleResponse = await this.handleOptionResult( - this.api.rpc.handles.getHandleForMsa(msaId), - `getHandleForMsa: No handle found for msaId: ${msaId}`, - ); - - return handleResponse - ? { - base_handle: handleResponse.base_handle.toString(), - canonical_base: handleResponse.canonical_base.toString(), - suffix: handleResponse.suffix.toNumber(), - } - : null; - } - - public async getCommonPrimitivesMsaDelegation( - msaId: AnyNumber, - providerId: AnyNumber, - ): Promise { - return this.handleOptionResult(this.api.query.msa.delegatorAndProviderToDelegation(msaId, providerId)); - } - - public async getProviderDelegationForMsa(msaId: AnyNumber, providerId: AnyNumber): Promise { - const response = await this.handleOptionResult( - this.api.query.msa.delegatorAndProviderToDelegation(msaId, providerId), - ); - return response ? chainDelegationToNative(providerId, response) : null; - } - - public async getDelegationsForMsa(msaId: AnyNumber): Promise { - const response = (await this.api.query.msa.delegatorAndProviderToDelegation.entries(msaId)) - .filter(([_, entry]) => entry.isSome) - .map(([key, value]) => chainDelegationToNative(key.args[1], value.unwrap())); - - return response; - } - - public async getProviderToRegistryEntry( - providerId: AnyNumber, - ): Promise { - const providerRegistry = await this.api.query.msa.providerToRegistryEntry(providerId); - if (providerRegistry.isSome) return providerRegistry.unwrap(); - return null; - } - - public async publicKeyToMsaId(publicKey: string | Uint8Array | AccountId32): Promise { - const response = await this.handleOptionResult(this.api.query.msa.publicKeyToMsaId(publicKey)); - return response ? response.toString() : null; - } - - public async capacityInfo(providerId: AnyNumber): Promise { - await this.isReady(); - const epochStart = await this.getCurrentCapacityEpochStart(); - const epochBlockLength = await this.getCurrentEpochLength(); - const capacityDetailsOption: Option = - await this.api.query.capacity.capacityLedger(providerId); - const { remainingCapacity, totalCapacityIssued } = capacityDetailsOption.unwrapOr({ - remainingCapacity: new u128(this.api.registry, 0), - totalCapacityIssued: new u128(this.api.registry, 0), - }); - const currentBlockNumber = (await this.api.query.system.number()).toNumber(); - const currentEpoch = await this.getCurrentCapacityEpoch(); - return { - currentEpoch, - providerId: providerId.toString(), - currentBlockNumber, - nextEpochStart: epochStart + epochBlockLength, - remainingCapacity: remainingCapacity.toBigInt(), - totalCapacityIssued: totalCapacityIssued.toBigInt(), - }; - } - - public async getCurrentCapacityEpoch(): Promise { - return (await this.api.query.capacity.currentEpoch()).toNumber(); - } - - public async getCurrentCapacityEpochStart(): Promise { - return (await this.api.query.capacity.currentEpochInfo()).epochStart.toNumber(); - } - - public async getCurrentEpochLength(): Promise { - return (await this.api.query.capacity.epochLength()).toNumber(); - } - /** * Handles the publish handle transaction result events and extracts the handle and msaId from the event data. * @param event - The HandleClaimed event @@ -614,4 +623,11 @@ export class BlockchainRpcQueryService implements OnApplicationBootstrap, OnAppl return 'unknown'; } } + + public createType( + type: K, + ...params: unknown[] + ): DetectCodec { + return this.api.createType(type, ...params); + } } diff --git a/libs/blockchain/src/blockchain.config.spec.ts b/libs/blockchain/src/blockchain.config.spec.ts index e26a7a88..65a08dd2 100644 --- a/libs/blockchain/src/blockchain.config.spec.ts +++ b/libs/blockchain/src/blockchain.config.spec.ts @@ -8,6 +8,7 @@ const { setupConfigService: setupConfigServiceReadOnly } = configSetup { const ALL_ENV: { [key: string]: string | undefined } = { + FREQUENCY_TIMEOUT_SECS: undefined, FREQUENCY_API_WS_URL: undefined, PROVIDER_ACCOUNT_SEED_PHRASE: undefined, PROVIDER_ID: undefined, @@ -69,6 +70,10 @@ describe('Blockchain module config', () => { expect(blockchainConf).toBeDefined(); }); + it('should get frequency timeout', () => { + expect(blockchainConf.frequencyTimeoutSecs).toStrictEqual(parseInt(ALL_ENV.FREQUENCY_TIMEOUT_SECS, 10)); + }); + it('should get frequency API web socket url', () => { const expectedUrl = new URL(ALL_ENV.FREQUENCY_API_WS_URL).toString(); expect(blockchainConf.frequencyApiWsUrl?.toString()).toStrictEqual(expectedUrl); diff --git a/libs/blockchain/src/blockchain.config.ts b/libs/blockchain/src/blockchain.config.ts index c99032e1..b7eeb53f 100644 --- a/libs/blockchain/src/blockchain.config.ts +++ b/libs/blockchain/src/blockchain.config.ts @@ -7,6 +7,7 @@ import { cryptoWaitReady } from '@polkadot/util-crypto'; import Keyring from '@polkadot/keyring'; export interface IBlockchainNonProviderConfig { + frequencyTimeoutSecs: number; frequencyApiWsUrl: URL; isDeployedReadOnly: boolean; } @@ -76,6 +77,10 @@ const doRegister = (mode: ChainMode = ChainMode.PROVIDER_SEED_REQUIRED) => } const configs: JoiUtil.JoiConfig = { + frequencyTimeoutSecs: { + value: process.env.FREQUENCY_TIMEOUT_SECS, + joi: Joi.number().positive().default(60), + }, frequencyApiWsUrl: { value: process.env.FREQUENCY_API_WS_URL, joi: Joi.string() diff --git a/libs/blockchain/src/blockchain.service.spec.ts b/libs/blockchain/src/blockchain.service.spec.ts index f0c502de..b4fa8014 100644 --- a/libs/blockchain/src/blockchain.service.spec.ts +++ b/libs/blockchain/src/blockchain.service.spec.ts @@ -1,7 +1,6 @@ /* eslint-disable class-methods-use-this */ /* eslint-disable import/no-extraneous-dependencies */ import { describe, it, jest, expect } from '@jest/globals'; -import { ApiPromise } from '@polkadot/api'; import { Test } from '@nestjs/testing'; import { BlockchainService, NONCE_SERVICE_REDIS_NAMESPACE } from './blockchain.service'; import { IBlockchainConfig } from './blockchain.config'; @@ -10,11 +9,26 @@ import { CommonPrimitivesMsaProviderRegistryEntry } from '@polkadot/types/lookup import { GenerateMockConfigProvider } from '#testlib/utils.config-tests'; import { getRedisToken } from '@songkeys/nestjs-redis'; import { Provider } from '@nestjs/common'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { mockApiPromise } from '#testlib/polkadot-api.mock.spec'; + +jest.mock('@polkadot/api', () => { + const originalModule = jest.requireActual('@polkadot/api'); + return { + __esModules: true, + WsProvider: jest.fn().mockImplementation(() => originalModule.WsProvider), + ApiPromise: jest.fn().mockImplementation(() => ({ + ...originalModule.ApiPromise, + ...mockApiPromise, + })), + }; +}); const mockBlockchainConfigProvider = GenerateMockConfigProvider('blockchain', { capacityLimit: { serviceLimit: { type: 'percentage', value: 80n } }, providerId: 1n, providerSeedPhrase: '//Alice', + frequencyTimeoutSecs: 10, frequencyApiWsUrl: new URL('ws://localhost:9944'), isDeployedReadOnly: false, }); @@ -30,32 +44,43 @@ describe('BlockchainService', () => { let mockApi: any; let blockchainService: BlockchainService; let blockchainConf: IBlockchainConfig; - const mockGenesisHashHex = jest.fn(); beforeAll(async () => { - mockApi = { - createType: jest.fn(), - query: { - capacity: { - currentEpochInfo: jest.fn(), - }, - }, - genesisHash: { - toHex: mockGenesisHashHex, - }, - } as unknown as ApiPromise; - + // const foo = await import('@polkadot/api'); + // console.log(foo); const moduleRef = await Test.createTestingModule({ - imports: [], + imports: [ + EventEmitterModule.forRoot({ + // Use this instance throughout the application + global: true, + // set this to `true` to use wildcards + wildcard: false, + // the delimiter used to segment namespaces + delimiter: '.', + // set this to `true` if you want to emit the newListener event + newListener: false, + // set this to `true` if you want to emit the removeListener event + removeListener: false, + // the maximum amount of listeners that can be assigned to an event + maxListeners: 10, + // show event name in memory leak message when more than maximum amount of listeners is assigned + verboseMemoryLeak: false, + // disable throwing uncaughtException if an error event is emitted and it has no listeners + ignoreErrors: false, + }), + ], controllers: [], providers: [BlockchainService, mockBlockchainConfigProvider, mockNonceRedisProvider], }).compile(); + moduleRef.enableShutdownHooks(); + blockchainService = moduleRef.get(BlockchainService); - blockchainService.api = mockApi; + mockApi = await blockchainService.getApi(); blockchainConf = moduleRef.get(mockBlockchainConfigProvider.provide); await cryptoWaitReady(); + mockApi.emit('connected'); // keeps the test suite from hanging when finished }); describe('getCurrentCapacityEpochStart', () => { @@ -117,17 +142,21 @@ describe('BlockchainService', () => { }); it('should return mainnet for the mainnet hash', () => { - mockGenesisHashHex.mockReturnValue('0x4a587bf17a404e3572747add7aab7bbe56e805a5479c6c436f07f36fcc8d3ae1'); + jest + .spyOn(mockApi.genesisHash, 'toHex') + .mockReturnValue('0x4a587bf17a404e3572747add7aab7bbe56e805a5479c6c436f07f36fcc8d3ae1'); expect(blockchainService.getNetworkType()).toEqual('mainnet'); }); it('should return testnet for the testnet hash', () => { - mockGenesisHashHex.mockReturnValue('0x203c6838fc78ea3660a2f298a58d859519c72a5efdc0f194abd6f0d5ce1838e0'); + jest + .spyOn(mockApi.genesisHash, 'toHex') + .mockReturnValue('0x203c6838fc78ea3660a2f298a58d859519c72a5efdc0f194abd6f0d5ce1838e0'); expect(blockchainService.getNetworkType()).toEqual('testnet-paseo'); }); it('should return unknown for anything else', () => { - mockGenesisHashHex.mockReturnValue('0xabcd'); + jest.spyOn(mockApi.genesisHash, 'toHex').mockReturnValue('0xabcd'); expect(blockchainService.getNetworkType()).toEqual('unknown'); }); }); diff --git a/libs/blockchain/src/blockchain.service.ts b/libs/blockchain/src/blockchain.service.ts index 1165805e..e4c0ddf8 100644 --- a/libs/blockchain/src/blockchain.service.ts +++ b/libs/blockchain/src/blockchain.service.ts @@ -5,7 +5,7 @@ import { Inject, Injectable, OnApplicationBootstrap } from '@nestjs/common'; import { Keyring } from '@polkadot/api'; import { Call } from '@polkadot/types/interfaces'; import { SubmittableExtrinsic } from '@polkadot/api/types'; -import { IMethod } from '@polkadot/types/types'; +import { IMethod, ISubmittableResult } from '@polkadot/types/types'; import { Vec } from '@polkadot/types'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { HexString } from '@polkadot/util/types'; @@ -14,6 +14,7 @@ import Redis from 'ioredis'; import { InjectRedis } from '@songkeys/nestjs-redis'; import { BlockchainRpcQueryService } from './blockchain-rpc-query.service'; import { NonceConstants } from '#types/constants'; +import { EventEmitter2 } from '@nestjs/event-emitter'; export const NONCE_SERVICE_REDIS_NAMESPACE = 'NonceService'; @@ -59,7 +60,6 @@ export class BlockchainService extends BlockchainRpcQueryService implements OnAp public async onApplicationBootstrap() { try { - await super.onApplicationBootstrap(); await this.baseIsReadyPromise; await this.validateProviderSeedPhrase(); this.accountId = await addressFromSeedPhrase(this.config.providerSeedPhrase); @@ -78,8 +78,9 @@ export class BlockchainService extends BlockchainRpcQueryService implements OnAp constructor( @Inject(blockchainConfig.KEY) private readonly config: IBlockchainConfig, @InjectRedis(NONCE_SERVICE_REDIS_NAMESPACE) private readonly nonceRedis: Redis, + eventEmitter: EventEmitter2, ) { - super(config); + super(config, eventEmitter); if (!this.nonceRedis) throw new Error('Unable to get NonceRedis'); this.nonceRedis.defineCommand('incrementNonce', { numberOfKeys: NUMBER_OF_NONCE_KEYS_TO_CHECK, @@ -193,4 +194,8 @@ export class BlockchainService extends BlockchainRpcQueryService implements OnAp this.logger.debug(`nextNonce ${nextNonce}`); return nextNonce; } + + public createTxFromEncoded(encodedTx: any): SubmittableExtrinsic<'promise', ISubmittableResult> { + return this.api.tx(encodedTx); + } } diff --git a/libs/blockchain/src/polkadot-api.service.ts b/libs/blockchain/src/polkadot-api.service.ts new file mode 100644 index 00000000..38aad28d --- /dev/null +++ b/libs/blockchain/src/polkadot-api.service.ts @@ -0,0 +1,136 @@ +import { options } from '@frequency-chain/api-augment'; +import { Logger, OnApplicationShutdown } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { WsProvider, ApiPromise, HttpProvider } from '@polkadot/api'; +import { ApiDecoration, ApiInterfaceEvents } from '@polkadot/api/types'; +import { ProviderInterface } from '@polkadot/rpc-provider/types'; +import { MILLISECONDS_PER_SECOND } from 'time-constants'; +import { IBlockchainNonProviderConfig } from './blockchain.config'; + +export class PolkadotApiService extends EventEmitter2 implements OnApplicationShutdown { + private provider: ProviderInterface; + + protected readonly api: ApiPromise; + + protected readonly logger: Logger; + + // eslint-disable-next-line no-undef + private disconnectedTimeout: NodeJS.Timeout | undefined; + + private apiListeners: [ApiInterfaceEvents, () => void][] = []; + + private baseReadyResolve: (arg: boolean) => void; + + private baseReadyReject: (reason: any) => void; + + protected readonly baseIsReadyPromise = new Promise((resolve, reject) => { + this.baseReadyResolve = resolve; + this.baseReadyReject = reject; + }); + + public async onApplicationShutdown(_signal?: string | undefined) { + this.apiListeners.forEach(([type, listener]) => this.api.off(type, listener)); + + const promises: Promise[] = []; + if (this.api) { + promises.push(this.api.disconnect()); + } + + if (this.provider) { + promises.push(this.provider.disconnect()); + } + await Promise.allSettled(promises); + } + + constructor( + private readonly baseConfig: IBlockchainNonProviderConfig, + private readonly eventEmitter: EventEmitter2, + ) { + super(); + this.logger = new Logger(this.constructor.name); + + try { + const providerUrl = this.baseConfig.frequencyApiWsUrl; + if (/^ws[s]?:/.test(providerUrl.toString())) { + this.provider = new WsProvider(this.baseConfig.frequencyApiWsUrl.toString()); + } else if (/^http[s]?:/.test(providerUrl.toString())) { + this.provider = new HttpProvider(this.baseConfig.frequencyApiWsUrl.toString()); + } else { + throw new Error('Unrecognized chain URL type', { cause: providerUrl.toJSON() }); + } + + this.api = new ApiPromise({ ...options, provider: this.provider, noInitWarn: false }); + // From https://github.com/polkadot-js/api/blob/700812aa6075c85d8306451ce062d8c06b161e2b/packages/api/src/promise/Api.ts#L157C1-L159C1 + // Swallow any rejections on isReadyOrError + // (in Node 15.x this creates issues, when not being looked at) + this.api.isReadyOrError.catch(() => {}); + this.registerApiListener('disconnected', () => this.startDisconnectedTimeout()); + this.registerApiListener('error', () => this.startDisconnectedTimeout()); + this.registerApiListener('connected', () => this.stopDisconnectedTimeout()); + this.registerApiListenerOnce('connected', () => { + this.api.isReady.then(() => this.baseReadyResolve(true)); + }); + this.startDisconnectedTimeout(true); + } catch (err: any) { + this.logger.error({ err, msg: err?.message }); + this.baseReadyReject(err); + } + } + + public registerApiListener(type: ApiInterfaceEvents, listener: () => void) { + this.apiListeners.push([type, listener]); + this.api.on(type, listener); + } + + public registerApiListenerOnce(type: ApiInterfaceEvents, listener: () => void) { + this.apiListeners.push([type, listener]); + this.wrapOnce(type, listener); + } + + private wrapOnce(event: ApiInterfaceEvents, listener: () => void) { + this.api.once(event, () => { + const entry = this.apiListeners.findIndex((l) => l[0] === event && l[1] === listener); + if (entry !== -1) { + this.apiListeners.splice(entry, 1); + } + listener(); + }); + } + + public async getApi(blockHash?: Uint8Array | string): Promise | ApiPromise> { + await this.api.isReady; + return blockHash ? this.api.at(blockHash) : this.api; + } + + public get chainPrefix(): number { + return this.api.consts.system.ss58Prefix.toBn().toNumber(); + } + + public async isReady(): Promise { + return (await this.baseIsReadyPromise) && !!(await this.api.isReady); + } + + private startDisconnectedTimeout(isInit = false) { + if (!this.disconnectedTimeout) { + this.logger[isInit ? 'log' : 'error']( + isInit + ? 'Awaiting Frequency RPC node connection' + : `Communications error with Frequency node; starting ${this.baseConfig.frequencyTimeoutSecs}-second shutdown timer`, + ); + this.disconnectedTimeout = setTimeout(() => { + this.logger.error('Failed to reconnect to Frequency node; terminating application'); + this.eventEmitter.emit('shutdown'); + }, this.baseConfig.frequencyTimeoutSecs * MILLISECONDS_PER_SECOND); + this.emit('chain.disconnected'); + } + } + + private stopDisconnectedTimeout() { + if (this.disconnectedTimeout) { + this.logger.log('Connected to Frequency'); + clearTimeout(this.disconnectedTimeout); + this.disconnectedTimeout = undefined; + this.emit('chain.connected'); + } + } +} diff --git a/libs/content-publishing-lib/src/utils/blockchain-scanner.service.spec.ts b/libs/content-publishing-lib/src/utils/blockchain-scanner.service.spec.ts index 167338e4..6ebc5c7c 100644 --- a/libs/content-publishing-lib/src/utils/blockchain-scanner.service.spec.ts +++ b/libs/content-publishing-lib/src/utils/blockchain-scanner.service.spec.ts @@ -1,11 +1,34 @@ import { Injectable, Logger } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { Hash, SignedBlock } from '@polkadot/types/interfaces'; -import { BlockchainService } from '#blockchain/blockchain.service'; +import { BlockHash, BlockNumber, SignedBlock } from '@polkadot/types/interfaces'; +import { BlockchainRpcQueryService } from '#blockchain/blockchain-rpc-query.service'; import { DEFAULT_REDIS_NAMESPACE, getRedisToken, InjectRedis } from '@songkeys/nestjs-redis'; import { Redis } from 'ioredis'; import { BlockchainScannerService } from './blockchain-scanner.service'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; +import { mockApiPromise } from '#testlib/polkadot-api.mock.spec'; +import { IBlockchainNonProviderConfig } from '#blockchain/blockchain.config'; +import { GenerateMockConfigProvider } from '#testlib/utils.config-tests'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { AnyNumber } from '@polkadot/types/types'; + +jest.mock('@polkadot/api', () => { + const originalModule = jest.requireActual('@polkadot/api'); + return { + __esModules: true, + WsProvider: jest.fn().mockImplementation(() => originalModule.WsProvider), + ApiPromise: jest.fn().mockImplementation(() => ({ + ...originalModule.ApiPromise, + ...mockApiPromise, + })), + }; +}); + +const mockBlockchainConfigProvider = GenerateMockConfigProvider('blockchain', { + frequencyTimeoutSecs: 10, + frequencyApiWsUrl: new URL('ws://localhost:9944'), + isDeployedReadOnly: false, +}); const mockRedis = { provide: getRedisToken(DEFAULT_REDIS_NAMESPACE), @@ -15,7 +38,8 @@ const mockRedis = { const mockBlockHash = { toString: jest.fn(() => '0x1234'), some: () => true, -}; + isEmpty: false, +} as unknown as BlockHash; const mockSignedBlock = { block: { @@ -26,44 +50,15 @@ const mockSignedBlock = { }, }; -Object.defineProperty(mockBlockHash, 'isEmpty', { - get: jest.fn(() => false), -}); - const mockEmptyBlockHash = { toString: jest.fn(() => '0x00000'), some: () => false, -}; -Object.defineProperty(mockEmptyBlockHash, 'isEmpty', { - get: jest.fn(() => true), -}); -const mockBlockchainService = { - getBlock: jest.fn((_blockHash?: string | Hash) => mockSignedBlock as unknown as SignedBlock), - getBlockHash: jest.fn((blockNumber: number) => (blockNumber > 1 ? mockEmptyBlockHash : mockBlockHash)), - getLatestFinalizedBlockNumber: jest.fn(), -}; -Object.defineProperty(mockBlockchainService, 'api', { - get: jest.fn(() => ({ - at: jest.fn(() => ({ - query: { - system: { - events: jest.fn(() => ({ - toArray: jest.fn(() => []), - })), - }, - }, - })), - })), -}); - -const mockBlockchainServiceProvider = { - provide: BlockchainService, - useValue: mockBlockchainService, -}; + isEmpty: true, +} as unknown as BlockHash; @Injectable() class ScannerService extends BlockchainScannerService { - constructor(@InjectRedis() redis: Redis, blockchainService: BlockchainService) { + constructor(@InjectRedis() redis: Redis, blockchainService: BlockchainRpcQueryService) { super(redis, blockchainService, new Logger('ScannerService')); } // eslint-disable-next-line @@ -74,14 +69,49 @@ class ScannerService extends BlockchainScannerService { describe('BlockchainScannerService', () => { let service: ScannerService; - let blockchainService: BlockchainService; + let blockchainService: BlockchainRpcQueryService; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - providers: [mockRedis, Logger, mockBlockchainServiceProvider, ScannerService], + imports: [ + EventEmitterModule.forRoot({ + // Use this instance throughout the application + global: true, + // set this to `true` to use wildcards + wildcard: false, + // the delimiter used to segment namespaces + delimiter: '.', + // set this to `true` if you want to emit the newListener event + newListener: false, + // set this to `true` if you want to emit the removeListener event + removeListener: false, + // the maximum amount of listeners that can be assigned to an event + maxListeners: 10, + // show event name in memory leak message when more than maximum amount of listeners is assigned + verboseMemoryLeak: false, + // disable throwing uncaughtException if an error event is emitted and it has no listeners + ignoreErrors: false, + }), + ], + providers: [mockRedis, mockBlockchainConfigProvider, Logger, BlockchainRpcQueryService, ScannerService], }).compile(); + + moduleRef.enableShutdownHooks(); + service = moduleRef.get(ScannerService); - blockchainService = moduleRef.get(BlockchainService); + blockchainService = moduleRef.get(BlockchainRpcQueryService); + const mockApi: any = await blockchainService.getApi(); + + jest.spyOn(blockchainService, 'getBlock').mockResolvedValue(mockSignedBlock as unknown as SignedBlock); + jest + .spyOn(blockchainService, 'getBlockHash') + .mockImplementation((blockNumber: BlockNumber | AnyNumber) => + Promise.resolve((blockNumber as unknown as number) > 1 ? mockEmptyBlockHash : mockBlockHash), + ); + jest.spyOn(blockchainService, 'getLatestFinalizedBlockNumber'); + jest.spyOn(blockchainService, 'getEvents').mockResolvedValue([]); + + mockApi.emit('connected'); // keeps the test suite from hanging when finished }); describe('scan', () => { diff --git a/libs/content-publishing-lib/src/utils/blockchain-scanner.service.ts b/libs/content-publishing-lib/src/utils/blockchain-scanner.service.ts index 25de641d..e9bb86d9 100644 --- a/libs/content-publishing-lib/src/utils/blockchain-scanner.service.ts +++ b/libs/content-publishing-lib/src/utils/blockchain-scanner.service.ts @@ -3,9 +3,9 @@ import '@frequency-chain/api-augment'; import { Logger } from '@nestjs/common'; import { BlockHash, SignedBlock } from '@polkadot/types/interfaces'; -import { BlockchainService } from '#blockchain/blockchain.service'; import Redis from 'ioredis'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; +import { BlockchainRpcQueryService } from '#blockchain/blockchain-rpc-query.service'; export const LAST_SEEN_BLOCK_NUMBER_KEY = 'lastSeenBlockNumber'; @@ -21,6 +21,8 @@ function eventName({ event: { section, method } }: FrameSystemEventRecord) { } export abstract class BlockchainScannerService { + private scanIsPaused = false; + protected scanInProgress = false; protected chainEventHandlers = new Map< @@ -34,10 +36,24 @@ export abstract class BlockchainScannerService { constructor( protected cacheManager: Redis, - protected readonly blockchainService: BlockchainService, + protected readonly blockchainService: BlockchainRpcQueryService, protected readonly logger: Logger, ) { this.lastSeenBlockNumberKey = `${this.constructor.name}:${LAST_SEEN_BLOCK_NUMBER_KEY}`; + blockchainService.on('chain.connected', () => { + this.paused = false; + }); + blockchainService.on('chain.disconnected', () => { + this.paused = true; + }); + } + + protected get paused() { + return this.scanIsPaused; + } + + private set paused(p: boolean) { + this.scanIsPaused = p; } public get scanParameters() { @@ -76,12 +92,11 @@ export abstract class BlockchainScannerService { this.logger.verbose(`Starting scan from block #${currentBlockNumber}`); // eslint-disable-next-line no-constant-condition - while (true) { + while (!this.paused) { try { await this.checkScanParameters(currentBlockNumber, currentBlockHash); // throws when end-of-chain reached const block = await this.blockchainService.getBlock(currentBlockHash); - const at = await this.blockchainService.api.at(currentBlockHash); - const blockEvents = (await at.query.system.events()).toArray(); + const blockEvents = await this.blockchainService.getEvents(currentBlockHash); await this.handleChainEvents(block, blockEvents); await this.processCurrentBlock(block, blockEvents); } catch (err) { @@ -103,6 +118,10 @@ export abstract class BlockchainScannerService { } this.logger.error(JSON.stringify(e)); + // Don't propagate the error if scan is paused; it's WHY the scan is paused + if (this.paused) { + return; + } throw e; } finally { this.scanInProgress = false; diff --git a/libs/content-watcher-lib/src/utils/chain-event-processor.service.ts b/libs/content-watcher-lib/src/utils/chain-event-processor.service.ts index 7f513861..1d09c783 100644 --- a/libs/content-watcher-lib/src/utils/chain-event-processor.service.ts +++ b/libs/content-watcher-lib/src/utils/chain-event-processor.service.ts @@ -1,13 +1,12 @@ import { Injectable } from '@nestjs/common'; import { ChainWatchOptionsDto } from '#types/dtos/content-watcher/chain.watch.dto'; -import { ApiDecoration } from '@polkadot/api/types'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { Vec } from '@polkadot/types'; import { BlockPaginationResponseMessage, MessageResponse } from '@frequency-chain/api-augment/interfaces'; import { MessageResponseWithSchemaId } from '#types/interfaces/content-watcher/message_response_with_schema_id'; import { createIPFSQueueJob } from '#types/interfaces/content-watcher/ipfs.job.interface'; import { Queue } from 'bullmq'; -import { BlockchainRpcQueryService } from '#blockchain/blockchain-rpc-query.service'; +import { BlockchainRpcQueryService, IBlockPaginationRequest } from '#blockchain/blockchain-rpc-query.service'; @Injectable() export class ChainEventProcessorService { @@ -22,39 +21,38 @@ export class ChainEventProcessorService { if (blockHash.isEmpty) { return []; } - const apiAt = await this.blockchainService.api.at(blockHash); - const events = await apiAt.query.system.events(); - return this.getMessagesFromEvents(apiAt, blockNumber, events, filter); + const events = await this.blockchainService.getEvents(blockHash); + return this.getMessagesFromEvents(blockNumber, events, filter); } private async getMessagesFromEvents( - apiAt: ApiDecoration<'promise'>, blockNumber: number, - events: Vec, + events: FrameSystemEventRecord[], filter?: ChainWatchOptionsDto, ): Promise { - const hasMessages = events.some(({ event }) => apiAt.events.messages.MessagesInBlock.is(event)); + const hasMessages = events.some(({ event }) => this.blockchainService.events.messages.MessagesInBlock.is(event)); if (!hasMessages) { return []; } - const keys = await apiAt.query.messages.messagesV2.keys(blockNumber); - let schemaIds = keys.map((key) => key.args[1].toNumber()); + let schemaIds = (await this.blockchainService.getMessageKeysInBlock(blockNumber)).map(([schemaId, _]) => schemaId); schemaIds = Array.from(new Set(schemaIds)); const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( schemaIds.map(async (schemaId) => { if (filter && filter?.schemaIds?.length > 0 && !filter.schemaIds.includes(schemaId)) { return null; } - let paginationRequest = { + let paginationRequest: IBlockPaginationRequest = { from_block: blockNumber, from_index: 0, page_size: 1000, to_block: blockNumber + 1, }; - let messageResponse: BlockPaginationResponseMessage = - await this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest); + let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.getMessagesBySchemaId( + schemaId, + paginationRequest, + ); const messages: Vec = messageResponse.content; while (messageResponse.has_next.toHuman()) { paginationRequest = { @@ -64,7 +62,7 @@ export class ChainEventProcessorService { to_block: blockNumber + 1, }; // eslint-disable-next-line no-await-in-loop - messageResponse = await this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest); + messageResponse = await this.blockchainService.getMessagesBySchemaId(schemaId, paginationRequest); if (messageResponse.content.length > 0) { messages.push(...messageResponse.content); } diff --git a/libs/graph-lib/src/utils/blockchain-scanner.service.spec.ts b/libs/graph-lib/src/utils/blockchain-scanner.service.spec.ts index 167338e4..dfc7a302 100644 --- a/libs/graph-lib/src/utils/blockchain-scanner.service.spec.ts +++ b/libs/graph-lib/src/utils/blockchain-scanner.service.spec.ts @@ -1,11 +1,34 @@ import { Injectable, Logger } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { Hash, SignedBlock } from '@polkadot/types/interfaces'; -import { BlockchainService } from '#blockchain/blockchain.service'; +import { BlockHash, BlockNumber, SignedBlock } from '@polkadot/types/interfaces'; import { DEFAULT_REDIS_NAMESPACE, getRedisToken, InjectRedis } from '@songkeys/nestjs-redis'; import { Redis } from 'ioredis'; import { BlockchainScannerService } from './blockchain-scanner.service'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; +import { mockApiPromise } from '#testlib/polkadot-api.mock.spec'; +import { BlockchainRpcQueryService } from '#blockchain/blockchain-rpc-query.service'; +import { IBlockchainNonProviderConfig } from '#blockchain/blockchain.config'; +import { GenerateMockConfigProvider } from '#testlib/utils.config-tests'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { AnyNumber } from '@polkadot/types/types'; + +jest.mock('@polkadot/api', () => { + const originalModule = jest.requireActual('@polkadot/api'); + return { + __esModules: true, + WsProvider: jest.fn().mockImplementation(() => originalModule.WsProvider), + ApiPromise: jest.fn().mockImplementation(() => ({ + ...originalModule.ApiPromise, + ...mockApiPromise, + })), + }; +}); + +const mockBlockchainConfigProvider = GenerateMockConfigProvider('blockchain', { + frequencyTimeoutSecs: 10, + frequencyApiWsUrl: new URL('ws://localhost:9944'), + isDeployedReadOnly: false, +}); const mockRedis = { provide: getRedisToken(DEFAULT_REDIS_NAMESPACE), @@ -15,7 +38,8 @@ const mockRedis = { const mockBlockHash = { toString: jest.fn(() => '0x1234'), some: () => true, -}; + isEmpty: false, +} as unknown as BlockHash; const mockSignedBlock = { block: { @@ -26,44 +50,15 @@ const mockSignedBlock = { }, }; -Object.defineProperty(mockBlockHash, 'isEmpty', { - get: jest.fn(() => false), -}); - const mockEmptyBlockHash = { toString: jest.fn(() => '0x00000'), some: () => false, -}; -Object.defineProperty(mockEmptyBlockHash, 'isEmpty', { - get: jest.fn(() => true), -}); -const mockBlockchainService = { - getBlock: jest.fn((_blockHash?: string | Hash) => mockSignedBlock as unknown as SignedBlock), - getBlockHash: jest.fn((blockNumber: number) => (blockNumber > 1 ? mockEmptyBlockHash : mockBlockHash)), - getLatestFinalizedBlockNumber: jest.fn(), -}; -Object.defineProperty(mockBlockchainService, 'api', { - get: jest.fn(() => ({ - at: jest.fn(() => ({ - query: { - system: { - events: jest.fn(() => ({ - toArray: jest.fn(() => []), - })), - }, - }, - })), - })), -}); - -const mockBlockchainServiceProvider = { - provide: BlockchainService, - useValue: mockBlockchainService, -}; + isEmpty: true, +} as unknown as BlockHash; @Injectable() class ScannerService extends BlockchainScannerService { - constructor(@InjectRedis() redis: Redis, blockchainService: BlockchainService) { + constructor(@InjectRedis() redis: Redis, blockchainService: BlockchainRpcQueryService) { super(redis, blockchainService, new Logger('ScannerService')); } // eslint-disable-next-line @@ -74,14 +69,49 @@ class ScannerService extends BlockchainScannerService { describe('BlockchainScannerService', () => { let service: ScannerService; - let blockchainService: BlockchainService; + let blockchainService: BlockchainRpcQueryService; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - providers: [mockRedis, Logger, mockBlockchainServiceProvider, ScannerService], + imports: [ + EventEmitterModule.forRoot({ + // Use this instance throughout the application + global: true, + // set this to `true` to use wildcards + wildcard: false, + // the delimiter used to segment namespaces + delimiter: '.', + // set this to `true` if you want to emit the newListener event + newListener: false, + // set this to `true` if you want to emit the removeListener event + removeListener: false, + // the maximum amount of listeners that can be assigned to an event + maxListeners: 10, + // show event name in memory leak message when more than maximum amount of listeners is assigned + verboseMemoryLeak: false, + // disable throwing uncaughtException if an error event is emitted and it has no listeners + ignoreErrors: false, + }), + ], + providers: [mockRedis, mockBlockchainConfigProvider, Logger, BlockchainRpcQueryService, ScannerService], }).compile(); + + moduleRef.enableShutdownHooks(); + service = moduleRef.get(ScannerService); - blockchainService = moduleRef.get(BlockchainService); + blockchainService = moduleRef.get(BlockchainRpcQueryService); + const mockApi: any = await blockchainService.getApi(); + + jest.spyOn(blockchainService, 'getBlock').mockResolvedValue(mockSignedBlock as unknown as SignedBlock); + jest + .spyOn(blockchainService, 'getBlockHash') + .mockImplementation((blockNumber: BlockNumber | AnyNumber) => + Promise.resolve((blockNumber as unknown as number) > 1 ? mockEmptyBlockHash : mockBlockHash), + ); + jest.spyOn(blockchainService, 'getLatestFinalizedBlockNumber'); + jest.spyOn(blockchainService, 'getEvents').mockResolvedValue([]); + + mockApi.emit('connected'); // keeps the test suite from hanging when finished }); describe('scan', () => { diff --git a/libs/graph-lib/src/utils/blockchain-scanner.service.ts b/libs/graph-lib/src/utils/blockchain-scanner.service.ts index 25de641d..98f65c47 100644 --- a/libs/graph-lib/src/utils/blockchain-scanner.service.ts +++ b/libs/graph-lib/src/utils/blockchain-scanner.service.ts @@ -3,7 +3,7 @@ import '@frequency-chain/api-augment'; import { Logger } from '@nestjs/common'; import { BlockHash, SignedBlock } from '@polkadot/types/interfaces'; -import { BlockchainService } from '#blockchain/blockchain.service'; +import { BlockchainRpcQueryService } from '#blockchain/blockchain-rpc-query.service'; import Redis from 'ioredis'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; @@ -21,6 +21,8 @@ function eventName({ event: { section, method } }: FrameSystemEventRecord) { } export abstract class BlockchainScannerService { + private scanIsPaused = false; + protected scanInProgress = false; protected chainEventHandlers = new Map< @@ -34,10 +36,24 @@ export abstract class BlockchainScannerService { constructor( protected cacheManager: Redis, - protected readonly blockchainService: BlockchainService, + protected readonly blockchainService: BlockchainRpcQueryService, protected readonly logger: Logger, ) { this.lastSeenBlockNumberKey = `${this.constructor.name}:${LAST_SEEN_BLOCK_NUMBER_KEY}`; + this.blockchainService.on('chain.disconnected', () => { + this.paused = true; + }); + this.blockchainService.on('chain.connected', () => { + this.paused = false; + }); + } + + protected get paused() { + return this.scanIsPaused; + } + + private set paused(p: boolean) { + this.scanIsPaused = p; } public get scanParameters() { @@ -76,12 +92,11 @@ export abstract class BlockchainScannerService { this.logger.verbose(`Starting scan from block #${currentBlockNumber}`); // eslint-disable-next-line no-constant-condition - while (true) { + while (!this.paused) { try { await this.checkScanParameters(currentBlockNumber, currentBlockHash); // throws when end-of-chain reached const block = await this.blockchainService.getBlock(currentBlockHash); - const at = await this.blockchainService.api.at(currentBlockHash); - const blockEvents = (await at.query.system.events()).toArray(); + const blockEvents = await this.blockchainService.getEvents(currentBlockHash); await this.handleChainEvents(block, blockEvents); await this.processCurrentBlock(block, blockEvents); } catch (err) { @@ -102,8 +117,11 @@ export abstract class BlockchainScannerService { return; } - this.logger.error(JSON.stringify(e)); - throw e; + // Don't throw if scan paused; that's WHY it's paused + if (!this.paused) { + this.logger.error(JSON.stringify(e)); + throw e; + } } finally { this.scanInProgress = false; } diff --git a/libs/types/src/dtos/account/accounts.request.dto.ts b/libs/types/src/dtos/account/accounts.request.dto.ts index 51b9ae56..2eed31d9 100644 --- a/libs/types/src/dtos/account/accounts.request.dto.ts +++ b/libs/types/src/dtos/account/accounts.request.dto.ts @@ -1,6 +1,5 @@ import { HexString } from '@polkadot/util/types'; import { RetireMsaPayloadResponseDto } from './accounts.response.dto'; -import { IsAccountIdOrAddress } from '#utils/decorators/is-account-id-address.decorator'; import { IsSignature } from '#utils/decorators/is-signature.decorator'; import { TransactionType } from '#types/account-webhook'; @@ -11,13 +10,6 @@ export class RetireMsaRequestDto extends RetireMsaPayloadResponseDto { */ @IsSignature({ requiresSignatureType: true }) signature: HexString; - - /** - * AccountId in hex or SS58 format - * @example '1LSLqpLWXo7A7xuiRdu6AQPnBPNJHoQSu8DBsUYJgsNEJ4N' - */ - @IsAccountIdOrAddress() - accountId: string; } export type PublishRetireMsaRequestDto = RetireMsaRequestDto & { diff --git a/libs/types/src/enums/item-action-type.enum.ts b/libs/types/src/enums/item-action-type.enum.ts new file mode 100644 index 00000000..1503e011 --- /dev/null +++ b/libs/types/src/enums/item-action-type.enum.ts @@ -0,0 +1,5 @@ +// eslint-disable-next-line no-shadow +export enum ItemActionType { + ADD_ITEM = 'ADD_ITEM', + DELETE_ITEM = 'DELETE_ITEM', +} diff --git a/package-lock.json b/package-lock.json index 45449aff..645ac31a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "@polkadot/api": "^13.2.1", "@polkadot/api-base": "^13.2.1", "@polkadot/keyring": "^13.1.1", + "@polkadot/rpc-provider": "^14.0.1", "@polkadot/types": "^13.2.1", "@polkadot/util": "^13.1.1", "@polkadot/util-crypto": "^13.1.1", @@ -67,8 +68,9 @@ "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.4.1", "@projectlibertylabs/frequency-scenario-template": "^1.1.8", - "@types/jest": "^29.5.2", + "@types/jest": "^29.5.13", "@types/node": "^20.3.1", + "@types/node-fetch": "^2.6.11", "@types/readline-sync": "^1.4.8", "@types/supertest": "^6.0.0", "@types/time-constants": "^1.0.2", @@ -99,7 +101,7 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "tsx": "^4.19.0", - "typescript": "^5.5.4" + "typescript": "~5.5.4" } }, "node_modules/@ampproject/remapping": { @@ -6654,6 +6656,45 @@ "globals": "^15.9.0" } }, + "node_modules/@frequency-chain/api-augment/node_modules/@polkadot/rpc-provider": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-13.2.1.tgz", + "integrity": "sha512-bbMVYHTNFUa89aY3UQ1hFYD+dP+v+0vhjsnHYYlv37rSUTqOGqW91rkHd63xYCpLAimFt7KRw8xR+SMSYiuDjw==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/keyring": "^13.1.1", + "@polkadot/types": "13.2.1", + "@polkadot/types-support": "13.2.1", + "@polkadot/util": "^13.1.1", + "@polkadot/util-crypto": "^13.1.1", + "@polkadot/x-fetch": "^13.1.1", + "@polkadot/x-global": "^13.1.1", + "@polkadot/x-ws": "^13.1.1", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.3.1", + "nock": "^13.5.4", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@substrate/connect": "0.8.11" + } + }, + "node_modules/@frequency-chain/api-augment/node_modules/@polkadot/types-support": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-13.2.1.tgz", + "integrity": "sha512-jSbbUTXU+yZJQPRAWmxaDoe4NRO6SjpZPzBIbpuiadx1slON8XB80fVYIGBXuM2xRVrNrB6fCjyCTG7Razj6Hg==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/util": "^13.1.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", @@ -8570,33 +8611,6 @@ "node": ">=18" } }, - "node_modules/@polkadot/api-contract/node_modules/@polkadot/rpc-provider": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-14.0.1.tgz", - "integrity": "sha512-mNfaKZUHPXGSY7TwgOfV05RN3Men21Dw7YXrSZDFkJYsZ55yOAYdmLg9anPZGHW100YnNWrXj+3uhQOw8JgqkA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@polkadot/keyring": "^13.1.1", - "@polkadot/types": "14.0.1", - "@polkadot/types-support": "14.0.1", - "@polkadot/util": "^13.1.1", - "@polkadot/util-crypto": "^13.1.1", - "@polkadot/x-fetch": "^13.1.1", - "@polkadot/x-global": "^13.1.1", - "@polkadot/x-ws": "^13.1.1", - "eventemitter3": "^5.0.1", - "mock-socket": "^9.3.1", - "nock": "^13.5.4", - "tslib": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@substrate/connect": "0.8.11" - } - }, "node_modules/@polkadot/api-contract/node_modules/@polkadot/types": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-14.0.1.tgz", @@ -8681,20 +8695,6 @@ "node": ">=18" } }, - "node_modules/@polkadot/api-contract/node_modules/@polkadot/types-support": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-14.0.1.tgz", - "integrity": "sha512-lcZEyOf5e3WLLtrFlLTvFfUpO0Vx/Gh5lhLLjdx1W9Xs0KJUlOxSAKxvjVieJJj6HifL0Jh6tDYOUeEc4TOrvA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@polkadot/util": "^13.1.1", - "tslib": "^2.7.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@polkadot/api-derive": { "version": "13.2.1", "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-13.2.1.tgz", @@ -8716,6 +8716,45 @@ "node": ">=18" } }, + "node_modules/@polkadot/api/node_modules/@polkadot/rpc-provider": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-13.2.1.tgz", + "integrity": "sha512-bbMVYHTNFUa89aY3UQ1hFYD+dP+v+0vhjsnHYYlv37rSUTqOGqW91rkHd63xYCpLAimFt7KRw8xR+SMSYiuDjw==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/keyring": "^13.1.1", + "@polkadot/types": "13.2.1", + "@polkadot/types-support": "13.2.1", + "@polkadot/util": "^13.1.1", + "@polkadot/util-crypto": "^13.1.1", + "@polkadot/x-fetch": "^13.1.1", + "@polkadot/x-global": "^13.1.1", + "@polkadot/x-ws": "^13.1.1", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.3.1", + "nock": "^13.5.4", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@substrate/connect": "0.8.11" + } + }, + "node_modules/@polkadot/api/node_modules/@polkadot/types-support": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-13.2.1.tgz", + "integrity": "sha512-jSbbUTXU+yZJQPRAWmxaDoe4NRO6SjpZPzBIbpuiadx1slON8XB80fVYIGBXuM2xRVrNrB6fCjyCTG7Razj6Hg==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/util": "^13.1.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@polkadot/extension-dapp": { "version": "0.46.9", "resolved": "https://registry.npmjs.org/@polkadot/extension-dapp/-/extension-dapp-0.46.9.tgz", @@ -9328,6 +9367,45 @@ "@polkadot/util": "*" } }, + "node_modules/@polkadot/extension-inject/node_modules/@polkadot/rpc-provider": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-13.2.1.tgz", + "integrity": "sha512-bbMVYHTNFUa89aY3UQ1hFYD+dP+v+0vhjsnHYYlv37rSUTqOGqW91rkHd63xYCpLAimFt7KRw8xR+SMSYiuDjw==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/keyring": "^13.1.1", + "@polkadot/types": "13.2.1", + "@polkadot/types-support": "13.2.1", + "@polkadot/util": "^13.1.1", + "@polkadot/util-crypto": "^13.1.1", + "@polkadot/x-fetch": "^13.1.1", + "@polkadot/x-global": "^13.1.1", + "@polkadot/x-ws": "^13.1.1", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.3.1", + "nock": "^13.5.4", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@substrate/connect": "0.8.11" + } + }, + "node_modules/@polkadot/extension-inject/node_modules/@polkadot/types-support": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-13.2.1.tgz", + "integrity": "sha512-jSbbUTXU+yZJQPRAWmxaDoe4NRO6SjpZPzBIbpuiadx1slON8XB80fVYIGBXuM2xRVrNrB6fCjyCTG7Razj6Hg==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/util": "^13.1.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@polkadot/keyring": { "version": "13.1.1", "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-13.1.1.tgz", @@ -9393,7 +9471,7 @@ "node": ">=18" } }, - "node_modules/@polkadot/rpc-provider": { + "node_modules/@polkadot/rpc-core/node_modules/@polkadot/rpc-provider": { "version": "13.2.1", "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-13.2.1.tgz", "integrity": "sha512-bbMVYHTNFUa89aY3UQ1hFYD+dP+v+0vhjsnHYYlv37rSUTqOGqW91rkHd63xYCpLAimFt7KRw8xR+SMSYiuDjw==", @@ -9419,6 +9497,107 @@ "@substrate/connect": "0.8.11" } }, + "node_modules/@polkadot/rpc-core/node_modules/@polkadot/types-support": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-13.2.1.tgz", + "integrity": "sha512-jSbbUTXU+yZJQPRAWmxaDoe4NRO6SjpZPzBIbpuiadx1slON8XB80fVYIGBXuM2xRVrNrB6fCjyCTG7Razj6Hg==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/util": "^13.1.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-provider": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-14.0.1.tgz", + "integrity": "sha512-mNfaKZUHPXGSY7TwgOfV05RN3Men21Dw7YXrSZDFkJYsZ55yOAYdmLg9anPZGHW100YnNWrXj+3uhQOw8JgqkA==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/keyring": "^13.1.1", + "@polkadot/types": "14.0.1", + "@polkadot/types-support": "14.0.1", + "@polkadot/util": "^13.1.1", + "@polkadot/util-crypto": "^13.1.1", + "@polkadot/x-fetch": "^13.1.1", + "@polkadot/x-global": "^13.1.1", + "@polkadot/x-ws": "^13.1.1", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.3.1", + "nock": "^13.5.4", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@substrate/connect": "0.8.11" + } + }, + "node_modules/@polkadot/rpc-provider/node_modules/@polkadot/types": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-14.0.1.tgz", + "integrity": "sha512-DOMzHsyVbCa12FT2Fng8iGiQJhHW2ONpv5oieU+Z2o0gFQqwNmIDXWncScG5mAUBNcDMXLuvWIKLKtUDOq8msg==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/keyring": "^13.1.1", + "@polkadot/types-augment": "14.0.1", + "@polkadot/types-codec": "14.0.1", + "@polkadot/types-create": "14.0.1", + "@polkadot/util": "^13.1.1", + "@polkadot/util-crypto": "^13.1.1", + "rxjs": "^7.8.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-provider/node_modules/@polkadot/types-augment": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-14.0.1.tgz", + "integrity": "sha512-PGo81444J5tGJxP3tu060Jx1kkeuo8SmBIt9S/w626Se49x4RLM5a7Pa5fguYVsg4TsJa9cgVPMuu6Y0F/2aCQ==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/types": "14.0.1", + "@polkadot/types-codec": "14.0.1", + "@polkadot/util": "^13.1.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-provider/node_modules/@polkadot/types-codec": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-14.0.1.tgz", + "integrity": "sha512-IyUlkrRZ6uppbHVlMJL+btKP7dfgW65K06ggQxH7Y/IyRAQVDNjXecAZrCUMB/gtjUXNPyTHEIfPGDlg8E6rig==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/util": "^13.1.1", + "@polkadot/x-bigint": "^13.1.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-provider/node_modules/@polkadot/types-create": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-14.0.1.tgz", + "integrity": "sha512-R9/ac3CHKrFhvPKVUdpjnCDFSaGjfrNwtuY+AzvExAMIq7pM9dxo2N8UfnLbyFaG/n1hfYPXDIS3hLHvOZsLbw==", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/types-codec": "14.0.1", + "@polkadot/util": "^13.1.1", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@polkadot/types": { "version": "13.2.1", "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-13.2.1.tgz", @@ -9499,9 +9678,9 @@ } }, "node_modules/@polkadot/types-support": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-13.2.1.tgz", - "integrity": "sha512-jSbbUTXU+yZJQPRAWmxaDoe4NRO6SjpZPzBIbpuiadx1slON8XB80fVYIGBXuM2xRVrNrB6fCjyCTG7Razj6Hg==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-14.0.1.tgz", + "integrity": "sha512-lcZEyOf5e3WLLtrFlLTvFfUpO0Vx/Gh5lhLLjdx1W9Xs0KJUlOxSAKxvjVieJJj6HifL0Jh6tDYOUeEc4TOrvA==", "license": "Apache-2.0", "dependencies": { "@polkadot/util": "^13.1.1", @@ -12149,6 +12328,17 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/node-forge": { "version": "1.3.11", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", @@ -27894,9 +28084,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "devOptional": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 9f03b13e..f864448e 100644 --- a/package.json +++ b/package.json @@ -13,38 +13,38 @@ "build:content-watcher": "nest build content-watcher", "build:graph": "nest build graph-api && nest build graph-worker", "build": "npm run build:account ; npm run build:content-publishing ; npm run build:content-watcher ; npm run build:graph", - "start:account-api": "nest start account-api", - "start:account-api:watch": "nest start account-api --watch", + "start:account-api": "dotenvx run -f .env.account -- nest start account-api", + "start:account-api:watch": "dotenvx run -f .env.account -- nest start account-api --watch", "start:account-api:prod": "node dist/apps/account-api/main.js", "start:account-api:dev": "dotenvx run -f .env.account -- nest start account-api --watch", "start:account-api:debug": "dotenvx run -f .env.account -- nest start account-api --debug --watch", - "start:account-worker": "nest start account-worker", - "start:account-worker:watch": "nest start account-worker --watch", + "start:account-worker": "dotenvx run -f .env.account -- nest start account-worker", + "start:account-worker:watch": "dotenvx run -f .env.account -- nest start account-worker --watch", "start:account-worker:prod": "node dist/apps/account-worker/main.js", "start:account-worker:dev": "dotenvx run -f .env.account -- nest start account-worker --watch", "start:account-worker:debug": "dotenvx run -f .env.account -- nest start account-worker --debug --watch", - "start:content-publishing-api": "nest start content-publishing-api", - "start:content-publishing-api:watch": "nest start content-publishing-api --watch", + "start:content-publishing-api": "dotenvx run -f .env.content-publishing -- nest start content-publishing-api", + "start:content-publishing-api:watch": "dotenvx run -f .env.content-publishing -- nest start content-publishing-api --watch", "start:content-publishing-api:prod": "node dist/apps/content-publishing-api/main.js", "start:content-publishing-api:dev": "dotenvx run -f .env.content-publishing -- nest start content-publishing-api --watch", "start:content-publishing-api:debug": "dotenvx run -f .env.content-publishing -- nest start content-publishing-api --debug --watch", - "start:content-publishing-worker": "nest start content-publishing-worker", - "start:content-publishing-worker:watch": "nest start content-publishing-worker --watch", + "start:content-publishing-worker": "dotenvx run -f .env.content-publishing -- nest start content-publishing-worker", + "start:content-publishing-worker:watch": "dotenvx run -f .env.content-publishing -- nest start content-publishing-worker --watch", "start:content-publishing-worker:prod": "node dist/apps/content-publishing-worker/main.js", "start:content-publishing-worker:dev": "dotenvx run -f .env.content-publishing -- nest start content-publishing-worker --watch", "start:content-publishing-worker:debug": "dotenvx run -f .env.content-publishing -- nest start content-publishing-worker --debug --watch", - "start:content-watcher": "nest start content-watcher", - "start:content-watcher:watch": "nest start content-watcher --watch", + "start:content-watcher": "dotenvx run -f .env.content-watcher -- nest start content-watcher", + "start:content-watcher:watch": "dotenvx run -f .env.content-watcher -- nest start content-watcher --watch", "start:content-watcher:prod": "node dist/apps/content-watcher/main.js", "start:content-watcher:dev": "dotenvx run -f .env.content-watcher -- nest start content-watcher --watch", "start:content-watcher:debug": "dotenvx run -f .env.content-watcher -- nest start content-watcher --debug --watch", - "start:graph-api": "nest start graph-api", - "start:graph-api:watch": "nest start graph-api --watch", + "start:graph-api": "dotenvx run -f .env.graph -- nest start graph-api", + "start:graph-api:watch": "dotenvx run -f .env.graph -- nest start graph-api --watch", "start:graph-api:prod": "node dist/apps/graph-api/main.js", "start:graph-api:dev": "dotenvx run -f .env.graph -- nest start graph-api --watch", "start:graph-api:debug": "dotenvx run -f .env.graph -- nest start graph-api --debug --watch", - "start:graph-worker": "nest start graph-worker", - "start:graph-worker:watch": "nest start graph-worker --watch", + "start:graph-worker": "dotenvx run -f .env.graph -- nest start graph-worker", + "start:graph-worker:watch": "dotenvx run -f .env.graph -- nest start graph-worker --watch", "start:graph-worker:prod": "node dist/apps/graph-worker/main.js", "start:graph-worker:dev": "dotenvx run -f .env.graph -- nest start graph-worker --watch", "start:graph-worker:debug": "dotenvx run -f .env.graph -- nest start graph-worker --debug --watch", @@ -69,9 +69,9 @@ "test:content-publishing": "dotenvx run -f env-files/content-publishing.template.env -- jest 'content-publishing*'", "test:content-watcher": "dotenvx run -f env-files/content-watcher.template.env -- jest 'content-watcher*'", "test:graph": "dotenvx run -f env-files/graph.template.env -- jest 'graph*'", - "test:libs:blockchain": "dotenvx run -f env-files/graph.template.env -- jest 'libs/blockchain*'", + "test:libs:blockchain": "dotenvx run -f env-files/graph.template.env -- jest --runInBand 'libs/blockchain*'", "test:libs:cache": "dotenvx run -f env-files/graph.template.env -- jest 'libs/cache*'", - "test:libs:utils": "dotenvx run -- jest 'libs/utils*'", + "test:libs:utils": "jest 'libs/utils*'", "test:libs": "npm run test:libs:blockchain ; npm run test:libs:cache; npm run test:libs:utils;", "test": "npm run test:account ; npm run test:content-publishing ; npm run test:content-watcher ; npm run test:graph ; npm run test:libs", "test:verbose": "jest --coverage --verbose", @@ -121,6 +121,7 @@ "@polkadot/api": "^13.2.1", "@polkadot/api-base": "^13.2.1", "@polkadot/keyring": "^13.1.1", + "@polkadot/rpc-provider": "^14.0.1", "@polkadot/types": "^13.2.1", "@polkadot/util": "^13.1.1", "@polkadot/util-crypto": "^13.1.1", @@ -152,8 +153,9 @@ "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.4.1", "@projectlibertylabs/frequency-scenario-template": "^1.1.8", - "@types/jest": "^29.5.2", + "@types/jest": "^29.5.13", "@types/node": "^20.3.1", + "@types/node-fetch": "^2.6.11", "@types/readline-sync": "^1.4.8", "@types/supertest": "^6.0.0", "@types/time-constants": "^1.0.2", @@ -184,6 +186,6 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "tsx": "^4.19.0", - "typescript": "^5.5.4" + "typescript": "~5.5.4" } } diff --git a/testlib/bytes.spec.ts b/testlib/bytes.spec.ts new file mode 100644 index 00000000..7e54289a --- /dev/null +++ b/testlib/bytes.spec.ts @@ -0,0 +1 @@ +import '@polkadot/api'; diff --git a/testlib/polkadot-api.mock.spec.ts b/testlib/polkadot-api.mock.spec.ts new file mode 100644 index 00000000..df08646a --- /dev/null +++ b/testlib/polkadot-api.mock.spec.ts @@ -0,0 +1,43 @@ +/** This defines what the mock for 'ApiPromise' looks like; to be used in individual test files like so: + * @example + * import { mockApiPromise } from '#testlib/polkadot-api.mock.spec'; + * + * jest.mock('@polkadot/api', () => { + * const originalModule = jest.requireActual('@polkadot/api'); + * return { + * __esModules: true, + * WsProvider: jest.fn().mockImplementation(() => originalModule.WsProvider), + * ApiPromise: jest.fn().mockImplementation(() => ({ + * ...originalModule.ApiPromise, + * ...mockApiPromise, + * })), + * }; + * }); + * + */ +import { EventEmitter2 } from '@nestjs/event-emitter'; + +export const mockApiPromise = { + // Need this here; @polkadot Api really extends the 'Events' class, but it's marked #private, + // and really it's just the same API as EventEmitter, so... + ...EventEmitter2.prototype, + isReady: Promise.resolve(), + isReadyOrError: Promise.resolve(), + createType: jest.fn(), + query: { + capacity: { + currentEpochInfo: jest.fn(), + }, + statefulStorage: { + applyItemActionsV2: jest.fn(), + }, + }, + rpc: { + chain: { + getBlockHash: jest.fn(), + }, + }, + genesisHash: { + toHex: jest.fn(), + }, +}; diff --git a/tsconfig.json b/tsconfig.json index 38c2ab59..8d57777b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -66,6 +66,6 @@ "strictBindCallApply": false, "strictNullChecks": false, "strictPropertyInitialization": false, - "target": "ES2021" + "target": "ES2023" } } diff --git a/webhook-servers/Cargo.lock b/webhook-servers/Cargo.lock index b7fe8c95..9196db5e 100644 --- a/webhook-servers/Cargo.lock +++ b/webhook-servers/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.5.0", + "bitflags", "bytes", "futures-core", "futures-sink", @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.6.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d223b13fd481fc0d1f83bb12659ae774d9e3601814c68a0bc539731698cca743" +checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" dependencies = [ "actix-codec", "actix-rt", @@ -31,7 +31,7 @@ dependencies = [ "actix-utils", "ahash 0.8.11", "base64", - "bitflags 2.5.0", + "bitflags", "brotli", "bytes", "bytestring", @@ -65,14 +65,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] name = "actix-multipart" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b960e2aea75f49c8f069108063d12a48d329fc8b60b786dfc7552a9d5918d2d" +checksum = "d974dd6c4f78d102d057c672dcf6faa618fafa9df91d44f9c466688fc1275a3a" dependencies = [ "actix-multipart-derive", "actix-utils", @@ -86,6 +86,7 @@ dependencies = [ "log", "memchr", "mime", + "rand", "serde", "serde_json", "serde_plain", @@ -103,27 +104,29 @@ dependencies = [ "parse-size", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] name = "actix-router" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" dependencies = [ "bytestring", + "cfg-if", "http", "regex", + "regex-lite", "serde", "tracing", ] [[package]] name = "actix-rt" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" dependencies = [ "futures-core", "tokio", @@ -131,9 +134,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" dependencies = [ "actix-rt", "actix-service", @@ -169,9 +172,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.5.1" +version = "4.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a6556ddebb638c2358714d853257ed226ece6023ef9364f23f0c70737ea984" +checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" dependencies = [ "actix-codec", "actix-http", @@ -191,6 +194,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", + "impl-more", "itoa", "language-tags", "log", @@ -198,6 +202,7 @@ dependencies = [ "once_cell", "pin-project-lite", "regex", + "regex-lite", "serde", "serde_json", "serde_urlencoded", @@ -209,30 +214,30 @@ dependencies = [ [[package]] name = "actix-web-codegen" -version = "4.2.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" @@ -299,9 +304,9 @@ dependencies = [ [[package]] name = "apistos" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e21b2af991880f5204a0b3de8a245795b09a28d7b15e5caacb79163ffaf249" +checksum = "a30d932d9cc7f7fab17da9639ded2c81528a07eeb442c4b337cf5858b6ca4cab" dependencies = [ "actix-service", "actix-web", @@ -324,9 +329,9 @@ dependencies = [ [[package]] name = "apistos-core" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f1a68e89d9db1576134abceab3024fc67f60fd0950738f2111728da028859" +checksum = "44c1ba759f64d7e1004a966435fa6873d956fd3c85deacdd8fe59c88bb953943" dependencies = [ "actix-multipart", "actix-web", @@ -344,9 +349,9 @@ dependencies = [ [[package]] name = "apistos-gen" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05c7d5caadd5ace07ad966dd39057cda0782a314968bdce65912e9b31e50494" +checksum = "a1591cc2b3d5fb878a080e174fb59868c705db491181ce64a386f13d1d3fbf3f" dependencies = [ "actix-web", "convert_case 0.6.0", @@ -354,14 +359,14 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] name = "apistos-models" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffef3c24da4fea301568173832f3767ba195cdcae02aa32dd26f754260a1457" +checksum = "7b39462e59033dbe05818054f48c4bec943101941e0675e1bc534170a2538482" dependencies = [ "apistos-schemars", "indexmap", @@ -371,36 +376,36 @@ dependencies = [ [[package]] name = "apistos-plugins" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0d772fc56f9ebe68a69c82c497da4d3eb250dc20b913fcb2a0e41a09d2a972" +checksum = "a2d5cb7847592bbfada7c90d545369965ebc08f70380cadbf2da04f55f8ca206" dependencies = [ "actix-web", ] [[package]] name = "apistos-rapidoc" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ec9c47a1f3e6be5c25780989af08c75ea9db52805f6b92c82bd87d37caa12a" +checksum = "a91cc67f2265e17031c001a1d5be57ab60f66317306d7ccbfd7c595f4d64501d" dependencies = [ "apistos-plugins", ] [[package]] name = "apistos-redoc" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79000dbe5faa537cdcdc3ff55da5f1ad453b7fd208cb28571d92f73b8e51faf0" +checksum = "9e8b591562a5affde3afd9acc91f070d9c65dc5dbaaf1698cf54a0bbc8b1a92a" dependencies = [ "apistos-plugins", ] [[package]] name = "apistos-schemars" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bda2366dfffa01180cfe5be299893391339ca0b49635929bdd3f49ad38f8d71" +checksum = "f75d3c533a415ba9bbedf67bd837f4fc895d1fccd3da3ed7441a6310ac4f1e56" dependencies = [ "apistos-schemars_derive", "chrono", @@ -414,69 +419,63 @@ dependencies = [ [[package]] name = "apistos-schemars_derive" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0ebfe0f869992dd9bf20d17d99c7f4e4e8e1c46b04a9fbd38f494eeb089f80" +checksum = "6628c03331322ef9a48f4dea21d01cb32832a03fc0ed57f56cf062ee47cd0fdf" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.83", ] [[package]] name = "apistos-swagger-ui" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c12687d1ccd901b941f7867145442fd964932937ffb2a05fd58f0c763d0a43" +checksum = "54e3dd06449efa0b920b3e04c77a599045ab24cea89f46dec0322125adc4180e" dependencies = [ "apistos-plugins", ] [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "bitflags" -version = "1.3.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -501,9 +500,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" dependencies = [ "borsh-derive", "cfg_aliases", @@ -511,23 +510,23 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", "syn_derive", ] [[package]] name = "brotli" -version = "3.5.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -536,9 +535,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -572,11 +571,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bytestring" @@ -589,22 +594,22 @@ dependencies = [ [[package]] name = "castaway" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" dependencies = [ "rustversion", ] [[package]] name = "cc" -version = "1.0.95" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] @@ -615,9 +620,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" @@ -630,7 +635,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets", ] [[package]] @@ -675,24 +680,24 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -709,9 +714,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.8" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -719,27 +724,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] @@ -753,15 +758,15 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", - "syn 1.0.109", + "syn 2.0.83", ] [[package]] @@ -810,9 +815,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -820,15 +825,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -857,9 +862,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -872,9 +877,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -882,15 +887,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -899,38 +904,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -958,9 +963,9 @@ dependencies = [ [[package]] name = "garde-actix-web" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfd11d8e64a3650e364e68ed815ce05ffc21b94add9c6787f6b771398835928" +checksum = "61a38483e932bdc9060f83b65e829904c5855056d9d60106741d2ad8391ed9ff" dependencies = [ "actix-http", "actix-router", @@ -985,7 +990,7 @@ checksum = "9cf62650515830c41553b72bd49ec20fb120226f9277c7f2847f071cf998325b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] @@ -1000,9 +1005,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -1011,9 +1016,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" @@ -1045,9 +1050,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "hermit-abi" @@ -1055,6 +1060,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "http" version = "0.2.12" @@ -1068,9 +1079,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1086,9 +1097,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1134,24 +1145,30 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-more" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" + [[package]] name = "indexmap" -version = "2.2.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.15.0", "serde", ] [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -1164,18 +1181,18 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1188,21 +1205,21 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-channel" @@ -1223,9 +1240,9 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1233,9 +1250,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matches" @@ -1251,9 +1268,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -1263,23 +1280,24 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "log", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1290,33 +1308,33 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" -version = "0.32.2" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1324,28 +1342,28 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets", ] [[package]] name = "parse-size" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" +checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b" [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" @@ -1355,22 +1373,22 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] @@ -1387,9 +1405,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "powerfmt" @@ -1399,15 +1417,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] @@ -1438,9 +1459,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -1467,9 +1488,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1512,18 +1533,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -1533,20 +1554,26 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rend" @@ -1559,9 +1586,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.44" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", @@ -1577,33 +1604,20 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.44" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] -[[package]] -name = "webhook-servers" -version = "0.1.0" -dependencies = [ - "actix-web", - "apistos", - "apistos-schemars", - "env_logger", - "serde", - "serde_json", - "validator", -] - [[package]] name = "rust_decimal" -version = "1.35.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "borsh", @@ -1617,26 +1631,26 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.5.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1645,15 +1659,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "scopeguard" @@ -1669,48 +1683,49 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.198" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.83", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1726,9 +1741,9 @@ dependencies = [ [[package]] name = "serde_qs" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" dependencies = [ "actix-web", "futures", @@ -1760,6 +1775,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1771,9 +1792,9 @@ dependencies = [ [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slab" @@ -1795,9 +1816,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1811,9 +1832,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" @@ -1828,9 +1849,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "01680f5d178a369f817f43f3d399650272873a8e7588a7872f7e90edc71d60a3" dependencies = [ "proc-macro2", "quote", @@ -1846,7 +1867,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] @@ -1857,14 +1878,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1878,22 +1900,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] @@ -1929,9 +1951,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -1944,9 +1966,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -1956,34 +1978,33 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", @@ -2018,36 +2039,36 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna 0.5.0", @@ -2056,9 +2077,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", "serde", @@ -2088,9 +2109,9 @@ checksum = "ad9680608df133af2c1ddd5eaf1ddce91d60d61b6bc51494ef326458365a470a" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -2100,34 +2121,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2135,48 +2157,52 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] -name = "winapi-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +name = "webhook-servers" +version = "0.1.0" dependencies = [ - "windows-sys 0.52.0", + "actix-web", + "apistos", + "apistos-schemars", + "env_logger", + "serde", + "serde_json", + "validator", ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "winapi-util" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-targets 0.52.5", + "windows-sys 0.59.0", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -2185,135 +2211,87 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -2329,47 +2307,48 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.83", ] [[package]] name = "zstd" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.1.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config",