From 45fa9b49e4c5d4213e326418ed8870827b0a2911 Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Wed, 5 Jul 2023 14:55:59 +0700 Subject: [PATCH 01/33] docs: cw721 --- docs/services/cw721/cw721-media.md | 53 ++++++++++++++++++++++++++++++ docs/services/cw721/cw721.md | 38 +++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 docs/services/cw721/cw721-media.md create mode 100644 docs/services/cw721/cw721.md diff --git a/docs/services/cw721/cw721-media.md b/docs/services/cw721/cw721-media.md new file mode 100644 index 000000000..22e00e852 --- /dev/null +++ b/docs/services/cw721/cw721-media.md @@ -0,0 +1,53 @@ +## Cw721 Media Service + +```mermaid + sequenceDiagram + autonumber + participant A as Cw721MediaService + participant B as DB + participant C as Network + participant D as S3 + + loop Interval + A->>B: Get top 10 token which media_info = null (unprocessed) + activate B + B-->>A: token instances + deactivate B + + A->>C: get token info (token_uri, extension) + activate C + C-->>A: token info + deactivate C + + alt token_uri!=null + A->>C: call ipfs link from token_uri to get metadata + else token_uri==null + A->>A: metadata = extension + end + + A->>A: get image url from metadata + alt image_url!=null + A->>D: get image then upload image to S3 + activate D + D-->>A: S3 link + deactivate D + else image_url==null + A->>A: empty image offchain + else error + A->>A: empty image offchain + end + + alt animation_url!=null + A->>D: get animation then upload image to S3 + activate D + D-->>A: S3 link + deactivate D + else animation_url==null + A->>A: empty animation offchain + else error + A->>A: empty animation offchain + end + + A-->>B: update media_info (animation offchain, image offchain) + end +``` diff --git a/docs/services/cw721/cw721.md b/docs/services/cw721/cw721.md new file mode 100644 index 000000000..20798a40c --- /dev/null +++ b/docs/services/cw721/cw721.md @@ -0,0 +1,38 @@ +## Cw721 Service + +```mermaid + sequenceDiagram + autonumber + participant A as Cw721Service + participant B as DB + + loop Interval + A->>B: Get BlockCheckpoint for Cw721Service + activate B + B-->>A: Return BlockCheckpoint + deactivate B + alt not found BlockCheckpoint + A->>A: Set checkpoint = startBlock config + end + A->>A: endBlock = startBlock + config.BlocksPerCall + + A->>B: Get cw721 smart contract event from startBlock to endBlock + activate B + B-->>A: return list cw721 events + deactivate B + A->>A: handle each cw721 event type + + alt instantiate + A->>B: Upsert new cw721 contract instance + else mint + A->>B: Insert new cw721 token instance + else transfer + A->>B: Update new owner + else burn + A->>B: Update burn status + end + + A->>B: insert new cw721 event instances + A->>B: Update checkpoint = endBlock + end +``` From b27aa4c5f9700a5e8171ceb30209f917b24afd4d Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Fri, 7 Jul 2023 14:25:31 +0700 Subject: [PATCH 02/33] docs: cw20 --- docs/services/cw20/cw20.md | 33 +++++++++++++++++++ docs/services/cw20/cw20_update_by_contract.md | 23 +++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 docs/services/cw20/cw20.md create mode 100644 docs/services/cw20/cw20_update_by_contract.md diff --git a/docs/services/cw20/cw20.md b/docs/services/cw20/cw20.md new file mode 100644 index 000000000..05d447c4a --- /dev/null +++ b/docs/services/cw20/cw20.md @@ -0,0 +1,33 @@ +## Cw20 Service + +```mermaid + sequenceDiagram + autonumber + participant A as Cw20Service + participant B as DB + participant C as Network + participant D as Cw20UpdateByContractService + loop Interval + A->>B: Get BlockCheckpoint for Cw20Service + activate B + B-->>A: Return BlockCheckpoint + deactivate B + alt not found BlockCheckpoint + A->>A: Set checkpoint = startBlock config + end + A->>A: endBlock = startBlock + config.BlocksPerCall + A->>B: Get cw20 smart contract event from startBlock to endBlock + activate B + B-->>A: return list cw20 events + deactivate B + A->>A: handle each cw20 instantiate event + A->>C: Get all current holders and their balance + activate C + C->>A: Return results + deactivate C + A->>B: Insert into cw20_contract and cw20_holder + A->>B: insert new cw20 event instances + A->>B: Update checkpoint = endBlock + A->>D: Call action update cw20 by contract {contract, startBlock, endBlock} + end +``` diff --git a/docs/services/cw20/cw20_update_by_contract.md b/docs/services/cw20/cw20_update_by_contract.md new file mode 100644 index 000000000..d4b665ee4 --- /dev/null +++ b/docs/services/cw20/cw20_update_by_contract.md @@ -0,0 +1,23 @@ +## Cw20 Update By Contract Service + +```mermaid + sequenceDiagram + autonumber + participant A as Cw20UpdateByContract + participant B as DB + loop Interval + A->>B: Get cw20 smart contract event from startBlock to endBlock for specified contract + activate B + B-->>A: return list cw20 events + deactivate B + A->>A: handle each cw20 event type + alt mint + A->>B: Insert new cw721_holder instance or add balance if he already has balance + else transfer + A->>B: Add balance to recipient and sub balance to sender + else burn + A->>B: Sub balance to owner + end + A->>B: Update total_supply + end +``` From 5e50079af69f8d0aaab0ed734184b55bf9f8d182 Mon Sep 17 00:00:00 2001 From: Phan Anh Tuan Date: Mon, 31 Jul 2023 14:16:24 +0700 Subject: [PATCH 03/33] fix: update validator as UNSPECIFIED when not found onchain --- src/models/validator.ts | 3 +++ .../crawl_validator.service.ts | 22 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/models/validator.ts b/src/models/validator.ts index 71c58a81b..4e9ea8758 100644 --- a/src/models/validator.ts +++ b/src/models/validator.ts @@ -73,6 +73,9 @@ export class Validator extends BaseModel { return { BONDED: 'BOND_STATUS_BONDED', UNBONDED: 'BOND_STATUS_UNBONDED', + UNSPECIFIED: 'BOND_STATUS_UNSPECIFIED', + UNBONDING: 'BOND_STATUS_UNBONDING', + UNRECOGNIZED: 'UNRECOGNIZED', }; } diff --git a/src/services/crawl-validator/crawl_validator.service.ts b/src/services/crawl-validator/crawl_validator.service.ts index d724269ab..af0aa6ab6 100644 --- a/src/services/crawl-validator/crawl_validator.service.ts +++ b/src/services/crawl-validator/crawl_validator.service.ts @@ -111,7 +111,7 @@ export default class CrawlValidatorService extends BullableService { } const validatorInDB: Validator[] = await knex('validator').select('*'); - + const offchainMapped: Map = new Map(); await Promise.all( validators.map(async (validator) => { const foundValidator = validatorInDB.find( @@ -123,6 +123,9 @@ export default class CrawlValidatorService extends BullableService { if (!foundValidator) { validatorEntity = Validator.createNewValidator(validator); } else { + // mark this offchain validator is mapped with onchain + offchainMapped.set(validator.operator_address, true); + validatorEntity = foundValidator; validatorEntity.jailed = validator.jailed; validatorEntity.status = validator.status; @@ -146,6 +149,23 @@ export default class CrawlValidatorService extends BullableService { updateValidators = await this.loadCustomInfo(updateValidators); + // loop all validator not found onchain, update status is UNSPECIFIED + validatorInDB + .filter((val: any) => !offchainMapped.get(val.operator_address)) + .forEach(async (validatorNotOnchain: any) => { + this.logger.debug( + 'Account not found onchain: ', + validatorNotOnchain.operator_address + ); + validatorNotOnchain.status = Validator.STATUS.UNSPECIFIED; + + validatorNotOnchain.jailed_until = + validatorNotOnchain.jailed_until.toISOString(); + validatorNotOnchain.unbonding_time = + validatorNotOnchain.unbonding_time.toISOString(); + updateValidators.push(validatorNotOnchain); + }); + await Validator.query() .insert(updateValidators) .onConflict('operator_address') From 147fb9bd058277412ce5adaf4b4c277759f0317c Mon Sep 17 00:00:00 2001 From: Phan Anh Tuan Date: Tue, 1 Aug 2023 14:30:04 +0700 Subject: [PATCH 04/33] fix: remove token and delegator_shares when validator is UNRECOGNIZED --- src/services/crawl-validator/crawl_validator.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/crawl-validator/crawl_validator.service.ts b/src/services/crawl-validator/crawl_validator.service.ts index af0aa6ab6..6e0cbe132 100644 --- a/src/services/crawl-validator/crawl_validator.service.ts +++ b/src/services/crawl-validator/crawl_validator.service.ts @@ -157,7 +157,9 @@ export default class CrawlValidatorService extends BullableService { 'Account not found onchain: ', validatorNotOnchain.operator_address ); - validatorNotOnchain.status = Validator.STATUS.UNSPECIFIED; + validatorNotOnchain.status = Validator.STATUS.UNRECOGNIZED; + validatorNotOnchain.tokens = 0; + validatorNotOnchain.delegator_shares = 0; validatorNotOnchain.jailed_until = validatorNotOnchain.jailed_until.toISOString(); From 6fd8005890c3f92d55d9714bc50d05a2325e4388 Mon Sep 17 00:00:00 2001 From: Phan Anh Tuan Date: Thu, 3 Aug 2023 16:55:08 +0700 Subject: [PATCH 05/33] feat: add test for unrecognized validator --- .../crawl_validator.service.ts | 8 ++-- .../crawl-validator/crawl_validator.spec.ts | 47 +++++++++++++++++-- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/services/crawl-validator/crawl_validator.service.ts b/src/services/crawl-validator/crawl_validator.service.ts index 6e0cbe132..1a8412ef8 100644 --- a/src/services/crawl-validator/crawl_validator.service.ts +++ b/src/services/crawl-validator/crawl_validator.service.ts @@ -70,7 +70,6 @@ export default class CrawlValidatorService extends BullableService { .select('value') .limit(1) .offset(0); - await knex.transaction(async (trx) => { if (resultTx.length > 0) { await this.updateValidators(trx); @@ -110,7 +109,9 @@ export default class CrawlValidatorService extends BullableService { } } - const validatorInDB: Validator[] = await knex('validator').select('*'); + const validatorInDB: Validator[] = await knex('validator') + .select('*') + .whereNot('status', Validator.STATUS.UNRECOGNIZED); const offchainMapped: Map = new Map(); await Promise.all( validators.map(async (validator) => { @@ -149,7 +150,7 @@ export default class CrawlValidatorService extends BullableService { updateValidators = await this.loadCustomInfo(updateValidators); - // loop all validator not found onchain, update status is UNSPECIFIED + // loop all validator not found onchain, update status is UNRECOGNIZED validatorInDB .filter((val: any) => !offchainMapped.get(val.operator_address)) .forEach(async (validatorNotOnchain: any) => { @@ -160,6 +161,7 @@ export default class CrawlValidatorService extends BullableService { validatorNotOnchain.status = Validator.STATUS.UNRECOGNIZED; validatorNotOnchain.tokens = 0; validatorNotOnchain.delegator_shares = 0; + validatorNotOnchain.percent_voting_power = 0; validatorNotOnchain.jailed_until = validatorNotOnchain.jailed_until.toISOString(); diff --git a/test/unit/services/crawl-validator/crawl_validator.spec.ts b/test/unit/services/crawl-validator/crawl_validator.spec.ts index fccb4be8f..ebc87dd91 100644 --- a/test/unit/services/crawl-validator/crawl_validator.spec.ts +++ b/test/unit/services/crawl-validator/crawl_validator.spec.ts @@ -1,4 +1,4 @@ -import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; +import { AfterEach, BeforeEach, Describe, Test } from '@jest-decorated/core'; import { ServiceBroker } from 'moleculer'; import { BULL_JOB_NAME } from '../../../../src/common'; import { @@ -75,7 +75,7 @@ export default class CrawlValidatorTest { crawlSigningInfoService?: CrawlSigningInfoService; - @BeforeAll() + @BeforeEach() async initSuite() { await this.broker.start(); this.crawlSigningInfoService = this.broker.createService( @@ -97,7 +97,7 @@ export default class CrawlValidatorTest { await BlockCheckpoint.query().insert(this.blockCheckpoint); } - @AfterAll() + @AfterEach() async tearDown() { await Promise.all([ Validator.query().delete(true), @@ -128,4 +128,45 @@ export default class CrawlValidatorTest { )?.consensus_address ).toEqual('auravalcons1rvq6km74pua3pt9g7u5svm4r6mrw8z08walfep'); } + + @Test('Set validator not found onchain is UNRECOGNIZED') + public async testCrawlValidatorNotFoundOnchain() { + await Validator.query().insert( + Validator.fromJson({ + operator_address: 'xxx', + account_address: 'xxx', + consensus_address: 'xxx', + consensus_hex_address: 'xxx', + consensus_pubkey: {}, + jailed: false, + status: Validator.STATUS.UNBONDED, + tokens: 100, + delegator_shares: 100, + description: {}, + unbonding_height: 0, + unbonding_time: '1970-01-01 00:00:00+00', + commission: {}, + min_self_delegation: 0, + uptime: 0, + self_delegation_balance: 0, + percent_voting_power: 100, + start_height: 0, + index_offset: 0, + jailed_until: '1970-01-01 00:00:00+00', + tombstoned: false, + missed_blocks_counter: 0, + delegators_count: 0, + delegators_last_height: 0, + image_url: 'xxx', + }) + ); + + await this.crawlValidatorService?.handleCrawlAllValidator({}); + + const validator = await Validator.query().findOne({ + operator_address: 'xxx', + }); + expect(validator?.status).toEqual(Validator.STATUS.UNRECOGNIZED); + expect(validator?.tokens).toEqual('0'); + } } From d8eb803a649473c64c91f882815094e2c96d0bf7 Mon Sep 17 00:00:00 2001 From: Phan Anh Tuan Date: Mon, 7 Aug 2023 09:10:58 +0700 Subject: [PATCH 06/33] feat: add migration to add index time to block, tx --- .../20230807020810_add_time_index_block_tx.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 migrations/20230807020810_add_time_index_block_tx.ts diff --git a/migrations/20230807020810_add_time_index_block_tx.ts b/migrations/20230807020810_add_time_index_block_tx.ts new file mode 100644 index 000000000..b197a6741 --- /dev/null +++ b/migrations/20230807020810_add_time_index_block_tx.ts @@ -0,0 +1,19 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable('block', (table) => { + table.index('time'); + }); + await knex.schema.alterTable('transaction', (table) => { + table.index('timestamp'); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable('block', (table) => { + table.dropIndex('time'); + }); + await knex.schema.alterTable('transaction', (table) => { + table.dropIndex('timestamp'); + }); +} From 32d103a670e350dd6977ff48bea263c2c2eb976b Mon Sep 17 00:00:00 2001 From: Tuan Phan Anh <38557844+fibonacci998@users.noreply.github.com> Date: Tue, 15 Aug 2023 15:29:21 +0700 Subject: [PATCH 07/33] fix: allow whitelist query can have depth more than config (#312) --- src/services/api-gateways/graphiql.service.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/services/api-gateways/graphiql.service.ts b/src/services/api-gateways/graphiql.service.ts index 2030bf82a..4cad62184 100644 --- a/src/services/api-gateways/graphiql.service.ts +++ b/src/services/api-gateways/graphiql.service.ts @@ -54,24 +54,6 @@ export default class GraphiQLService extends BaseService { message: '', data: null, }; - let openBrackets = 0; - let isWhere = false; - for (let i = 0; i < query.length; i += 1) { - if (query.charAt(i) === '(') isWhere = true; - else if (query.charAt(i) === ')') isWhere = false; - - if (query.charAt(i) === '{' && !isWhere) openBrackets += 1; - else if (query.charAt(i) === '}' && !isWhere) openBrackets -= 1; - - if (openBrackets > config.graphiqlApi.depthLimit + 2) { - result = { - code: ErrorCode.WRONG, - message: ErrorMessage.VALIDATION_ERROR, - errors: `The query depth must not be greater than ${config.graphiqlApi.depthLimit}`, - }; - return result; - } - } let graphqlObj; try { @@ -109,6 +91,25 @@ export default class GraphiQLService extends BaseService { if ( !queryWhitelist.includes(query.replaceAll(' ', '').replaceAll('\n', '')) ) { + let openBrackets = 0; + let isWhere = false; + for (let i = 0; i < query.length; i += 1) { + if (query.charAt(i) === '(') isWhere = true; + else if (query.charAt(i) === ')') isWhere = false; + + if (query.charAt(i) === '{' && !isWhere) openBrackets += 1; + else if (query.charAt(i) === '}' && !isWhere) openBrackets -= 1; + + if (openBrackets > config.graphiqlApi.depthLimit + 2) { + result = { + code: ErrorCode.WRONG, + message: ErrorMessage.VALIDATION_ERROR, + errors: `The query depth must not be greater than ${config.graphiqlApi.depthLimit}`, + }; + return result; + } + } + const selections = ( graphqlObj.definitions[0] as OperationDefinitionNode ).selectionSet.selections as FieldNode[]; From 685bb4b658db4033d29173b19898fd7c592e33a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Th=C3=A0nh=20Phong?= <49814372+phamphong9981@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:28:56 +0700 Subject: [PATCH 08/33] feat: reindex cw20 service ( main ) (#277) * feat: reindex cw20 service * test: test cw20 reindexing * fix: idiot * refactor: code * feat: api admin --- ci/config.json.ci | 5 +- config.json | 5 +- src/common/constant.ts | 10 + src/models/cw20_activity.ts | 2 + src/models/cw20_contract.ts | 2 + src/models/cw20_holder.ts | 2 + src/models/cw20_total_holder_stats.ts | 2 + .../api-gateways/api_gateway.service.ts | 6 + .../api-gateways/cw20_admin.service.ts | 45 +++ src/services/cw20/cw20.service.ts | 98 +++-- src/services/cw20/cw20_reindexing.service.ts | 181 +++++++++ .../cw20/cw20_update_by_contract.service.ts | 2 +- test/unit/services/cw20/cw20.spec.ts | 357 +++++++++++++++--- .../services/cw20/cw20_reindexing.spec.ts | 161 ++++++++ 14 files changed, 807 insertions(+), 71 deletions(-) create mode 100644 src/services/api-gateways/cw20_admin.service.ts create mode 100644 src/services/cw20/cw20_reindexing.service.ts create mode 100644 test/unit/services/cw20/cw20_reindexing.spec.ts diff --git a/ci/config.json.ci b/ci/config.json.ci index 245de74f2..31bcf5173 100644 --- a/ci/config.json.ci +++ b/ci/config.json.ci @@ -108,7 +108,10 @@ "cw20": { "blocksPerCall": 100, "millisecondRepeatJob": 2000, - "key": "cw20" + "key": "cw20", + "reindexHistory": { + "limitRecordGet": 500 + } }, "dashboardStatistics": { "millisecondCrawl": 10000, diff --git a/config.json b/config.json index cd07b5711..e2b66e521 100644 --- a/config.json +++ b/config.json @@ -103,7 +103,10 @@ "cw20": { "blocksPerCall": 100, "millisecondRepeatJob": 2000, - "key": "cw20" + "key": "cw20", + "reindexHistory": { + "limitRecordGet": 500 + } }, "crawlContractEvent": { "key": "crawlContractEvent", diff --git a/src/common/constant.ts b/src/common/constant.ts index fcbed0f5f..624cb63ec 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -66,6 +66,8 @@ export const BULL_JOB_NAME = { REINDEX_CW721_HISTORY: 'reindex:cw721-history', HANDLE_MIGRATE_CONTRACT: 'handle:migrate-contract', JOB_REDECODE_TX: 'job:redecode-tx', + REINDEX_CW20_CONTRACT: 'reindex:cw20-contract', + REINDEX_CW20_HISTORY: 'reindex:cw20-history', }; export const SERVICE = { @@ -223,6 +225,14 @@ export const SERVICE = { path: 'v1.ReDecodeTx', }, }, + Cw20ReindexingService: { + key: 'Cw20ReindexingService', + name: 'v1.Cw20ReindexingService', + Reindexing: { + key: 'reindexing', + path: 'v1.Cw20ReindexingService.reindexing', + }, + }, }, }; diff --git a/src/models/cw20_activity.ts b/src/models/cw20_activity.ts index ac0646b53..19d8a6eb0 100644 --- a/src/models/cw20_activity.ts +++ b/src/models/cw20_activity.ts @@ -5,6 +5,8 @@ import { Cw20Contract } from './cw20_contract'; import { SmartContract } from './smart_contract'; export class Cw20Event extends BaseModel { + static softDelete = false; + [relation: string]: any; id!: number; diff --git a/src/models/cw20_contract.ts b/src/models/cw20_contract.ts index 7fd748023..b337cf097 100644 --- a/src/models/cw20_contract.ts +++ b/src/models/cw20_contract.ts @@ -26,6 +26,8 @@ export interface IContractInfo { name?: string; } export class Cw20Contract extends BaseModel { + static softDelete = false; + [relation: string]: any; id!: number; diff --git a/src/models/cw20_holder.ts b/src/models/cw20_holder.ts index 4b6b01ef4..ab4b00469 100644 --- a/src/models/cw20_holder.ts +++ b/src/models/cw20_holder.ts @@ -5,6 +5,8 @@ import { Cw20Contract } from './cw20_contract'; import { SmartContract } from './smart_contract'; export class CW20Holder extends BaseModel { + static softDelete = false; + [relation: string]: any; id?: number; diff --git a/src/models/cw20_total_holder_stats.ts b/src/models/cw20_total_holder_stats.ts index 7d5d8a846..b5bc50b2d 100644 --- a/src/models/cw20_total_holder_stats.ts +++ b/src/models/cw20_total_holder_stats.ts @@ -3,6 +3,8 @@ import BaseModel from './base'; import { Cw20Contract } from './cw20_contract'; export class CW20TotalHolderStats extends BaseModel { + static softDelete = false; + [relation: string]: any; date!: Date; diff --git a/src/services/api-gateways/api_gateway.service.ts b/src/services/api-gateways/api_gateway.service.ts index 9e02973e1..0503fd3a3 100644 --- a/src/services/api-gateways/api_gateway.service.ts +++ b/src/services/api-gateways/api_gateway.service.ts @@ -23,6 +23,12 @@ import { bullBoardMixin } from '../../mixins/bullBoard/bullBoard.mixin'; mappingPolicy: 'restrict', // allow action called with exact method whitelist: ['v2.dashboard-statistics.*', 'v2.graphql.*'], }, + { + path: '/admin', + autoAliases: true, // allow generate rest info (GET/PUT/POST...) in the services + mappingPolicy: 'restrict', // allow action called with exact method + whitelist: ['v1.cw20-admin.*'], + }, ], // empty cors object will have moleculer to generate handler for preflight request and CORS header which allow all origin cors: {}, diff --git a/src/services/api-gateways/cw20_admin.service.ts b/src/services/api-gateways/cw20_admin.service.ts new file mode 100644 index 000000000..f94fccd0e --- /dev/null +++ b/src/services/api-gateways/cw20_admin.service.ts @@ -0,0 +1,45 @@ +import { Post, Service } from '@ourparentcenter/moleculer-decorators-extended'; +import { Context, ServiceBroker } from 'moleculer'; +import networks from '../../../network.json' assert { type: 'json' }; +import BaseService from '../../base/base.service'; + +@Service({ + name: 'cw20-admin', + version: 1, +}) +export default class Cw20AdminService extends BaseService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @Post('/cw20-reindexing', { + name: 'cw20Reindexing', + params: { + chainid: { + type: 'string', + optional: false, + enum: networks.map((network) => network.chainId), + }, + contractAddress: { + type: 'string', + optional: false, + }, + }, + }) + async cw20ReindexingByChainId( + ctx: Context< + { chainid: string; contractAddress: string }, + Record + > + ) { + const selectedChain = networks.find( + (network) => network.chainId === ctx.params.chainid + ); + return this.broker.call( + `v1.Cw20ReindexingService.reindexing@${selectedChain?.moleculerNamespace}`, + { + contractAddress: ctx.params.contractAddress, + } + ); + } +} diff --git a/src/services/cw20/cw20.service.ts b/src/services/cw20/cw20.service.ts index 49cc86251..db63a1e93 100644 --- a/src/services/cw20/cw20.service.ts +++ b/src/services/cw20/cw20.service.ts @@ -1,4 +1,5 @@ import { Service } from '@ourparentcenter/moleculer-decorators-extended'; +import { Queue } from 'bullmq'; import { Knex } from 'knex'; import _ from 'lodash'; import { ServiceBroker } from 'moleculer'; @@ -34,6 +35,13 @@ export const CW20_ACTION = { BURN_FROM: 'burn_from', SEND_FROM: 'send_from', }; +export interface ICw20ReindexingHistoryParams { + smartContractId: number; + startBlock: number; + endBlock: number; + prevId: number; + contractAddress: string; +} @Service({ name: SERVICE.V1.Cw20.key, version: 1, @@ -205,41 +213,38 @@ export default class Cw20Service extends BullableService { } } - async getCw20ContractEvents(startBlock: number, endBlock: number) { + async getCw20ContractEvents( + startBlock: number, + endBlock: number, + smartContractId?: number, + page?: { prevId: number; limit: number } + ) { return SmartContractEvent.query() - .alias('smart_contract_event') - .withGraphJoined( - '[message(selectMessage), tx(selectTransaction), attributes(selectAttribute), smart_contract(selectSmartContract).code(selectCode)]' - ) - .modifiers({ - selectCode(builder) { - builder.select('type'); - }, - selectTransaction(builder) { - builder.select('hash', 'height'); - }, - selectMessage(builder) { - builder.select('sender', 'content'); - }, - selectAttribute(builder) { - builder.select('key', 'value'); - }, - selectSmartContract(builder) { - builder.select('address', 'id'); - }, - }) + .withGraphFetched('attributes(selectAttribute)') + .joinRelated('[message, tx, smart_contract.code]') .where('smart_contract:code.type', 'CW20') .where('tx.height', '>', startBlock) .andWhere('tx.height', '<=', endBlock) + .modify((builder) => { + if (smartContractId) { + builder.andWhere('smart_contract.id', smartContractId); + } + if (page) { + builder + .andWhere('smart_contract_event.id', '>', page.prevId) + .orderBy('smart_contract_event.id', 'asc') + .limit(page.limit); + } + }) .select( 'message.sender as sender', 'smart_contract.address as contract_address', 'smart_contract_event.action', - 'smart_contract_event.event_id as event_id', - 'smart_contract_event.index', + 'smart_contract_event.event_id', 'smart_contract.id as smart_contract_id', - 'tx.height as height', - 'smart_contract_event.id as smart_contract_event_id' + 'smart_contract_event.id as smart_contract_event_id', + 'tx.hash', + 'tx.height' ) .orderBy('smart_contract_event.id', 'asc'); } @@ -304,4 +309,45 @@ export default class Cw20Service extends BullableService { } return super._start(); } + + @QueueHandler({ + queueName: BULL_JOB_NAME.REINDEX_CW20_HISTORY, + jobName: BULL_JOB_NAME.REINDEX_CW20_HISTORY, + }) + public async reindexHistory(_payload: ICw20ReindexingHistoryParams) { + const { smartContractId, startBlock, endBlock, prevId, contractAddress } = + _payload; + // insert data from event_attribute_backup to event_attribute + const { limitRecordGet } = config.cw20.reindexHistory; + const events = await this.getCw20ContractEvents( + startBlock, + endBlock, + smartContractId, + { limit: limitRecordGet, prevId } + ); + if (events.length > 0) { + await knex.transaction(async (trx) => { + await this.handleCw20Histories(events, trx); + }); + await this.createJob( + BULL_JOB_NAME.REINDEX_CW20_HISTORY, + BULL_JOB_NAME.REINDEX_CW20_HISTORY, + { + smartContractId, + startBlock, + endBlock, + prevId: events[events.length - 1].smart_contract_event_id, + contractAddress, + } satisfies ICw20ReindexingHistoryParams, + { + removeOnComplete: true, + } + ); + } else { + const queue: Queue = this.getQueueManager().getQueue( + BULL_JOB_NAME.REINDEX_CW20_CONTRACT + ); + (await queue.getJob(contractAddress))?.remove(); + } + } } diff --git a/src/services/cw20/cw20_reindexing.service.ts b/src/services/cw20/cw20_reindexing.service.ts new file mode 100644 index 000000000..b2caf4cb8 --- /dev/null +++ b/src/services/cw20/cw20_reindexing.service.ts @@ -0,0 +1,181 @@ +import { + Action, + Service, +} from '@ourparentcenter/moleculer-decorators-extended'; +import _ from 'lodash'; +import { Context, ServiceBroker } from 'moleculer'; +import config from '../../../config.json' assert { type: 'json' }; +import BullableService, { QueueHandler } from '../../base/bullable.service'; +import { BULL_JOB_NAME, IContextUpdateCw20, SERVICE } from '../../common'; +import { + CW20Holder, + CW20TotalHolderStats, + Cw20Contract, + Cw20Event, + IHolderEvent, + SmartContract, +} from '../../models'; +import { ICw20ReindexingHistoryParams } from './cw20.service'; +import knex from '../../common/utils/db_connection'; + +export interface IAddressParam { + contractAddress: string; +} +interface ICw20ReindexingParams { + contractAddress: string; + smartContractId: number; +} +@Service({ + name: SERVICE.V1.Cw20ReindexingService.key, + version: 1, +}) +export default class Cw20ReindexingContract extends BullableService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @Action({ + name: SERVICE.V1.Cw20ReindexingService.Reindexing.key, + params: { + contractAddress: 'string', + }, + }) + public async reindexing(ctx: Context) { + const { contractAddress } = ctx.params; + const smartContract = await SmartContract.query() + .withGraphJoined('code') + .where('address', contractAddress) + .first() + .throwIfNotFound(); + + // check whether contract is Cw20 type -> throw error to user + if (smartContract.code.type === 'CW20') { + await this.createJob( + BULL_JOB_NAME.REINDEX_CW20_CONTRACT, + BULL_JOB_NAME.REINDEX_CW20_CONTRACT, + { + contractAddress, + smartContractId: smartContract.id, + } satisfies ICw20ReindexingParams, + { + jobId: contractAddress, + } + ); + } else { + throw new Error( + `Smart contract ${ctx.params.contractAddress} is not CW20 type` + ); + } + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.REINDEX_CW20_CONTRACT, + jobName: BULL_JOB_NAME.REINDEX_CW20_CONTRACT, + }) + async jobHandler(_payload: ICw20ReindexingParams): Promise { + const { smartContractId, contractAddress } = _payload; + const cw20Contract = await Cw20Contract.query() + .withGraphJoined('smart_contract') + .where('smart_contract.address', contractAddress) + .select(['cw20_contract.id']) + .first(); + // query + const contractInfo = ( + await Cw20Contract.getContractsInfo([contractAddress]) + )[0]; + let track = true; + let initBalances: IHolderEvent[] = []; + // get init address holder, init amount + try { + initBalances = await Cw20Contract.getInstantiateBalances(contractAddress); + } catch (error) { + track = false; + } + const minUpdatedHeightOwner = + _.min(initBalances.map((holder) => holder.event_height)) || 0; + const maxUpdatedHeightOwner = + _.max(initBalances.map((holder) => holder.event_height)) || 0; + let id = -1; + let lastUpdatedHeight = -1; + await knex.transaction(async (trx) => { + if (cw20Contract) { + await Cw20Event.query() + .delete() + .where('cw20_contract_id', cw20Contract.id) + .transacting(trx); + await CW20TotalHolderStats.query() + .delete() + .where('cw20_contract_id', cw20Contract.id) + .transacting(trx); + await CW20Holder.query() + .delete() + .where('cw20_contract_id', cw20Contract.id) + .transacting(trx); + await Cw20Contract.query().deleteById(cw20Contract.id).transacting(trx); + } + const newCw20Contract = await Cw20Contract.query() + .insertGraph({ + ...Cw20Contract.fromJson({ + smart_contract_id: smartContractId, + symbol: contractInfo?.symbol, + minter: contractInfo?.minter, + marketing_info: contractInfo?.marketing_info, + name: contractInfo?.name, + total_supply: initBalances.reduce( + (acc: string, curr: { address: string; amount: string }) => + (BigInt(acc) + BigInt(curr.amount)).toString(), + '0' + ), + track, + decimal: contractInfo?.decimal, + last_updated_height: minUpdatedHeightOwner, + }), + holders: initBalances.map((e) => ({ + address: e.address, + amount: e.amount, + last_updated_height: e.event_height, + })), + }) + .transacting(trx); + id = newCw20Contract.id; + lastUpdatedHeight = newCw20Contract.last_updated_height; + }); + // handle from minUpdatedHeightOwner to blockHeight + await this.broker.call( + SERVICE.V1.Cw20UpdateByContract.UpdateByContract.path, + { + cw20Contracts: [ + { + id, + last_updated_height: lastUpdatedHeight, + }, + ], + startBlock: minUpdatedHeightOwner, + endBlock: maxUpdatedHeightOwner, + } satisfies IContextUpdateCw20 + ); + // insert histories + await this.createJob( + BULL_JOB_NAME.REINDEX_CW20_HISTORY, + BULL_JOB_NAME.REINDEX_CW20_HISTORY, + { + smartContractId, + startBlock: config.crawlBlock.startBlock, + endBlock: maxUpdatedHeightOwner, + prevId: 0, + contractAddress, + } satisfies ICw20ReindexingHistoryParams, + { + removeOnComplete: true, + } + ); + } + + async _start(): Promise { + await this.broker.waitForServices([ + SERVICE.V1.Cw20.name, + SERVICE.V1.Cw20UpdateByContract.name, + ]); + return super._start(); + } +} diff --git a/src/services/cw20/cw20_update_by_contract.service.ts b/src/services/cw20/cw20_update_by_contract.service.ts index f4741a785..09c865863 100644 --- a/src/services/cw20/cw20_update_by_contract.service.ts +++ b/src/services/cw20/cw20_update_by_contract.service.ts @@ -78,7 +78,7 @@ export default class Cw20UpdateByContractService extends BullableService { const { startBlock, endBlock } = ctx.params; // eslint-disable-next-line no-restricted-syntax for (const cw20Contract of ctx.params.cw20Contracts) { - const startUpdateBlock = Math.min( + const startUpdateBlock = Math.max( startBlock, cw20Contract.last_updated_height ); diff --git a/test/unit/services/cw20/cw20.spec.ts b/test/unit/services/cw20/cw20.spec.ts index f14426e2d..d87ab76d6 100644 --- a/test/unit/services/cw20/cw20.spec.ts +++ b/test/unit/services/cw20/cw20.spec.ts @@ -4,6 +4,7 @@ import { BULL_JOB_NAME } from '../../../../src/common'; import knex from '../../../../src/common/utils/db_connection'; import { Block, + BlockCheckpoint, Code, Cw20Contract, Cw20Event, @@ -12,6 +13,7 @@ import { import { SmartContractEvent } from '../../../../src/models/smart_contract_event'; import Cw20Service from '../../../../src/services/cw20/cw20.service'; import Cw20UpdateByContractService from '../../../../src/services/cw20/cw20_update_by_contract.service'; +import CrawlContractEventService from '../../../../src/services/crawl-cosmwasm/crawl_contract_event.service'; @Describe('Test cw20 service') export default class Cw20 { @@ -19,6 +21,10 @@ export default class Cw20 { cw20Service = this.broker.createService(Cw20Service) as Cw20Service; + crawlContractEventService = this.broker.createService( + CrawlContractEventService + ) as CrawlContractEventService; + cw20UpdateByContractService = this.broker.createService( Cw20UpdateByContractService ) as Cw20UpdateByContractService; @@ -31,6 +37,36 @@ export default class Cw20 { data: {}, }); + codeId = { + ...Code.fromJson({ + creator: 'code_id_creator', + code_id: 100, + data_hash: 'code_id_data_hash', + instantiate_permission: { permission: '', address: '', addresses: [] }, + store_hash: 'code_id_store_hash', + store_height: 1000, + type: 'CW20', + }), + contracts: [ + { + name: 'Base Contract 2', + address: 'mock_contract_address', + creator: 'phamphong_creator', + code_id: 100, + instantiate_hash: 'abc', + instantiate_height: 300000, + }, + { + code_id: 100, + address: 'mock_contract_address_2', + name: 'name', + creator: 'phamphong_creator 2', + instantiate_hash: 'abc', + instantiate_height: 300000, + }, + ], + }; + txInsert = { ...Transaction.fromJson({ height: this.block.height, @@ -57,24 +93,114 @@ export default class Cw20 { content: {}, events: [ { - type: 'execute', block_height: this.block.height, source: 'TX_EVENT', + type: 'instantiate', attributes: [ { - block_height: this.block.height, index: 0, + block_height: this.block.height, composite_key: 'execute._contract_address', key: '_contract_address', - value: 'this.mockInitContract_2.smart_contract.address', + value: this.codeId.contracts[0].address, }, { + index: 1, + block_height: this.block.height, + composite_key: 'execute._contract_address', + key: 'code_id', + value: '6', + }, + ], + }, + { + block_height: this.block.height, + source: 'TX_EVENT', + type: 'wasm', + attributes: [ + { + index: 0, block_height: this.block.height, + composite_key: 'execute._contract_address', + key: '_contract_address', + value: this.codeId.contracts[0].address, + }, + { index: 1, - // tx_id: 1, + block_height: this.block.height, + composite_key: 'execute._contract_address', + key: 'action', + value: 'add_whitelist', + }, + { + index: 2, + block_height: this.block.height, + composite_key: 'execute._contract_address', + key: 'token_id', + value: 'test2', + }, + ], + }, + { + block_height: this.block.height, + source: 'TX_EVENT', + type: 'wasm', + attributes: [ + { + index: 2, + block_height: this.block.height, + composite_key: 'execute._contract_address', + key: '_contract_address', + value: this.codeId.contracts[1].address, + }, + { + index: 3, + block_height: this.block.height, + composite_key: 'execute._contract_address', + key: 'action', + value: 'add_mint_phase', + }, + { + index: 4, + block_height: this.block.height, + composite_key: 'execute._contract_address', + key: 'token_id', + value: 'test1', + }, + ], + }, + { + block_height: this.block.height, + source: 'TX_EVENT', + type: 'wasm', + attributes: [ + { + index: 0, + block_height: this.block.height, composite_key: 'execute._contract_address', key: '_contract_address', - value: 'fdgdgdfgdfg', + value: this.codeId.contracts[0].address, + }, + { + index: 1, + block_height: this.block.height, + composite_key: 'execute._contract_address', + key: 'action', + value: 'dfgdfgdfgdfg', + }, + { + index: 2, + block_height: this.block.height, + composite_key: 'execute._contract_address', + key: 'bcvbcb', + value: 'fdsdfsdf', + }, + { + index: 3, + block_height: this.block.height, + composite_key: 'execute._contract_address', + key: 'vbvbv', + value: 'sesesese', }, ], }, @@ -83,42 +209,16 @@ export default class Cw20 { ], }; - codeId = { - ...Code.fromJson({ - creator: 'code_id_creator', - code_id: 100, - data_hash: 'code_id_data_hash', - instantiate_permission: { permission: '', address: '', addresses: [] }, - store_hash: 'code_id_store_hash', - store_height: 1000, - type: 'CW721', + mockBlockCheckpoint = [ + BlockCheckpoint.fromJson({ + job_name: BULL_JOB_NAME.CRAWL_CONTRACT_EVENT, + height: this.block.height - 1, }), - contracts: [ - { - name: 'Base Contract 2', - address: 'mock_contract_address', - creator: 'phamphong_creator', - code_id: 100, - instantiate_hash: 'abc', - instantiate_height: 300000, - }, - { - code_id: 100, - address: 'mock_contract_address_2', - name: 'name', - creator: 'phamphong_creator 2', - instantiate_hash: 'abc', - instantiate_height: 300000, - }, - ], - }; - - smartContractEvent = SmartContractEvent.fromJson({ - smart_contract_id: 1, - action: 'huh', - event_id: '1', - index: 1, - }); + BlockCheckpoint.fromJson({ + job_name: BULL_JOB_NAME.CRAWL_SMART_CONTRACT, + height: this.block.height, + }), + ]; @BeforeAll() async initSuite() { @@ -126,12 +226,13 @@ export default class Cw20 { this.cw20UpdateByContractService.getQueueManager().stopAll(); await this.broker.start(); await knex.raw( - 'TRUNCATE TABLE code, cw20_contract, block, transaction RESTART IDENTITY CASCADE' + 'TRUNCATE TABLE code, cw20_contract, block, transaction, smart_contract_event, block_checkpoint RESTART IDENTITY CASCADE' ); await Block.query().insert(this.block); await Transaction.query().insertGraph(this.txInsert); await Code.query().insertGraph(this.codeId); - await SmartContractEvent.query().insert(this.smartContractEvent); + await BlockCheckpoint.query().insert(this.mockBlockCheckpoint); + await this.crawlContractEventService.jobHandler(); } @AfterAll() @@ -337,4 +438,176 @@ export default class Cw20 { await trx.rollback(); }); } + + @Test('test getCw20ContractEvent function') + public async testGetCw20ContractEvent() { + const extractData = await this.cw20Service.getCw20ContractEvents( + this.block.height - 1, + this.block.height + ); + expect( + extractData.map((data) => ({ + action: data.action, + sender: data.sender, + contractAddress: data.contract_address, + attributes: data.attributes, + hash: data.hash, + height: data.height, + })) + ).toEqual([ + { + action: 'instantiate', + sender: this.txInsert.messages[0].sender, + contractAddress: + this.txInsert.messages[0].events[0].attributes[0].value, + attributes: [ + this.txInsert.messages[0].events[0].attributes[0], + this.txInsert.messages[0].events[0].attributes[1], + ].map((attribute) => ({ key: attribute.key, value: attribute.value })), + hash: this.txInsert.hash, + height: this.txInsert.height, + }, + { + action: this.txInsert.messages[0].events[1].attributes[1].value, + sender: this.txInsert.messages[0].sender, + contractAddress: + this.txInsert.messages[0].events[1].attributes[0].value, + attributes: [ + this.txInsert.messages[0].events[1].attributes[0], + this.txInsert.messages[0].events[1].attributes[1], + this.txInsert.messages[0].events[1].attributes[2], + ].map((attribute) => ({ key: attribute.key, value: attribute.value })), + hash: this.txInsert.hash, + height: this.txInsert.height, + }, + { + action: this.txInsert.messages[0].events[2].attributes[1].value, + sender: this.txInsert.messages[0].sender, + contractAddress: + this.txInsert.messages[0].events[2].attributes[0].value, + attributes: [ + this.txInsert.messages[0].events[2].attributes[0], + this.txInsert.messages[0].events[2].attributes[1], + this.txInsert.messages[0].events[2].attributes[2], + ].map((attribute) => ({ key: attribute.key, value: attribute.value })), + hash: this.txInsert.hash, + height: this.txInsert.height, + }, + { + action: this.txInsert.messages[0].events[3].attributes[1].value, + sender: this.txInsert.messages[0].sender, + contractAddress: + this.txInsert.messages[0].events[3].attributes[0].value, + attributes: [ + this.txInsert.messages[0].events[3].attributes[0], + this.txInsert.messages[0].events[3].attributes[1], + this.txInsert.messages[0].events[3].attributes[2], + this.txInsert.messages[0].events[3].attributes[3], + ].map((attribute) => ({ key: attribute.key, value: attribute.value })), + hash: this.txInsert.hash, + height: this.txInsert.height, + }, + ]); + } + + @Test('test getCw20ContractEvent function by contract') + public async testGetCw20ContractEventByContract() { + const extractData = await this.cw20Service.getCw20ContractEvents( + this.block.height - 1, + this.block.height, + 1 + ); + expect( + extractData.map((data) => ({ + action: data.action, + sender: data.sender, + contractAddress: data.contract_address, + attributes: data.attributes, + hash: data.hash, + height: data.height, + })) + ).toEqual([ + { + action: 'instantiate', + sender: this.txInsert.messages[0].sender, + contractAddress: + this.txInsert.messages[0].events[0].attributes[0].value, + attributes: [ + this.txInsert.messages[0].events[0].attributes[0], + this.txInsert.messages[0].events[0].attributes[1], + ].map((attribute) => ({ key: attribute.key, value: attribute.value })), + hash: this.txInsert.hash, + height: this.txInsert.height, + }, + { + action: this.txInsert.messages[0].events[1].attributes[1].value, + sender: this.txInsert.messages[0].sender, + contractAddress: + this.txInsert.messages[0].events[1].attributes[0].value, + attributes: [ + this.txInsert.messages[0].events[1].attributes[0], + this.txInsert.messages[0].events[1].attributes[1], + this.txInsert.messages[0].events[1].attributes[2], + ].map((attribute) => ({ key: attribute.key, value: attribute.value })), + hash: this.txInsert.hash, + height: this.txInsert.height, + }, + { + action: this.txInsert.messages[0].events[3].attributes[1].value, + sender: this.txInsert.messages[0].sender, + contractAddress: + this.txInsert.messages[0].events[3].attributes[0].value, + attributes: [ + this.txInsert.messages[0].events[3].attributes[0], + this.txInsert.messages[0].events[3].attributes[1], + this.txInsert.messages[0].events[3].attributes[2], + this.txInsert.messages[0].events[3].attributes[3], + ].map((attribute) => ({ key: attribute.key, value: attribute.value })), + hash: this.txInsert.hash, + height: this.txInsert.height, + }, + ]); + const extractData2 = await this.cw20Service.getCw20ContractEvents( + this.block.height - 1, + this.block.height, + 1, + { prevId: 0, limit: 2 } + ); + expect( + extractData2.map((data) => ({ + action: data.action, + sender: data.sender, + contractAddress: data.contract_address, + attributes: data.attributes, + hash: data.hash, + height: data.height, + })) + ).toEqual([ + { + action: 'instantiate', + sender: this.txInsert.messages[0].sender, + contractAddress: + this.txInsert.messages[0].events[0].attributes[0].value, + attributes: [ + this.txInsert.messages[0].events[0].attributes[0], + this.txInsert.messages[0].events[0].attributes[1], + ].map((attribute) => ({ key: attribute.key, value: attribute.value })), + hash: this.txInsert.hash, + height: this.txInsert.height, + }, + { + action: this.txInsert.messages[0].events[1].attributes[1].value, + sender: this.txInsert.messages[0].sender, + contractAddress: + this.txInsert.messages[0].events[1].attributes[0].value, + attributes: [ + this.txInsert.messages[0].events[1].attributes[0], + this.txInsert.messages[0].events[1].attributes[1], + this.txInsert.messages[0].events[1].attributes[2], + ].map((attribute) => ({ key: attribute.key, value: attribute.value })), + hash: this.txInsert.hash, + height: this.txInsert.height, + }, + ]); + } } diff --git a/test/unit/services/cw20/cw20_reindexing.spec.ts b/test/unit/services/cw20/cw20_reindexing.spec.ts new file mode 100644 index 000000000..8d6eba414 --- /dev/null +++ b/test/unit/services/cw20/cw20_reindexing.spec.ts @@ -0,0 +1,161 @@ +import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; +import { ServiceBroker } from 'moleculer'; +import knex from '../../../../src/common/utils/db_connection'; +import Cw20ReindexingContract from '../../../../src/services/cw20/cw20_reindexing.service'; +import { Code, Cw20Contract, CW20Holder } from '../../../../src/models'; +import Cw20UpdateByContractService from '../../../../src/services/cw20/cw20_update_by_contract.service'; +import Cw20Service from '../../../../src/services/cw20/cw20.service'; + +@Describe('Test cw20 reindexing service') +export default class TestCw20ReindexingService { + codeId = { + ...Code.fromJson({ + creator: 'code_id_creator', + code_id: 100, + data_hash: 'code_id_data_hash', + instantiate_permission: { permission: '', address: '', addresses: [] }, + store_hash: 'code_id_store_hash', + store_height: 1000, + type: 'CW721', + }), + contracts: [ + { + name: 'Base Contract 2', + address: 'mock_contract_address', + creator: 'phamphong_creator', + code_id: 100, + instantiate_hash: 'abc', + instantiate_height: 300000, + }, + { + code_id: 100, + address: 'mock_contract_address_2', + name: 'name', + creator: 'phamphong_creator 2', + instantiate_hash: 'abc', + instantiate_height: 300000, + }, + ], + }; + + cw20Contract = { + ...Cw20Contract.fromJson({ + smart_contract_id: 1, + marketing_info: {}, + total_supply: '1121112133', + symbol: 'TEST SyMbol', + minter: 'jfglkdfjgklfdgklklfdkl', + name: 'dgbdfmnlkgsdfklgjksdfl', + track: true, + last_updated_height: 10000, + }), + holders: [ + { + address: 'holder_1', + amount: '123134134434', + last_updated_height: 8000, + }, + { + address: 'holder_2', + amount: '20032204', + last_updated_height: 8500, + }, + ], + }; + + broker = new ServiceBroker({ logger: false }); + + cw20reindexingService = this.broker.createService( + Cw20ReindexingContract + ) as Cw20ReindexingContract; + + cw20UpdateByContractService = this.broker.createService( + Cw20UpdateByContractService + ) as Cw20UpdateByContractService; + + cw20Service = this.broker.createService(Cw20Service) as Cw20Service; + + @BeforeAll() + async initSuite() { + await this.broker.start(); + await knex.raw( + 'TRUNCATE TABLE code, cw20_contract, block_checkpoint RESTART IDENTITY CASCADE' + ); + await Code.query().insertGraph(this.codeId); + } + + @AfterAll() + async tearDown() { + await this.broker.stop(); + } + + @Test('Test ReindexingService function') + public async testReindexingService() { + const mockContractInfo = { + address: this.codeId.contracts[0].address, + name: 'dgjkfjgdkg', + symbol: 'NNNJNJ', + minter: 'hfgjksghkjsf', + }; + const mockHolders = [ + { + address: 'holder_1', + amount: '123134134434', + event_height: 8000, + contract_address: this.codeId.contracts[0].address, + }, + { + address: 'holder_2', + amount: '20032204', + event_height: 8500, + contract_address: this.codeId.contracts[0].address, + }, + { + address: 'holder_3', + amount: '5467987', + event_height: 9600, + contract_address: this.codeId.contracts[0].address, + }, + { + address: 'holder_4', + amount: '11111111', + event_height: 23655422, + contract_address: this.codeId.contracts[0].address, + }, + ]; + this.cw20Service.reindexHistory = jest.fn(() => Promise.resolve()); + this.cw20UpdateByContractService.UpdateByContract = jest.fn(() => + Promise.resolve() + ); + Cw20Contract.getContractsInfo = jest.fn(() => + Promise.resolve([mockContractInfo]) + ); + Cw20Contract.getInstantiateBalances = jest.fn(() => + Promise.resolve(mockHolders) + ); + await this.cw20reindexingService.jobHandler({ + contractAddress: this.codeId.contracts[0].address, + smartContractId: 1, + }); + const cw20Contract = await Cw20Contract.query() + .withGraphJoined('smart_contract') + .where('smart_contract.address', this.codeId.contracts[0].address) + .first() + .throwIfNotFound(); + expect(cw20Contract.name).toEqual(mockContractInfo.name); + expect(cw20Contract.minter).toEqual(mockContractInfo.minter); + expect(cw20Contract.symbol).toEqual(mockContractInfo.symbol); + const cw20Holders = await CW20Holder.query() + .withGraphJoined('token') + .where('token.id', cw20Contract.id) + .orderBy('address', 'asc'); + expect( + cw20Holders.map((cw20Holder) => ({ + address: cw20Holder.address, + amount: cw20Holder.amount, + event_height: cw20Holder.last_updated_height, + contract_address: this.codeId.contracts[0].address, + })) + ).toEqual(mockHolders); + } +} From 92795eb9e99d37d45602b9d4ee801577436eb900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Th=C3=A0nh=20Phong?= <49814372+phamphong9981@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:53:07 +0700 Subject: [PATCH 09/33] refactor: cw20/cw721 index and add relation ( main ) (#306) * refactor: cw20 index and add relation * refactor: code --- .../20230809073622_index_cw20_models.ts | 27 +++++++++++++++++++ src/models/cw20_activity.ts | 22 +++++++++++++++ src/models/cw721_tx.ts | 13 +++++++++ 3 files changed, 62 insertions(+) create mode 100644 migrations/20230809073622_index_cw20_models.ts diff --git a/migrations/20230809073622_index_cw20_models.ts b/migrations/20230809073622_index_cw20_models.ts new file mode 100644 index 000000000..76f46a0ed --- /dev/null +++ b/migrations/20230809073622_index_cw20_models.ts @@ -0,0 +1,27 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable('cw20_activity', (table) => { + table.index('from'); + table.index('to'); + table.index('action'); + }); + await knex.schema.alterTable('cw721_activity', (table) => { + table.index('from'); + table.index('to'); + table.index('action'); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable('cw20_activity', (table) => { + table.dropIndex('from'); + table.dropIndex('to'); + table.dropIndex('action'); + }); + await knex.schema.alterTable('cw721_activity', (table) => { + table.dropIndex('from'); + table.dropIndex('to'); + table.dropIndex('action'); + }); +} diff --git a/src/models/cw20_activity.ts b/src/models/cw20_activity.ts index 19d8a6eb0..777b835ae 100644 --- a/src/models/cw20_activity.ts +++ b/src/models/cw20_activity.ts @@ -3,6 +3,8 @@ import BaseModel from './base'; // eslint-disable-next-line import/no-cycle import { Cw20Contract } from './cw20_contract'; import { SmartContract } from './smart_contract'; +import { SmartContractEvent } from './smart_contract_event'; +import { Event } from './event'; export class Cw20Event extends BaseModel { static softDelete = false; @@ -70,6 +72,26 @@ export class Cw20Event extends BaseModel { }, }, }, + smart_contract_event: { + relation: Model.BelongsToOneRelation, + modelClass: SmartContractEvent, + join: { + from: 'cw20_activity.smart_contract_event_id', + to: 'smart_contract_event.id', + }, + }, + event: { + relation: Model.HasOneThroughRelation, + modelClass: Event, + join: { + from: 'cw20_activity.smart_contract_event_id', + to: 'event.id', + through: { + from: 'smart_contract_event.id', + to: 'smart_contract_event.event_id', + }, + }, + }, }; } } diff --git a/src/models/cw721_tx.ts b/src/models/cw721_tx.ts index fd94f8abe..7ec84fbc3 100644 --- a/src/models/cw721_tx.ts +++ b/src/models/cw721_tx.ts @@ -4,6 +4,7 @@ import BaseModel from './base'; import CW721Contract from './cw721_contract'; import CW721Token from './cw721_token'; import { SmartContractEvent } from './smart_contract_event'; +import { Event } from './event'; export default class CW721Activity extends BaseModel { static softDelete = false; @@ -77,6 +78,18 @@ export default class CW721Activity extends BaseModel { to: 'smart_contract_event.id', }, }, + event: { + relation: Model.HasOneThroughRelation, + modelClass: Event, + join: { + from: 'cw721_activity.smart_contract_event_id', + to: 'event.id', + through: { + from: 'smart_contract_event.id', + to: 'smart_contract_event.event_id', + }, + }, + }, }; } } From b6b007c25a1e4cdb764fd84dfe4b137e26184b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Th=C3=A0nh=20Phong?= <49814372+phamphong9981@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:54:17 +0700 Subject: [PATCH 10/33] fix: duplicate latest migrate contract (#304) --- .../crawl-cosmwasm/crawl_smart_contract.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/services/crawl-cosmwasm/crawl_smart_contract.service.ts b/src/services/crawl-cosmwasm/crawl_smart_contract.service.ts index e6e9cff83..b4c82ca32 100644 --- a/src/services/crawl-cosmwasm/crawl_smart_contract.service.ts +++ b/src/services/crawl-cosmwasm/crawl_smart_contract.service.ts @@ -182,10 +182,12 @@ export default class CrawlSmartContractService extends BullableService { 'code_id', codeContractValues.map((contract) => contract.codeId) ), - SmartContract.query().whereIn( - 'address', - codeContractValues.map((contract) => contract.address) - ), + SmartContract.query() + .whereIn( + 'address', + codeContractValues.map((contract) => contract.address) + ) + .andWhere('status', SmartContract.STATUS.LATEST), ]); const codeContractsByKey = _.keyBy(contractsWithMigrateCode, 'code_id'); From 887ecfd00aa562c05b2ebd3a6fcb938031c215af Mon Sep 17 00:00:00 2001 From: Vu Ngoc Quang Date: Tue, 22 Aug 2023 15:29:55 +0700 Subject: [PATCH 11/33] Feat/statistics (#272) * feat: move statistic feature from v1 to v2 (not tested yet) * feat: change job params so it can aggregate data at a specific date * fix: query fields from exact table * fix: query exact fields * feat: add logger when done * feat: finish statistic jobs (not tested) * feat: create a separate interval job that gets the current date and create statistics jobs with date * feat: update logic using dayjs and lodash lib * feat: update cron jobs logic, move api actions to its service folder * fix: query event from db and group it based on event_id and tx_id * fix: just use a single job to query all daily data * fix: move all queries in daily_stats job into a single Promise all * fix: move dayjs.extend to after import statements * fix: remove _start * fix: only count native token in account_stats job * feat: add api to sync stats from prev dates * fix: support case when user just pass startDate to api * fix: fix whitelist graphql * fix: update column in account_statistic table, update bignum top_tx_sent * fix: remove drop index in modify account statistic table --------- Co-authored-by: AnDQK Co-authored-by: Phan Anh Tuan --- ci/config.json.ci | 10 + config.json | 10 + ..._create_table_daily_stats_account_stats.ts | 26 + ...6_modify_column_table_account_statistic.ts | 13 + package.json | 1 + src/common/constant.ts | 25 + src/common/utils/request.ts | 8 + src/models/account_statistics.ts | 56 + src/models/daily_statistics.ts | 37 + src/models/event.ts | 4 + src/models/event_attribute.ts | 25 +- src/models/index.ts | 2 + .../api-gateways/api_gateway.service.ts | 12 +- .../dashboard_statistics.service.ts | 52 - .../api-gateways/statistics.service.ts | 99 ++ .../statistics/account_statistics.service.ts | 420 +++++ .../statistics/api_statistics.service.ts | 91 + .../statistics/daily_statistics.service.ts | 127 ++ .../statistics/daily_stats_jobs.service.ts | 59 + .../dashboard_statistics.service.ts | 116 +- yarn.lock | 1551 +++++++++-------- 21 files changed, 1872 insertions(+), 872 deletions(-) create mode 100644 migrations/20230704102018_create_table_daily_stats_account_stats.ts create mode 100644 migrations/20230817015916_modify_column_table_account_statistic.ts create mode 100644 src/models/account_statistics.ts create mode 100644 src/models/daily_statistics.ts delete mode 100644 src/services/api-gateways/dashboard_statistics.service.ts create mode 100644 src/services/api-gateways/statistics.service.ts create mode 100644 src/services/statistics/account_statistics.service.ts create mode 100644 src/services/statistics/api_statistics.service.ts create mode 100644 src/services/statistics/daily_statistics.service.ts create mode 100644 src/services/statistics/daily_stats_jobs.service.ts diff --git a/ci/config.json.ci b/ci/config.json.ci index 31bcf5173..bf53142cf 100644 --- a/ci/config.json.ci +++ b/ci/config.json.ci @@ -162,6 +162,16 @@ "dispatchMilisecond": 1000, "batchSizeLimit": 10 }, + "dailyStatistics": { + "recordsPerCall": 100 + }, + "accountStatistics": { + "numberOfTopRecords": 10, + "dayRange": [3, 15, 30] + }, + "dailyStatsJobs": { + "jobPattern": "0 0 0 * * ?" + }, "jobRedecodeTx": { "limitRecordGet": 100 } diff --git a/config.json b/config.json index e2b66e521..d6766950a 100644 --- a/config.json +++ b/config.json @@ -162,6 +162,16 @@ "dispatchMilisecond": 1000, "batchSizeLimit": 10 }, + "dailyStatistics": { + "recordsPerCall": 100 + }, + "accountStatistics": { + "numberOfTopRecords": 10, + "dayRange": [3, 15, 30] + }, + "dailyStatsJobs": { + "jobPattern": "0 0 0 * * ?" + }, "jobRedecodeTx": { "limitRecordGet": 100 } diff --git a/migrations/20230704102018_create_table_daily_stats_account_stats.ts b/migrations/20230704102018_create_table_daily_stats_account_stats.ts new file mode 100644 index 000000000..20c24a761 --- /dev/null +++ b/migrations/20230704102018_create_table_daily_stats_account_stats.ts @@ -0,0 +1,26 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.createTable('daily_statistics', (table: any) => { + table.increments(); + table.bigint('daily_txs').index().notNullable(); + table.bigint('daily_active_addresses').index().notNullable(); + table.bigint('unique_addresses').index().notNullable(); + table.timestamp('date').unique().notNullable(); + }); + await knex.schema.createTable('account_statistics', (table: any) => { + table.increments(); + table.string('address').index().notNullable(); + table.bigint('amount_sent').index().notNullable(); + table.bigint('amount_received').index().notNullable(); + table.bigint('tx_sent').index().notNullable(); + table.bigint('gas_used').index().notNullable(); + table.timestamp('date').index().notNullable(); + table.unique(['address', 'date']); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.dropTable('account_statistics'); + await knex.schema.dropTable('daily_statistics'); +} diff --git a/migrations/20230817015916_modify_column_table_account_statistic.ts b/migrations/20230817015916_modify_column_table_account_statistic.ts new file mode 100644 index 000000000..0912a4d56 --- /dev/null +++ b/migrations/20230817015916_modify_column_table_account_statistic.ts @@ -0,0 +1,13 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable('account_statistics', (table) => { + table.integer('tx_sent').alter(); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable('account_statistics', (table) => { + table.bigint('tx_sent').alter(); + }); +} diff --git a/package.json b/package.json index 968b55113..a97b72840 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "bull": "^4.10.2", "bullmq": "^3.13.3", "cosmjs-types": "^0.6.1", + "dayjs": "^1.11.9", "dotenv": "^16.0.3", "file-type": "^18.4.0", "graphql": "^16.6.0", diff --git a/src/common/constant.ts b/src/common/constant.ts index 624cb63ec..9e08c3600 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -1,6 +1,7 @@ export const REDIS_KEY = { IBC_DENOM: 'ibc_denom', DASHBOARD_STATISTICS: 'dashboard_statistics', + TOP_ACCOUNTS: 'top_accounts', }; export const URL_TYPE_CONSTANTS = { @@ -62,6 +63,10 @@ export const BULL_JOB_NAME = { 'job:check-need-create-event-attr-partition', JOB_CREATE_EVENT_ATTR_PARTITION: 'job:create-event-attr-partition', CRAWL_GENESIS_FEEGRANT: 'crawl:genesis-feegrant', + CRAWL_DAILY_STATISTICS: 'crawl:daily-statistics', + CRAWL_ACCOUNT_STATISTICS: 'crawl:account-statistics', + HANDLE_TOP_ACCOUNTS: 'handle:top-accounts', + HANDLE_DAILY_STATS_JOBS: 'handle:daily-stats-jobs', REINDEX_CW721_CONTRACT: 'reindex:cw721-contract', REINDEX_CW721_HISTORY: 'reindex:cw721-history', HANDLE_MIGRATE_CONTRACT: 'handle:migrate-contract', @@ -225,6 +230,26 @@ export const SERVICE = { path: 'v1.ReDecodeTx', }, }, + DailyStatisticsService: { + key: 'DailyStatisticsService', + name: 'v1.DailyStatisticsService', + CreateSpecificDateJob: { + key: 'CreateSpecificDateJob', + path: 'v1.DailyStatisticsService.CreateSpecificDateJob', + }, + }, + AccountStatisticsService: { + key: 'AccountStatisticsService', + name: 'v1.AccountStatisticsService', + CreateSpecificDateJob: { + key: 'CreateSpecificDateJob', + path: 'v1.AccountStatisticsService.CreateSpecificDateJob', + }, + }, + DailyStatsJobsService: { + key: 'DailyStatsJobsService', + name: 'v1.DailyStatsJobsService', + }, Cw20ReindexingService: { key: 'Cw20ReindexingService', name: 'v1.Cw20ReindexingService', diff --git a/src/common/utils/request.ts b/src/common/utils/request.ts index 854947411..8be6126f8 100644 --- a/src/common/utils/request.ts +++ b/src/common/utils/request.ts @@ -8,3 +8,11 @@ export interface IProposalIdParam { export interface ITxIdsParam { txIds: number[]; } + +export interface IStatisticsParam { + date: string; +} + +export interface ICreateSpecificDateJob { + date: string; +} diff --git a/src/models/account_statistics.ts b/src/models/account_statistics.ts new file mode 100644 index 000000000..d097e61ee --- /dev/null +++ b/src/models/account_statistics.ts @@ -0,0 +1,56 @@ +import BaseModel from './base'; + +export class AccountStatistics extends BaseModel { + address!: string; + + amount_sent!: string; + + amount_received!: string; + + tx_sent!: number; + + gas_used!: string; + + date!: Date; + + static get tableName() { + return 'account_statistics'; + } + + static get jsonSchema() { + return { + type: 'object', + required: [ + 'address', + 'amount_sent', + 'amount_received', + 'tx_sent', + 'gas_used', + 'date', + ], + properties: { + address: { type: 'string' }, + amount_sent: { type: 'string' }, + amount_received: { type: 'string' }, + tx_sent: { type: 'number' }, + gas_used: { type: 'string' }, + date: { type: 'string', format: 'date-time' }, + }, + }; + } + + static get relationMappings() { + return {}; + } + + static newAccountStat(address: string, date: string) { + return AccountStatistics.fromJson({ + address, + amount_sent: '0', + amount_received: '0', + tx_sent: 0, + gas_used: '0', + date, + }); + } +} diff --git a/src/models/daily_statistics.ts b/src/models/daily_statistics.ts new file mode 100644 index 000000000..e9991a281 --- /dev/null +++ b/src/models/daily_statistics.ts @@ -0,0 +1,37 @@ +import BaseModel from './base'; + +export class DailyStatistics extends BaseModel { + daily_txs!: number; + + daily_active_addresses!: number; + + unique_addresses!: number; + + date!: Date; + + static get tableName() { + return 'daily_statistics'; + } + + static get jsonSchema() { + return { + type: 'object', + required: [ + 'daily_txs', + 'daily_active_addresses', + 'unique_addresses', + 'date', + ], + properties: { + daily_txs: { type: 'number' }, + daily_active_addresses: { type: 'number' }, + unique_addresses: { type: 'number' }, + date: { type: 'string', format: 'date-time' }, + }, + }; + } + + static get relationMappings() { + return {}; + } +} diff --git a/src/models/event.ts b/src/models/event.ts index e5cc1a9d1..3f983faea 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -89,6 +89,10 @@ export class Event extends BaseModel { REVOKE_FEEGRANT: 'revoke_feegrant', USE_FEEGRANT: 'use_feegrant', SET_FEEGRANT: 'set_feegrant', + COIN_SPENT: 'coin_spent', + COIN_RECEIVED: 'coin_received', + TX: 'tx', + TRANSFER: 'transfer', MIGRATE: 'migrate', }; } diff --git a/src/models/event_attribute.ts b/src/models/event_attribute.ts index 49200ac98..2fb47b872 100644 --- a/src/models/event_attribute.ts +++ b/src/models/event_attribute.ts @@ -2,6 +2,7 @@ import { Model } from 'objection'; import BaseModel from './base'; // eslint-disable-next-line import/no-cycle import { Event } from './event'; +import { Transaction } from './transaction'; export class EventAttribute extends BaseModel { event_id!: string; @@ -52,6 +53,14 @@ export class EventAttribute extends BaseModel { to: 'event.id', }, }, + transaction: { + relation: Model.BelongsToOneRelation, + modelClass: Transaction, + join: { + from: 'event_attribute.tx_id', + to: 'transaction.id', + }, + }, }; } @@ -61,13 +70,10 @@ export class EventAttribute extends BaseModel { REDELEGATION_RESPONSES: 'redelegation_responses', UNBONDING_RESPONSES: 'unbonding_responses', ACTION: 'action', - TRANSFER: 'transfer', SENDER: 'sender', RECEIVER: 'receiver', SPENDER: 'spender', RECIPIENT: 'recipient', - COIN_RECEIVED: 'coin_received', - COIN_SPENT: 'coin_spent', WITHDRAW_REWARDS: 'withdraw_rewards', AMOUNT: 'amount', VALIDATOR: 'validator', @@ -90,5 +96,18 @@ export class EventAttribute extends BaseModel { GRANTER: 'granter', GRANTEE: 'grantee', FROM: 'from', + FEE: 'fee', + FEE_PAYER: 'fee_payer', + }; + + static ATTRIBUTE_COMPOSITE_KEY = { + COIN_SPENT_SPENDER: 'coin_spent.spender', + COIN_RECEIVED_RECEIVER: 'coin_received.receiver', + COIN_SPENT_AMOUNT: 'coin_spent.amount', + COIN_RECEIVED_AMOUNT: 'coin_received.amount', + USE_FEEGRANT_GRANTER: 'use_feegrant.granter', + USE_FEEGRANT_GRANTEE: 'use_feegrant.grantee', + TX_FEE: 'tx.fee', + TX_FEE_PAYER: 'tx.fee_payer', }; } diff --git a/src/models/index.ts b/src/models/index.ts index 3ab4c3c6e..9ee83f061 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -22,4 +22,6 @@ export * from './delegator'; export * from './code_id_verification'; export * from './feegrant'; export * from './feegrant_history'; +export * from './daily_statistics'; +export * from './account_statistics'; export * from './cw20_total_holder_stats'; diff --git a/src/services/api-gateways/api_gateway.service.ts b/src/services/api-gateways/api_gateway.service.ts index 0503fd3a3..9ac09560a 100644 --- a/src/services/api-gateways/api_gateway.service.ts +++ b/src/services/api-gateways/api_gateway.service.ts @@ -21,7 +21,17 @@ import { bullBoardMixin } from '../../mixins/bullBoard/bullBoard.mixin'; path: '/api', autoAliases: true, // allow generate rest info (GET/PUT/POST...) in the services mappingPolicy: 'restrict', // allow action called with exact method - whitelist: ['v2.dashboard-statistics.*', 'v2.graphql.*'], + whitelist: [ + 'v2.graphql.*', + 'v2.statistics.getDashboardStatisticsByChainId', + 'v2.statistics.getTopAccountsByChainId', + ], + }, + { + path: '/admin', + autoAliases: true, // allow generate rest info (GET/PUT/POST...) in the services + mappingPolicy: 'restrict', // allow action called with exact method + whitelist: ['v2.statistics.syncPrevDateStatsByChainId'], }, { path: '/admin', diff --git a/src/services/api-gateways/dashboard_statistics.service.ts b/src/services/api-gateways/dashboard_statistics.service.ts deleted file mode 100644 index 4c378b14e..000000000 --- a/src/services/api-gateways/dashboard_statistics.service.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - Action, - Get, - Service, -} from '@ourparentcenter/moleculer-decorators-extended'; -import { Context, ServiceBroker } from 'moleculer'; -import { REDIS_KEY } from '../../common'; -import BaseService from '../../base/base.service'; -import networks from '../../../network.json' assert { type: 'json' }; - -@Service({ - name: 'dashboard-statistics', - version: 2, -}) -export default class DashboardStatisticsService extends BaseService { - public constructor(public broker: ServiceBroker) { - super(broker); - } - - @Get('/', { - name: 'getDashboardStatisticsByChainId', - params: { - chainid: { - type: 'string', - optional: false, - enum: networks.map((network) => network.chainId), - }, - }, - }) - async getDashboardStatisticsByChainId( - ctx: Context<{ chainid: string }, Record> - ) { - const selectedChain = networks.find( - (network) => network.chainId === ctx.params.chainid - ); - - return this.broker.call( - `v2.dashboard-statistics.getDashboardStatistics@${selectedChain?.moleculerNamespace}` - ); - } - - @Action({ - name: 'getDashboardStatistics', - params: {}, - }) - async getDashboardStatistics() { - const result = await this.broker.cacher?.get( - REDIS_KEY.DASHBOARD_STATISTICS - ); - return result; - } -} diff --git a/src/services/api-gateways/statistics.service.ts b/src/services/api-gateways/statistics.service.ts new file mode 100644 index 000000000..aae362274 --- /dev/null +++ b/src/services/api-gateways/statistics.service.ts @@ -0,0 +1,99 @@ +import { + Get, + Post, + Service, +} from '@ourparentcenter/moleculer-decorators-extended'; +import { Context, ServiceBroker } from 'moleculer'; +import BaseService from '../../base/base.service'; +import networks from '../../../network.json' assert { type: 'json' }; + +@Service({ + name: 'statistics', + version: 2, +}) +export default class StatisticsService extends BaseService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @Get('/dashboard', { + name: 'getDashboardStatisticsByChainId', + params: { + chainid: { + type: 'string', + optional: false, + enum: networks.map((network) => network.chainId), + }, + }, + }) + async getDashboardStatisticsByChainId( + ctx: Context<{ chainid: string }, Record> + ) { + const selectedChain = networks.find( + (network) => network.chainId === ctx.params.chainid + ); + + return this.broker.call( + `v2.api-statistics.getDashboardStatistics@${selectedChain?.moleculerNamespace}` + ); + } + + @Get('/top-accounts', { + name: 'getTopAccountsByChainId', + params: { + chainid: { + type: 'string', + optional: false, + enum: networks.map((network) => network.chainId), + }, + }, + }) + async getTopAccountsByChainId( + ctx: Context<{ chainid: string }, Record> + ) { + const selectedChain = networks.find( + (network) => network.chainId === ctx.params.chainid + ); + + return this.broker.call( + `v2.api-statistics.getTopAccounts@${selectedChain?.moleculerNamespace}` + ); + } + + @Post('/sync-prev-date-stats', { + name: 'syncPrevDateStatsByChainId', + params: { + chainid: { + type: 'string', + optional: false, + enum: networks.map((network) => network.chainId), + }, + startDate: { + type: 'string', + optional: false, + }, + endDate: { + type: 'string', + optional: true, + }, + }, + }) + async syncPrevDateStatsByChainId( + ctx: Context< + { chainid: string; startDate: string; endDate: string }, + Record + > + ) { + const selectedChain = networks.find( + (network) => network.chainId === ctx.params.chainid + ); + + return this.broker.call( + `v2.api-statistics.syncPrevDateStats@${selectedChain?.moleculerNamespace}`, + { + startDate: ctx.params.startDate, + endDate: ctx.params.endDate ?? null, + } + ); + } +} diff --git a/src/services/statistics/account_statistics.service.ts b/src/services/statistics/account_statistics.service.ts new file mode 100644 index 000000000..dd97d64bf --- /dev/null +++ b/src/services/statistics/account_statistics.service.ts @@ -0,0 +1,420 @@ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-case-declarations */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { + Action, + Service, +} from '@ourparentcenter/moleculer-decorators-extended'; +import { Context, ServiceBroker } from 'moleculer'; +import { parseCoins } from '@cosmjs/proto-signing'; +import BigNumber from 'bignumber.js'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import _ from 'lodash'; +import { AccountStatistics, EventAttribute, Transaction } from '../../models'; +import { + BULL_JOB_NAME, + IStatisticsParam, + ICreateSpecificDateJob, + REDIS_KEY, + SERVICE, +} from '../../common'; +import config from '../../../config.json' assert { type: 'json' }; +import BullableService, { QueueHandler } from '../../base/bullable.service'; +import knex from '../../common/utils/db_connection'; + +dayjs.extend(utc); + +@Service({ + name: SERVICE.V1.AccountStatisticsService.key, + version: 1, +}) +export default class AccountStatisticsService extends BullableService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @Action({ + name: SERVICE.V1.AccountStatisticsService.CreateSpecificDateJob.key, + params: { + date: 'string', + }, + }) + public async actionCreateSpecificDateJob( + ctx: Context + ) { + await this.createJob( + BULL_JOB_NAME.CRAWL_ACCOUNT_STATISTICS, + BULL_JOB_NAME.CRAWL_ACCOUNT_STATISTICS, + { + date: ctx.params.date, + }, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + attempts: config.jobRetryAttempt, + backoff: config.jobRetryBackoff, + } + ); + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.CRAWL_ACCOUNT_STATISTICS, + jobName: BULL_JOB_NAME.CRAWL_ACCOUNT_STATISTICS, + // prefix: `horoscope-v2-${config.chainId}`, + }) + public async handleJob(_payload: IStatisticsParam): Promise { + const { date } = _payload; + const accountStats: any = {}; + + const endTime = dayjs.utc(date).startOf('day').toDate(); + const startTime = dayjs.utc(endTime).subtract(1, 'day').toDate(); + + const startTx = await Transaction.query() + .select('id') + .where('transaction.timestamp', '>=', startTime) + .limit(1) + .orderBy('id'); + const endTx = await Transaction.query() + .select('id') + .where('transaction.timestamp', '<', endTime) + .limit(1) + .orderBy('id', 'desc'); + + this.logger.info( + `Get account statistic events for day ${new Date(startTime)}` + ); + + await Promise.all([ + this.calculateSpendReceive( + startTx[0].id, + endTx[0].id, + accountStats, + startTime.toISOString() + ), + this.calculateGasUsedTxSent( + startTx[0].id, + endTx[0].id, + accountStats, + startTime.toISOString() + ), + ]); + + const dailyAccountStats = Object.keys(accountStats).map( + (acc) => accountStats[acc] + ); + + this.logger.info(`Insert new account statistics for date ${startTime}`); + if (dailyAccountStats.length > 0) { + await AccountStatistics.query().insert(dailyAccountStats); + } + + await this.createJob( + BULL_JOB_NAME.HANDLE_TOP_ACCOUNTS, + BULL_JOB_NAME.HANDLE_TOP_ACCOUNTS, + {}, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + attempts: config.jobRetryAttempt, + backoff: config.jobRetryBackoff, + } + ); + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.HANDLE_TOP_ACCOUNTS, + jobName: BULL_JOB_NAME.HANDLE_TOP_ACCOUNTS, + // prefix: `horoscope-v2-${config.chainId}`, + }) + public async handleTopAccounts(_payload: object): Promise { + const now = dayjs.utc().startOf('day').toDate(); + + const { dayRange } = config.accountStatistics; + const [threeDayStat, fifteenDayStat, thirtyDayStat] = await Promise.all([ + this.getStatsFromSpecificDaysAgo(dayRange[0], now), + this.getStatsFromSpecificDaysAgo(dayRange[1], now), + this.getStatsFromSpecificDaysAgo(dayRange[2], now), + ]); + + const topAccounts = { + three_days: this.calculateTop(threeDayStat), + fifteen_days: this.calculateTop(fifteenDayStat), + thirty_days: this.calculateTop(thirtyDayStat), + }; + + this.logger.info(`Update top accounts for day ${new Date(now)}`); + await this.broker.cacher?.set(REDIS_KEY.TOP_ACCOUNTS, topAccounts); + } + + private async calculateSpendReceive( + startTxId: number, + endTxId: number, + accountStats: any, + date: string + ) { + const dailyEvents: any[] = await EventAttribute.query() + .select(knex.raw('jsonb_agg(jsonb_build_object(composite_key, value))')) + .where('tx_id', '>=', startTxId) + .andWhere('tx_id', '<', endTxId) + .andWhere((builder) => + builder + // Get the address that actually spent or received token + .whereIn('composite_key', [ + EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_SPENT_SPENDER, + EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_SPENT_AMOUNT, + EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_RECEIVED_AMOUNT, + EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_RECEIVED_RECEIVER, + ]) + ) + .groupBy('event_id'); + + if (dailyEvents.length > 0) { + dailyEvents + .map((event) => Object.assign({}, ...event.jsonb_agg)) + .map( + (event) => + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_SPENT_SPENDER] ?? + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_RECEIVED_RECEIVER] + ) + .forEach((address) => { + if (!accountStats[address]) { + accountStats[address] = AccountStatistics.newAccountStat( + address, + date + ); + } + }); + + dailyEvents + .map((event) => Object.assign({}, ...event.jsonb_agg)) + .forEach((event) => { + if ( + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_SPENT_SPENDER] + ) { + const addrSpent = + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_SPENT_SPENDER]; + const amountSpent = parseCoins( + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_SPENT_AMOUNT] + )[0]; + + if (amountSpent.denom === config.networkDenom) { + accountStats[addrSpent].amount_sent = ( + BigInt(accountStats[addrSpent].amount_sent) + + BigInt(amountSpent.amount) + ).toString(); + } + } else if ( + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_RECEIVED_RECEIVER] + ) { + const addrReceived = + event[ + EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_RECEIVED_RECEIVER + ]; + const amountReceived = parseCoins( + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.COIN_RECEIVED_AMOUNT] + )[0]; + + if (amountReceived.denom === config.networkDenom) { + accountStats[addrReceived].amount_received = ( + BigInt(accountStats[addrReceived].amount_received) + + BigInt(amountReceived.amount) + ).toString(); + } + } + }); + } + } + + private async calculateGasUsedTxSent( + startTxId: number, + endTxId: number, + accountStats: any, + date: string + ) { + const feeEvents: any[] = await EventAttribute.query() + .joinRelated('transaction') + .select( + knex.raw('jsonb_agg(jsonb_build_object(composite_key, value))'), + 'gas_used' + ) + .where('tx_id', '>=', startTxId) + .andWhere('tx_id', '<', endTxId) + .andWhere((builder) => + builder + // If fee_grant is involved, then needs to track to the granters and grantees + .whereIn('composite_key', [ + EventAttribute.ATTRIBUTE_COMPOSITE_KEY.TX_FEE_PAYER, + EventAttribute.ATTRIBUTE_COMPOSITE_KEY.USE_FEEGRANT_GRANTEE, + ]) + ) + .groupBy('tx_id', 'gas_used'); + + if (feeEvents.length > 0) { + feeEvents + .map((event) => Object.assign({}, ...event.jsonb_agg)) + .map( + (event) => + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.TX_FEE_PAYER] ?? + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.USE_FEEGRANT_GRANTEE] + ) + .forEach((address) => { + if (!accountStats[address]) { + accountStats[address] = AccountStatistics.newAccountStat( + address, + date + ); + } + }); + + feeEvents + .map((event) => + Object.assign({ gas_used: event.gas_used }, ...event.jsonb_agg) + ) + .forEach((event) => { + let addr = event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.TX_FEE_PAYER]; + + if ( + event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.USE_FEEGRANT_GRANTEE] + ) { + addr = + event[ + EventAttribute.ATTRIBUTE_COMPOSITE_KEY.USE_FEEGRANT_GRANTEE + ]; + } + + accountStats[addr].tx_sent += 1; + accountStats[addr].gas_used = ( + BigInt(accountStats[addr].gas_used) + BigInt(event.gas_used) + ).toString(); + }); + } + } + + private async getStatsFromSpecificDaysAgo( + daysAgo: number, + endTime: Date + ): Promise { + const startTime = dayjs + .utc() + .subtract(daysAgo, 'day') + .startOf('day') + .toDate(); + + const result = await AccountStatistics.query() + .select('address') + .sum('amount_sent as amount_sent') + .sum('amount_received as amount_received') + .sum('tx_sent as tx_sent') + .sum('gas_used as gas_used') + .where('date', '>=', startTime) + .andWhere('date', '<', endTime) + .groupBy('address'); + + return result; + } + + private calculateTop(dayStat: AccountStatistics[]) { + let topAmountSent: any[] = []; + let topAmountReceived: any[] = []; + let topTxSent: any[] = []; + let topGasUsed: any[] = []; + + const dayStatAmountSent = dayStat + .reduce( + (init: bigint, accStat: AccountStatistics) => + init + BigInt(accStat.amount_sent), + BigInt(0) + ) + .toString(); + const dayStatAmountReceived = dayStat + .reduce( + (init: bigint, accStat: AccountStatistics) => + init + BigInt(accStat.amount_received), + BigInt(0) + ) + .toString(); + const dayStatTxSent = dayStat + .reduce( + (init: bigint, accStat: AccountStatistics) => + init + BigInt(accStat.tx_sent), + BigInt(0) + ) + .toString(); + const dayStatGasUsed = dayStat + .reduce( + (init: bigint, accStat: AccountStatistics) => + init + BigInt(accStat.gas_used), + BigInt(0) + ) + .toString(); + + dayStat.forEach((stat) => { + topAmountSent.push({ + address: stat.address, + amount: stat.amount_sent, + percentage: Number( + BigNumber(stat.amount_sent) + .multipliedBy(100) + .dividedBy(BigNumber(dayStatAmountSent)) + ), + }); + topAmountReceived.push({ + address: stat.address, + amount: stat.amount_received, + percentage: Number( + BigNumber(stat.amount_received) + .multipliedBy(100) + .dividedBy(BigNumber(dayStatAmountReceived)) + ), + }); + topTxSent.push({ + address: stat.address, + amount: stat.tx_sent, + percentage: Number( + BigNumber(stat.tx_sent) + .multipliedBy(100) + .dividedBy(BigNumber(dayStatTxSent)) + ), + }); + topGasUsed.push({ + address: stat.address, + amount: stat.gas_used, + percentage: Number( + BigNumber(stat.gas_used) + .multipliedBy(100) + .dividedBy(BigNumber(dayStatGasUsed)) + ), + }); + }); + + topAmountSent = _.orderBy(topAmountSent, 'percentage', 'desc').slice( + 0, + config.accountStatistics.numberOfTopRecords + ); + topAmountReceived = _.orderBy( + topAmountReceived, + 'percentage', + 'desc' + ).slice(0, config.accountStatistics.numberOfTopRecords); + topTxSent = _.orderBy(topTxSent, 'percentage', 'desc').slice( + 0, + config.accountStatistics.numberOfTopRecords + ); + topGasUsed = _.orderBy(topGasUsed, 'percentage', 'desc').slice( + 0, + config.accountStatistics.numberOfTopRecords + ); + + return { + top_amount_sent: topAmountSent, + top_amount_received: topAmountReceived, + top_tx_sent: topTxSent, + top_gas_used: topGasUsed, + }; + } +} diff --git a/src/services/statistics/api_statistics.service.ts b/src/services/statistics/api_statistics.service.ts new file mode 100644 index 000000000..e58abfaea --- /dev/null +++ b/src/services/statistics/api_statistics.service.ts @@ -0,0 +1,91 @@ +/* eslint-disable no-await-in-loop */ +import { + Action, + Service, +} from '@ourparentcenter/moleculer-decorators-extended'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import { Context, ServiceBroker } from 'moleculer'; +import { REDIS_KEY, SERVICE } from '../../common'; +import BaseService from '../../base/base.service'; +import { ErrorCode, ErrorMessage } from '../../common/types/errors'; + +dayjs.extend(utc); + +@Service({ + name: 'api-statistics', + version: 2, +}) +export default class ApiStatisticsService extends BaseService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @Action({ + name: 'getDashboardStatistics', + params: {}, + }) + async getDashboardStatistics() { + const result = await this.broker.cacher?.get( + REDIS_KEY.DASHBOARD_STATISTICS + ); + return result; + } + + @Action({ + name: 'getTopAccounts', + params: {}, + }) + async getTopAccounts() { + const result = await this.broker.cacher?.get(REDIS_KEY.TOP_ACCOUNTS); + return result; + } + + @Action({ + name: 'syncPrevDateStats', + params: { + startDate: { + type: 'string', + }, + endDate: { + type: 'string', + optional: true, + }, + }, + }) + async syncPrevDateStats( + ctx: Context< + { startDate: string; endDate: string }, + Record + > + ) { + // Since each stats job query data of the prev date, + // so the start and end date needs to change to the following date + const startTime = dayjs.utc(ctx.params.startDate).add(1, 'day').toDate(); + const endTime = ctx.params.endDate + ? dayjs.utc(ctx.params.endDate).add(1, 'day').toDate() + : dayjs.utc(ctx.params.startDate).add(1, 'day').toDate(); + + for ( + let date = startTime; + date <= endTime; + date.setDate(date.getDate() + 1) + ) { + await Promise.all([ + this.broker.call( + SERVICE.V1.DailyStatisticsService.CreateSpecificDateJob.path, + { date: date.toString() } + ), + this.broker.call( + SERVICE.V1.AccountStatisticsService.CreateSpecificDateJob.path, + { date: date.toString() } + ), + ]); + } + + return { + code: ErrorCode.SUCCESSFUL, + message: ErrorMessage.SUCCESSFUL, + }; + } +} diff --git a/src/services/statistics/daily_statistics.service.ts b/src/services/statistics/daily_statistics.service.ts new file mode 100644 index 000000000..775d93a5f --- /dev/null +++ b/src/services/statistics/daily_statistics.service.ts @@ -0,0 +1,127 @@ +import { + Action, + Service, +} from '@ourparentcenter/moleculer-decorators-extended'; +import { Context, ServiceBroker } from 'moleculer'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import { + Account, + DailyStatistics, + EventAttribute, + Transaction, +} from '../../models'; +import { + BULL_JOB_NAME, + ICreateSpecificDateJob, + IStatisticsParam, + SERVICE, +} from '../../common'; +import config from '../../../config.json' assert { type: 'json' }; +import BullableService, { QueueHandler } from '../../base/bullable.service'; +import Utils from '../../common/utils/utils'; + +dayjs.extend(utc); + +@Service({ + name: SERVICE.V1.DailyStatisticsService.key, + version: 1, +}) +export default class DailyStatisticsService extends BullableService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @Action({ + name: SERVICE.V1.DailyStatisticsService.CreateSpecificDateJob.key, + params: { + date: 'string', + }, + }) + public async actionCreateSpecificDateJob( + ctx: Context + ) { + await this.createJob( + BULL_JOB_NAME.CRAWL_DAILY_STATISTICS, + BULL_JOB_NAME.CRAWL_DAILY_STATISTICS, + { + date: ctx.params.date, + }, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + attempts: config.jobRetryAttempt, + backoff: config.jobRetryBackoff, + } + ); + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.CRAWL_DAILY_STATISTICS, + jobName: BULL_JOB_NAME.CRAWL_DAILY_STATISTICS, + // prefix: `horoscope-v2-${config.chainId}`, + }) + public async handleJob(_payload: IStatisticsParam): Promise { + const endTime = dayjs.utc(_payload.date).startOf('day').toDate(); + const startTime = dayjs.utc(endTime).subtract(1, 'day').toDate(); + this.logger.info( + `Get daily statistic events for day ${new Date(startTime)}` + ); + + const startTx = await Transaction.query() + .select('id') + .where('transaction.timestamp', '>=', startTime) + .limit(1) + .orderBy('id'); + const endTx = await Transaction.query() + .select('id') + .where('transaction.timestamp', '<', endTime) + .limit(1) + .orderBy('id', 'desc'); + + const [dailyTxs, dailyAddresses, uniqueAddrs] = await Promise.all([ + Transaction.query() + .count('id') + .where('timestamp', '>=', startTime) + .andWhere('timestamp', '<', endTime) + .andWhere('code', 0), + EventAttribute.query() + .distinct('value') + .where('tx_id', '>=', startTx[0].id) + .andWhere('tx_id', '<=', endTx[0].id) + .andWhere('value', 'like', `${config.networkPrefixAddress}%`), + Account.query().count('id'), + ]); + // TODO: Need to re-define if it just count normal addresses only or also the contract addresses + const activeAddrs = Array.from( + new Set( + dailyAddresses + .filter((event) => + Utils.isValidAccountAddress( + event.value, + config.networkPrefixAddress, + 20 + ) + ) + .map((event) => event.value) + ) + ); + + const dailyStat = DailyStatistics.fromJson({ + daily_txs: dailyTxs[0].count, + daily_active_addresses: activeAddrs.length, + unique_addresses: Number(uniqueAddrs[0].count), + date: startTime.toISOString(), + }); + + this.logger.info(`Insert new daily statistic for date ${startTime}`); + await DailyStatistics.query() + .insert(dailyStat) + .catch((error) => { + this.logger.error('Error insert new daily statistic record'); + this.logger.error(error); + }); + } +} diff --git a/src/services/statistics/daily_stats_jobs.service.ts b/src/services/statistics/daily_stats_jobs.service.ts new file mode 100644 index 000000000..668b53fc5 --- /dev/null +++ b/src/services/statistics/daily_stats_jobs.service.ts @@ -0,0 +1,59 @@ +import { Service } from '@ourparentcenter/moleculer-decorators-extended'; +import { ServiceBroker } from 'moleculer'; +import { BULL_JOB_NAME, SERVICE } from '../../common'; +import config from '../../../config.json' assert { type: 'json' }; +import BullableService, { QueueHandler } from '../../base/bullable.service'; + +@Service({ + name: SERVICE.V1.DailyStatsJobsService.key, + version: 1, +}) +export default class DailyStatsJobsService extends BullableService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.HANDLE_DAILY_STATS_JOBS, + jobName: BULL_JOB_NAME.HANDLE_DAILY_STATS_JOBS, + // prefix: `horoscope-v2-${config.chainId}`, + }) + public async handleJob(_payload: object): Promise { + const date = new Date().toString(); + + await Promise.all([ + this.broker.call( + SERVICE.V1.DailyStatisticsService.CreateSpecificDateJob.path, + { date } + ), + this.broker.call( + SERVICE.V1.AccountStatisticsService.CreateSpecificDateJob.path, + { date } + ), + ]); + } + + public async _start() { + await this.broker.waitForServices([ + SERVICE.V1.DailyStatisticsService.name, + SERVICE.V1.AccountStatisticsService.name, + ]); + + this.createJob( + BULL_JOB_NAME.HANDLE_DAILY_STATS_JOBS, + BULL_JOB_NAME.HANDLE_DAILY_STATS_JOBS, + {}, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + repeat: { + pattern: config.dailyStatsJobs.jobPattern, + }, + } + ); + + return super._start(); + } +} diff --git a/src/services/statistics/dashboard_statistics.service.ts b/src/services/statistics/dashboard_statistics.service.ts index 7727909be..b998d3d4e 100644 --- a/src/services/statistics/dashboard_statistics.service.ts +++ b/src/services/statistics/dashboard_statistics.service.ts @@ -32,76 +32,70 @@ export default class DashboardStatisticsService extends BullableService { // prefix: `horoscope-v2-${config.chainId}`, }) public async handleJob(_payload: object): Promise { - try { - this.logger.info('Update AuraScan dashboard statistics'); - this._lcdClient = await getLcdClient(); + this.logger.info('Update AuraScan dashboard statistics'); + this._lcdClient = await getLcdClient(); - const [totalBlocks, totalTxs, totalValidators] = await Promise.all([ - BlockCheckpoint.query().findOne('job_name', BULL_JOB_NAME.CRAWL_BLOCK), - Transaction.query().count('id'), - Validator.query(), - ]); + const [totalBlocks, totalTxs, totalValidators] = await Promise.all([ + BlockCheckpoint.query().findOne('job_name', BULL_JOB_NAME.CRAWL_BLOCK), + Transaction.query().count('id'), + Validator.query(), + ]); - const [communityPool, inflation, distribution, supply] = - await Promise.all([ - this._lcdClient.auranw.cosmos.distribution.v1beta1.communityPool(), - this._lcdClient.auranw.cosmos.mint.v1beta1.inflation(), - this._lcdClient.auranw.cosmos.distribution.v1beta1.params(), - this._lcdClient.auranw.cosmos.bank.v1beta1.supplyOf({ - denom: config.networkDenom, - }), - ]); - let bondedTokens = BigInt(0); - totalValidators - .filter( - (val) => - val.status === Validator.STATUS.BONDED && val.jailed === false - ) - .forEach((val) => { - bondedTokens += BigInt(val.tokens); - }); - const totalAura = supply.amount.amount; + const [communityPool, inflation, distribution, supply] = await Promise.all([ + this._lcdClient.auranw.cosmos.distribution.v1beta1.communityPool(), + this._lcdClient.auranw.cosmos.mint.v1beta1.inflation(), + this._lcdClient.auranw.cosmos.distribution.v1beta1.params(), + this._lcdClient.auranw.cosmos.bank.v1beta1.supplyOf({ + denom: config.networkDenom, + }), + ]); + let bondedTokens = BigInt(0); + totalValidators + .filter( + (val) => val.status === Validator.STATUS.BONDED && val.jailed === false + ) + .forEach((val) => { + bondedTokens += BigInt(val.tokens); + }); + const totalAura = supply.amount.amount; - const dashboardStatistics = { - total_blocks: totalBlocks?.height, - community_pool: communityPool.pool.find( - (pool: DecCoinSDKType) => pool.denom === config.networkDenom - ).amount, - total_transactions: Number(totalTxs[0].count), - total_validators: totalValidators.length, - total_active_validators: totalValidators.filter( - (val) => val.status === Validator.STATUS.BONDED - ).length, - total_inactive_validators: totalValidators.filter( - (val) => val.status === Validator.STATUS.UNBONDED - ).length, - bonded_tokens: bondedTokens.toString(), - inflation: inflation.inflation, - total_aura: totalAura, - staking_apr: Number( - BigNumber(inflation.inflation) - .multipliedBy( - BigNumber(1 - Number(distribution.params.community_tax)) - ) - .multipliedBy(BigNumber(totalAura)) - .dividedBy(BigNumber(bondedTokens.toString())) - .multipliedBy(100) - ), - }; + const dashboardStatistics = { + total_blocks: totalBlocks?.height, + community_pool: communityPool.pool.find( + (pool: DecCoinSDKType) => pool.denom === config.networkDenom + ).amount, + total_transactions: Number(totalTxs[0].count), + total_validators: totalValidators.length, + total_active_validators: totalValidators.filter( + (val) => val.status === Validator.STATUS.BONDED + ).length, + total_inactive_validators: totalValidators.filter( + (val) => val.status === Validator.STATUS.UNBONDED + ).length, + bonded_tokens: bondedTokens.toString(), + inflation: inflation.inflation, + total_aura: totalAura, + staking_apr: Number( + BigNumber(inflation.inflation) + .multipliedBy( + BigNumber(1 - Number(distribution.params.community_tax)) + ) + .multipliedBy(BigNumber(totalAura)) + .dividedBy(BigNumber(bondedTokens.toString())) + .multipliedBy(100) + ), + }; - await this.broker.cacher?.set( - REDIS_KEY.DASHBOARD_STATISTICS, - dashboardStatistics - ); - } catch (error) { - this.logger.error(error); - } + await this.broker.cacher?.set( + REDIS_KEY.DASHBOARD_STATISTICS, + dashboardStatistics + ); } public async _start() { this.createJob( BULL_JOB_NAME.HANDLE_DASHBOARD_STATISTICS, - 'crawl', + BULL_JOB_NAME.HANDLE_DASHBOARD_STATISTICS, {}, { removeOnComplete: true, diff --git a/yarn.lock b/yarn.lock index 5688a451e..4f62687f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + "@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -16,9 +21,9 @@ integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg== "@aura-nw/aurajs@^0.1.3-alpha": - version "0.1.3-alpha" - resolved "https://npm.pkg.github.com/download/@aura-nw/aurajs/0.1.3-alpha/89ae79c11848c0530c0cd46e94f7f1149786a5b1#89ae79c11848c0530c0cd46e94f7f1149786a5b1" - integrity sha512-BMsaxIVNK58ugn3kqnxoomnTAPh5D58b61dWDCcNV0SoRWBAbcbb/1Y82fDXznOR1S1nJc+H3hXw1B2HA1v/Fw== + version "0.1.3-beta-smart-account" + resolved "https://npm.pkg.github.com/download/@aura-nw/aurajs/0.1.3-beta-smart-account/6e88fd3bcd73d81fc73e1c9f7ea4fdf18be9b5e2#6e88fd3bcd73d81fc73e1c9f7ea4fdf18be9b5e2" + integrity sha512-C0fLBowtAG671SQLH3Z0FDcoQo/7yZRbJ2m9qJfB6EGUGgSWr3bIlTFC2dxEgiUkLamiKymgxMgNJZ4/gMlOIw== dependencies: "@babel/runtime" "^7.19.4" "@cosmjs/amino" "0.29.4" @@ -36,10 +41,10 @@ dependencies: "@babel/highlight" "^7.22.5" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.4", "@babel/compat-data@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.5.tgz#b1f6c86a02d85d2dd3368a2b67c09add8cd0c255" - integrity sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.4", "@babel/compat-data@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== "@babel/core@7.18.10": version "7.18.10" @@ -84,25 +89,25 @@ semver "^6.3.0" "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.5.tgz#d67d9747ecf26ee7ecd3ebae1ee22225fe902a89" - integrity sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg== + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" + integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.5" - "@babel/helper-module-transforms" "^7.22.5" - "@babel/helpers" "^7.22.5" - "@babel/parser" "^7.22.5" + "@babel/generator" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.9" + "@babel/helper-module-transforms" "^7.22.9" + "@babel/helpers" "^7.22.6" + "@babel/parser" "^7.22.7" "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" + "@babel/traverse" "^7.22.8" "@babel/types" "^7.22.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.2" - semver "^6.3.0" + semver "^6.3.1" "@babel/generator@7.18.12": version "7.18.12" @@ -123,10 +128,10 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/generator@^7.18.10", "@babel/generator@^7.21.4", "@babel/generator@^7.22.5", "@babel/generator@^7.7.2": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7" - integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA== +"@babel/generator@^7.18.10", "@babel/generator@^7.21.4", "@babel/generator@^7.22.7", "@babel/generator@^7.22.9", "@babel/generator@^7.7.2": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" + integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== dependencies: "@babel/types" "^7.22.5" "@jridgewell/gen-mapping" "^0.3.2" @@ -147,40 +152,40 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.4", "@babel/helper-compilation-targets@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz#fc7319fc54c5e2fa14b2909cf3c5fd3046813e02" - integrity sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.4", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" + integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== dependencies: - "@babel/compat-data" "^7.22.5" + "@babel/compat-data" "^7.22.9" "@babel/helper-validator-option" "^7.22.5" - browserslist "^4.21.3" + browserslist "^4.21.9" lru-cache "^5.1.1" - semver "^6.3.0" + semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz#2192a1970ece4685fbff85b48da2c32fcb130b7c" - integrity sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz#c36ea240bb3348f942f08b0fbe28d6d979fab236" + integrity sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" "@babel/helper-member-expression-to-functions" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.5" - semver "^6.3.0" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz#bb2bf0debfe39b831986a4efbf4066586819c6e4" - integrity sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A== + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6" + integrity sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" regexpu-core "^5.3.1" - semver "^6.3.0" + semver "^6.3.1" "@babel/helper-define-polyfill-provider@^0.3.2", "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -228,19 +233,16 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.21.2", "@babel/helper-module-transforms@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef" - integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw== +"@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.21.2", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" + integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-module-imports" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.5" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" - "@babel/types" "^7.22.5" "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" @@ -255,26 +257,22 @@ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== "@babel/helper-remap-async-to-generator@^7.18.9", "@babel/helper-remap-async-to-generator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz#14a38141a7bf2165ad38da61d61cf27b43015da2" - integrity sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g== + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz#53a25b7484e722d7efb9c350c75c032d4628de82" + integrity sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-wrap-function" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/helper-wrap-function" "^7.22.9" -"@babel/helper-replace-supers@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz#71bc5fb348856dea9fdc4eafd7e2e49f585145dc" - integrity sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg== +"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779" + integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-member-expression-to-functions" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" - "@babel/types" "^7.22.5" "@babel/helper-simple-access@^7.22.5": version "7.22.5" @@ -290,10 +288,10 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08" - integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ== +"@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== dependencies: "@babel/types" "^7.22.5" @@ -312,23 +310,22 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== -"@babel/helper-wrap-function@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz#44d205af19ed8d872b4eefb0d2fa65f45eb34f06" - integrity sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw== +"@babel/helper-wrap-function@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz#189937248c45b0182c1dcf32f3444ca153944cb9" + integrity sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q== dependencies: "@babel/helper-function-name" "^7.22.5" "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/helpers@^7.18.9", "@babel/helpers@^7.21.0", "@babel/helpers@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.5.tgz#74bb4373eb390d1ceed74a15ef97767e63120820" - integrity sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q== +"@babel/helpers@^7.18.9", "@babel/helpers@^7.21.0", "@babel/helpers@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd" + integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA== dependencies: "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" + "@babel/traverse" "^7.22.6" "@babel/types" "^7.22.5" "@babel/highlight@^7.22.5": @@ -345,10 +342,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.21.4", "@babel/parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea" - integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.20.7", "@babel/parser@^7.21.4", "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": + version "7.22.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" + integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.22.5" @@ -685,18 +682,18 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.21.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz#635d4e98da741fad814984639f4c0149eb0135e1" - integrity sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ== + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363" + integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.6" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.18.9", "@babel/plugin-transform-computed-properties@^7.20.7": @@ -826,9 +823,9 @@ "@babel/helper-replace-supers" "^7.22.5" "@babel/plugin-transform-optional-chaining@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz#1003762b9c14295501beb41be72426736bedd1e0" - integrity sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ== + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz#4bacfe37001fe1901117672875e931d439811564" + integrity sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -924,12 +921,12 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-typescript@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz#5c0f7adfc1b5f38c4dbc8f79b1f0f8074134bd7d" - integrity sha512-SMubA9S7Cb5sGSFFUlqxyClTA9zWJ8qGQrppNUm05LtFuN1ELRFNndkix4zUJrC9F+YivWwa1dHMSyo0e0N9dA== + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.9.tgz#91e08ad1eb1028ecc62662a842e93ecfbf3c7234" + integrity sha512-BnVR1CpKiuD0iobHPaM1iLvcwPYN2uVFAqoLVSpEDKWuOikoCv5HbKLxclhKYUXlWkX86DoZGtqI4XhbOsyrMg== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.9" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.22.5" @@ -1138,9 +1135,9 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.11.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.0", "@babel/runtime@^7.19.4", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" - integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" + integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== dependencies: regenerator-runtime "^0.13.11" @@ -1185,18 +1182,18 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.21.4", "@babel/traverse@^7.22.5", "@babel/traverse@^7.7.2": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.5.tgz#44bd276690db6f4940fdb84e1cb4abd2f729ccd1" - integrity sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ== +"@babel/traverse@^7.18.10", "@babel/traverse@^7.21.4", "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": + version "7.22.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" + integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== dependencies: "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.5" + "@babel/generator" "^7.22.7" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.5" - "@babel/parser" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.22.7" "@babel/types" "^7.22.5" debug "^4.1.0" globals "^11.1.0" @@ -1233,38 +1230,38 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@bull-board/api@5.2.1", "@bull-board/api@^5.1.1": - version "5.2.1" - resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-5.2.1.tgz#cda18d20c0b2a1ddc79d96f2313bb9043233d961" - integrity sha512-+WRK5pkjly8bVgedBY4f0zzuywFb9bbd2ABv8unvV5F9dWKc1+8W76xAycfvMQNshbWCjAJISWsc5Y/13xcT3Q== +"@bull-board/api@5.6.1", "@bull-board/api@^5.1.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-5.6.1.tgz#2dd149620110748cc5b64e8908a1f77737c7fceb" + integrity sha512-iq+VpvCt7v2kYCtapDDmgHFyTyHiCbJkSeYbgIDtMIspXfsKxz0ZWKsqaY/VOCRE26jAX2asK17S2XSrANEU0A== dependencies: redis-info "^3.0.8" "@bull-board/express@^5.1.1": - version "5.2.1" - resolved "https://registry.yarnpkg.com/@bull-board/express/-/express-5.2.1.tgz#90ffcc5bb6c3ef4db340c89f4d1b52c4c0d8e479" - integrity sha512-fW6FHka4EvQAqydGlDky6FYcf98HT2vjXWn/qhVbt+X7JoFB0WkMbCthoQy96Y7ac1+Cd8PbvTHYUktSArfSPg== + version "5.6.1" + resolved "https://registry.yarnpkg.com/@bull-board/express/-/express-5.6.1.tgz#616f81c16c159f5d162d0a6c27a423d63a6cdeb7" + integrity sha512-/K5lRN40c0mcu6ye+oMWrBb+luJaz4mbGOqrOoYZif5FJ0jOoyYy+WFc9LlMdSUarLFVX0dbOYV5RmYrUxZJkg== dependencies: - "@bull-board/api" "5.2.1" - "@bull-board/ui" "5.2.1" + "@bull-board/api" "5.6.1" + "@bull-board/ui" "5.6.1" ejs "3.1.7" express "4.17.3" -"@bull-board/ui@5.2.1": - version "5.2.1" - resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-5.2.1.tgz#40bc4e4553d0e9bedb95c1292c8cf4e1fc56e207" - integrity sha512-G5gazsvCXm8J2cVS4QEKctGLoXhh9GOFs8fooXEWGd5AAnJZrjGcvI3ICAa4lx8taxlLG0tyF9y3R39iwzccuw== +"@bull-board/ui@5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-5.6.1.tgz#da062505d5e3d7e0c485625abaffdf23c7f841bb" + integrity sha512-cy4fEXxOBHR5+3Ez3bfax+Cd2TUTPyuSYRyQWL0WIwd3bivn/3zov9IThcnHu1YmJ8rjmUNoMHz7JIDXwUg8zg== dependencies: - "@bull-board/api" "5.2.1" + "@bull-board/api" "5.6.1" "@commitlint/cli@^17.4.1": - version "17.6.5" - resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-17.6.5.tgz#3a8abd6499f9d4aeafe3bf9201338ccb868a14b9" - integrity sha512-3PQrWr/uo6lzF5k7n5QuosCYnzaxP9qGBp3jhWP0Vmsa7XA6wrl9ccPqfQyXpSbQE3zBROVO3TDqgPKe4tfmLQ== + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-17.6.7.tgz#1d352a6cfdb6b6a6ae49a959e6c13dcef1b63782" + integrity sha512-nzZmfO5KIOupYppn1MsnYX/80I+KDlxiwkks3CJT0XT+t34UgqGi3eSyEuzgcIjPlORk5/GMaAEiys78iLfGMg== dependencies: "@commitlint/format" "^17.4.4" - "@commitlint/lint" "^17.6.5" - "@commitlint/load" "^17.5.0" + "@commitlint/lint" "^17.6.7" + "@commitlint/load" "^17.6.7" "@commitlint/read" "^17.5.1" "@commitlint/types" "^17.4.4" execa "^5.0.0" @@ -1274,24 +1271,24 @@ yargs "^17.0.0" "@commitlint/config-conventional@^17.3.0": - version "17.6.5" - resolved "https://registry.yarnpkg.com/@commitlint/config-conventional/-/config-conventional-17.6.5.tgz#a8ec286e634a071329fe45dc4955032c2176aeb5" - integrity sha512-Xl9H9KLl86NZm5CYNTNF9dcz1xelE/EbvhWIWcYxG/rn3UWYWdWmmnX2q6ZduNdLFSGbOxzUpIx61j5zxbeXxg== + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/config-conventional/-/config-conventional-17.6.7.tgz#8469d977def36148615e9516b1a521e38ca27ddd" + integrity sha512-4oTpEUC0HRM54QRHBPMOJW1pETp7usxXn9RuNYNWHcmu8wi1mpws95hvS20u2n6HtIkTn0jfn7vHioCm4AGUTw== dependencies: conventional-changelog-conventionalcommits "^5.0.0" -"@commitlint/config-validator@^17.4.4": - version "17.4.4" - resolved "https://registry.yarnpkg.com/@commitlint/config-validator/-/config-validator-17.4.4.tgz#d0742705719559a101d2ee49c0c514044af6d64d" - integrity sha512-bi0+TstqMiqoBAQDvdEP4AFh0GaKyLFlPPEObgI29utoKEYoPQTvF0EYqIwYYLEoJYhj5GfMIhPHJkTJhagfeg== +"@commitlint/config-validator@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/config-validator/-/config-validator-17.6.7.tgz#c664d42a1ecf5040a3bb0843845150f55734df41" + integrity sha512-vJSncmnzwMvpr3lIcm0I8YVVDJTzyjy7NZAeXbTXy+MPUdAr9pKyyg7Tx/ebOQ9kqzE6O9WT6jg2164br5UdsQ== dependencies: "@commitlint/types" "^17.4.4" ajv "^8.11.0" -"@commitlint/ensure@^17.4.4": - version "17.4.4" - resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-17.4.4.tgz#a36e7719bdb9c2b86c8b8c2e852b463a7bfda5fa" - integrity sha512-AHsFCNh8hbhJiuZ2qHv/m59W/GRE9UeOXbkOqxYMNNg9pJ7qELnFcwj5oYpa6vzTSHtPGKf3C2yUFNy1GGHq6g== +"@commitlint/ensure@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-17.6.7.tgz#77a77a0c05e6a1c34589f59e82e6cb937101fc4b" + integrity sha512-mfDJOd1/O/eIb/h4qwXzUxkmskXDL9vNPnZ4AKYKiZALz4vHzwMxBSYtyL2mUIDeU9DRSpEUins8SeKtFkYHSw== dependencies: "@commitlint/types" "^17.4.4" lodash.camelcase "^4.3.0" @@ -1313,32 +1310,32 @@ "@commitlint/types" "^17.4.4" chalk "^4.1.0" -"@commitlint/is-ignored@^17.6.5": - version "17.6.5" - resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-17.6.5.tgz#cea24cd2031fe7d242590b91fab3352750887194" - integrity sha512-CQvAPt9gX7cuUbMrIaIMKczfWJqqr6m8IlJs0F2zYwyyMTQ87QMHIj5jJ5HhOaOkaj6dvTMVGx8Dd1I4xgUuoQ== +"@commitlint/is-ignored@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-17.6.7.tgz#711897f19180f1121ecf302a3c5496f9a920a59e" + integrity sha512-vqyNRqtbq72P2JadaoWiuoLtXIs9SaAWDqdtef6G2zsoXqKFc7vqj1f+thzVgosXG3X/5K9jNp+iYijmvOfc/g== dependencies: "@commitlint/types" "^17.4.4" - semver "7.5.0" + semver "7.5.2" -"@commitlint/lint@^17.6.5": - version "17.6.5" - resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-17.6.5.tgz#dfa437f14430c9874d6b1a3ba8a2d44b79780c02" - integrity sha512-BSJMwkE4LWXrOsiP9KoHG+/heSDfvOL/Nd16+ojTS/DX8HZr8dNl8l3TfVr/d/9maWD8fSegRGtBtsyGuugFrw== +"@commitlint/lint@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-17.6.7.tgz#fb49c2722749e3ef83e2b41258fc32531068a13b" + integrity sha512-TW+AozfuOFMrHn+jdwtz0IWu8REKFp0eryOvoBp2r8IXNc4KihKB1spAiUB6SFyHD6hVVeolz12aHnJ3Mb+xVQ== dependencies: - "@commitlint/is-ignored" "^17.6.5" - "@commitlint/parse" "^17.6.5" - "@commitlint/rules" "^17.6.5" + "@commitlint/is-ignored" "^17.6.7" + "@commitlint/parse" "^17.6.7" + "@commitlint/rules" "^17.6.7" "@commitlint/types" "^17.4.4" -"@commitlint/load@^17.5.0": - version "17.5.0" - resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-17.5.0.tgz#be45dbbb50aaf5eb7e8e940e1e0d6171d1426bab" - integrity sha512-l+4W8Sx4CD5rYFsrhHH8HP01/8jEP7kKf33Xlx2Uk2out/UKoKPYMOIRcDH5ppT8UXLMV+x6Wm5osdRKKgaD1Q== +"@commitlint/load@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-17.6.7.tgz#c63b18ca8942a8fc095ec7a7ff7aa52f3854f6ba" + integrity sha512-QZ2rJTbX55BQdYrCm/p6+hh/pFBgC9nTJxfsrK6xRPe2thiQzHN0AQDBqBwAirn6gIkHrjIbCbtAE6kiDYLjrw== dependencies: - "@commitlint/config-validator" "^17.4.4" + "@commitlint/config-validator" "^17.6.7" "@commitlint/execute-rule" "^17.4.0" - "@commitlint/resolve-extends" "^17.4.4" + "@commitlint/resolve-extends" "^17.6.7" "@commitlint/types" "^17.4.4" "@types/node" "*" chalk "^4.1.0" @@ -1356,10 +1353,10 @@ resolved "https://registry.yarnpkg.com/@commitlint/message/-/message-17.4.2.tgz#f4753a79701ad6db6db21f69076e34de6580e22c" integrity sha512-3XMNbzB+3bhKA1hSAWPCQA3lNxR4zaeQAQcHj0Hx5sVdO6ryXtgUBGGv+1ZCLMgAPRixuc6en+iNAzZ4NzAa8Q== -"@commitlint/parse@^17.6.5": - version "17.6.5" - resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-17.6.5.tgz#7b84b328a6a94ca08ab7c98c491d9d3dab68f09d" - integrity sha512-0zle3bcn1Hevw5Jqpz/FzEWNo2KIzUbc1XyGg6WrWEoa6GH3A1pbqNF6MvE6rjuy6OY23c8stWnb4ETRZyN+Yw== +"@commitlint/parse@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-17.6.7.tgz#b87c61213653d670f956faafe7783aef9ef13020" + integrity sha512-ibO03BgEns+JJpohpBZYD49mCdSNMg6fTv7vA5yqzEFWkBQk5NWhEBw2yG+Z1UClStIRkMkAYyI2HzoQG9tCQQ== dependencies: "@commitlint/types" "^17.4.4" conventional-changelog-angular "^5.0.11" @@ -1376,24 +1373,24 @@ git-raw-commits "^2.0.11" minimist "^1.2.6" -"@commitlint/resolve-extends@^17.4.4": - version "17.4.4" - resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-17.4.4.tgz#8f931467dea8c43b9fe38373e303f7c220de6fdc" - integrity sha512-znXr1S0Rr8adInptHw0JeLgumS11lWbk5xAWFVno+HUFVN45875kUtqjrI6AppmD3JI+4s0uZlqqlkepjJd99A== +"@commitlint/resolve-extends@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-17.6.7.tgz#9c53a4601c96ab2dd20b90fb35c988639307735d" + integrity sha512-PfeoAwLHtbOaC9bGn/FADN156CqkFz6ZKiVDMjuC2N5N0740Ke56rKU7Wxdwya8R8xzLK9vZzHgNbuGhaOVKIg== dependencies: - "@commitlint/config-validator" "^17.4.4" + "@commitlint/config-validator" "^17.6.7" "@commitlint/types" "^17.4.4" import-fresh "^3.0.0" lodash.mergewith "^4.6.2" resolve-from "^5.0.0" resolve-global "^1.0.0" -"@commitlint/rules@^17.6.5": - version "17.6.5" - resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-17.6.5.tgz#fabcacdde923e26ac5ef90d4b3f8fc05526bbaa1" - integrity sha512-uTB3zSmnPyW2qQQH+Dbq2rekjlWRtyrjDo4aLFe63uteandgkI+cc0NhhbBAzcXShzVk0qqp8SlkQMu0mgHg/A== +"@commitlint/rules@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-17.6.7.tgz#2dbf52e82b5bcb1c74445637c6a9974571ab54b6" + integrity sha512-x/SDwDTN3w3Gr5xkhrIORu96rlKCc8ZLYEMXRqi9+MB33st2mKcGvKa5uJuigHlbl3xm75bAAubATrodVrjguQ== dependencies: - "@commitlint/ensure" "^17.4.4" + "@commitlint/ensure" "^17.6.7" "@commitlint/message" "^17.4.2" "@commitlint/to-lines" "^17.4.0" "@commitlint/types" "^17.4.4" @@ -1768,14 +1765,14 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== -"@eslint/eslintrc@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" - integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== +"@eslint/eslintrc@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.0.tgz#82256f164cc9e0b59669efc19d57f8092706841d" + integrity sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.2" + espree "^9.6.0" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -1783,10 +1780,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.42.0": - version "8.42.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" - integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== +"@eslint/js@8.44.0": + version "8.44.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af" + integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw== "@fastify/busboy@^1.0.0": version "1.2.1" @@ -1860,28 +1857,28 @@ resolved "https://registry.yarnpkg.com/@jest-decorated/shared/-/shared-0.1.7.tgz#fe2a25c9fc6e79ecfa79a75b4473c0adf4122877" integrity sha512-zmr/uMl7xqIe5jjSiaM3vitcDYUnVC3rGlG7Z2lKI4nvGmCiGv7g9I9U3a48G4X8PQEf1aZP0bLW4SiJMrhDGA== -"@jest/console@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57" - integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== +"@jest/console@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.1.tgz#b48ba7b9c34b51483e6d590f46e5837f1ab5f639" + integrity sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.6.1" + jest-util "^29.6.1" slash "^3.0.0" -"@jest/core@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03" - integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== - dependencies: - "@jest/console" "^29.5.0" - "@jest/reporters" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" +"@jest/core@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.1.tgz#fac0d9ddf320490c93356ba201451825231e95f6" + integrity sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ== + dependencies: + "@jest/console" "^29.6.1" + "@jest/reporters" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" @@ -1889,81 +1886,81 @@ exit "^0.1.2" graceful-fs "^4.2.9" jest-changed-files "^29.5.0" - jest-config "^29.5.0" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" + jest-config "^29.6.1" + jest-haste-map "^29.6.1" + jest-message-util "^29.6.1" jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-resolve-dependencies "^29.5.0" - jest-runner "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - jest-watcher "^29.5.0" + jest-resolve "^29.6.1" + jest-resolve-dependencies "^29.6.1" + jest-runner "^29.6.1" + jest-runtime "^29.6.1" + jest-snapshot "^29.6.1" + jest-util "^29.6.1" + jest-validate "^29.6.1" + jest-watcher "^29.6.1" micromatch "^4.0.4" - pretty-format "^29.5.0" + pretty-format "^29.6.1" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" - integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== +"@jest/environment@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.1.tgz#ee358fff2f68168394b4a50f18c68278a21fe82f" + integrity sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A== dependencies: - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/fake-timers" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-mock "^29.5.0" + jest-mock "^29.6.1" -"@jest/expect-utils@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" - integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== +"@jest/expect-utils@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.1.tgz#ab83b27a15cdd203fe5f68230ea22767d5c3acc5" + integrity sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw== dependencies: jest-get-type "^29.4.3" -"@jest/expect@^29.0.1", "@jest/expect@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba" - integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== +"@jest/expect@^29.0.1", "@jest/expect@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.1.tgz#fef18265188f6a97601f1ea0a2912d81a85b4657" + integrity sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg== dependencies: - expect "^29.5.0" - jest-snapshot "^29.5.0" + expect "^29.6.1" + jest-snapshot "^29.6.1" -"@jest/fake-timers@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" - integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== +"@jest/fake-timers@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.1.tgz#c773efddbc61e1d2efcccac008139f621de57c69" + integrity sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.6.1" + jest-mock "^29.6.1" + jest-util "^29.6.1" -"@jest/globals@^29.3.1", "@jest/globals@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298" - integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== +"@jest/globals@^29.3.1", "@jest/globals@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.1.tgz#c8a8923e05efd757308082cc22893d82b8aa138f" + integrity sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/types" "^29.5.0" - jest-mock "^29.5.0" + "@jest/environment" "^29.6.1" + "@jest/expect" "^29.6.1" + "@jest/types" "^29.6.1" + jest-mock "^29.6.1" -"@jest/reporters@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b" - integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== +"@jest/reporters@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.1.tgz#3325a89c9ead3cf97ad93df3a427549d16179863" + integrity sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/console" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" @@ -1975,9 +1972,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-message-util "^29.6.1" + jest-util "^29.6.1" + jest-worker "^29.6.1" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -1990,40 +1987,40 @@ dependencies: "@sinclair/typebox" "^0.24.1" -"@jest/schemas@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" - integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== +"@jest/schemas@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" + integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== dependencies: - "@sinclair/typebox" "^0.25.16" + "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" - integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== +"@jest/source-map@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.0.tgz#bd34a05b5737cb1a99d43e1957020ac8e5b9ddb1" + integrity sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA== dependencies: - "@jridgewell/trace-mapping" "^0.3.15" + "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408" - integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== +"@jest/test-result@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.1.tgz#850e565a3f58ee8ca6ec424db00cb0f2d83c36ba" + integrity sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw== dependencies: - "@jest/console" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.6.1" + "@jest/types" "^29.6.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4" - integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== +"@jest/test-sequencer@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz#e3e582ee074dd24ea9687d7d1aaf05ee3a9b068e" + integrity sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg== dependencies: - "@jest/test-result" "^29.5.0" + "@jest/test-result" "^29.6.1" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.6.1" slash "^3.0.0" "@jest/transform@28.1.3": @@ -2047,22 +2044,22 @@ slash "^3.0.0" write-file-atomic "^4.0.1" -"@jest/transform@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9" - integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== +"@jest/transform@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.1.tgz#acb5606019a197cb99beda3c05404b851f441c92" + integrity sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.6.1" jest-regex-util "^29.4.3" - jest-util "^29.5.0" + jest-util "^29.6.1" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -2080,12 +2077,12 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jest/types@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" - integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== +"@jest/types@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" + integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== dependencies: - "@jest/schemas" "^29.4.3" + "@jest/schemas" "^29.6.0" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -2134,7 +2131,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": version "0.3.18" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== @@ -2429,10 +2426,10 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== -"@sinclair/typebox@^0.25.16": - version "0.25.24" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" - integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== "@sinonjs/commons@^3.0.0": version "3.0.0" @@ -2442,9 +2439,9 @@ type-detect "4.0.8" "@sinonjs/fake-timers@^10.0.2": - version "10.2.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.2.0.tgz#b3e322a34c5f26e3184e7f6115695f299c1b1194" - integrity sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg== + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== dependencies: "@sinonjs/commons" "^3.0.0" @@ -2568,9 +2565,9 @@ "@types/jest" "*" "@types/jest@*": - version "29.5.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.2.tgz#86b4afc86e3a8f3005b297ed8a72494f89e6395b" - integrity sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg== + version "29.5.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.3.tgz#7a35dc0044ffb8b56325c6802a4781a626b05777" + integrity sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -2613,14 +2610,14 @@ "@types/node" "*" "@types/node@*", "@types/node@>=13.7.0": - version "20.3.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" - integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== + version "20.4.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" + integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== "@types/node@^18.11.11": - version "18.16.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.18.tgz#85da09bafb66d4bc14f7c899185336d0c1736390" - integrity sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw== + version "18.16.19" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.19.tgz#cb03fca8910fdeb7595b755126a8a78144714eea" + integrity sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2685,87 +2682,87 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.40.1": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz#8d466aa21abea4c3f37129997b198d141f09e76f" - integrity sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg== + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" + integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.11" - "@typescript-eslint/type-utils" "5.59.11" - "@typescript-eslint/utils" "5.59.11" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/type-utils" "5.62.0" + "@typescript-eslint/utils" "5.62.0" debug "^4.3.4" - grapheme-splitter "^1.0.4" + graphemer "^1.4.0" ignore "^5.2.0" natural-compare-lite "^1.4.0" semver "^7.3.7" tsutils "^3.21.0" "@typescript-eslint/parser@^5.40.1": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.11.tgz#af7d4b7110e3068ce0b97550736de455e4250103" - integrity sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA== + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== dependencies: - "@typescript-eslint/scope-manager" "5.59.11" - "@typescript-eslint/types" "5.59.11" - "@typescript-eslint/typescript-estree" "5.59.11" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz#5d131a67a19189c42598af9fb2ea1165252001ce" - integrity sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q== +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== dependencies: - "@typescript-eslint/types" "5.59.11" - "@typescript-eslint/visitor-keys" "5.59.11" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/type-utils@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz#5eb67121808a84cb57d65a15f48f5bdda25f2346" - integrity sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g== +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== dependencies: - "@typescript-eslint/typescript-estree" "5.59.11" - "@typescript-eslint/utils" "5.59.11" + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.11.tgz#1a9018fe3c565ba6969561f2a49f330cf1fe8db1" - integrity sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA== +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/typescript-estree@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz#b2caaa31725e17c33970c1197bcd54e3c5f42b9f" - integrity sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA== +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== dependencies: - "@typescript-eslint/types" "5.59.11" - "@typescript-eslint/visitor-keys" "5.59.11" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.11.tgz#9dbff49dc80bfdd9289f9f33548f2e8db3c59ba1" - integrity sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg== +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.11" - "@typescript-eslint/types" "5.59.11" - "@typescript-eslint/typescript-estree" "5.59.11" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.59.11": - version "5.59.11" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz#dca561ddad169dc27d62396d64f45b2d2c3ecc56" - integrity sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA== +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== dependencies: - "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" JSONStream@^1.0.4: @@ -2794,10 +2791,10 @@ acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^8.4.1, acorn@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +acorn@^8.4.1, acorn@^8.9.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== aggregate-error@^3.0.0: version "3.1.0" @@ -2999,6 +2996,18 @@ array.prototype.flatmap@^1.3.1: es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" +arraybuffer.prototype.slice@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz#9b5ea3868a6eebc30273da577eb888381c0044bb" + integrity sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -3044,9 +3053,9 @@ available-typed-arrays@^1.0.5: integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== aws-sdk@^2.1377.0: - version "2.1396.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1396.0.tgz#14dc92f874aca039a329d5a96f9f894fd9159252" - integrity sha512-5tAzB4pO9mfwb4XbDIv7wj4IsxaLI+KEAUZ8CR80sh2OdsP9AVGtMGH61dH6DQbHxCiwtLyQuoy7gZEuXv2ldQ== + version "2.1418.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1418.0.tgz#3c04e3484b9c826492b1d1d10f4ba19f4860e2a0" + integrity sha512-6WDMJQAWKwVt+44+61c/SAXKpUSwToqBMeaqizhEe3GN8TWfxMc9RfCnsYIIwS+L+5hedmKC5oc6Fg2ujs8KUQ== dependencies: buffer "4.9.2" events "1.1.1" @@ -3093,12 +3102,12 @@ axios@^1.3.4: form-data "^4.0.0" proxy-from-env "^1.1.0" -babel-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" - integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== +babel-jest@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.1.tgz#a7141ad1ed5ec50238f3cd36127636823111233a" + integrity sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A== dependencies: - "@jest/transform" "^29.5.0" + "@jest/transform" "^29.6.1" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" babel-preset-jest "^29.5.0" @@ -3302,13 +3311,13 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browserslist@^4.21.3, browserslist@^4.21.5: - version "4.21.8" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.8.tgz#db2498e1f4b80ed199c076248a094935860b6017" - integrity sha512-j+7xYe+v+q2Id9qbBeCI8WX5NmZSRe8es1+0xntD/+gaWXznP8tFEkv5IgSaHf5dS1YwVMbX/4W6m937mj+wQw== +browserslist@^4.21.9: + version "4.21.9" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" + integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== dependencies: - caniuse-lite "^1.0.30001502" - electron-to-chromium "^1.4.428" + caniuse-lite "^1.0.30001503" + electron-to-chromium "^1.4.431" node-releases "^2.0.12" update-browserslist-db "^1.0.11" @@ -3368,9 +3377,9 @@ bull@^4.10.2, bull@^4.7.0: uuid "^8.3.0" bullmq@^3.13.3: - version "3.15.5" - resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-3.15.5.tgz#b8e80f47cd167a0ce77e34e47404adcb98447813" - integrity sha512-NotMUfU5wBAjZQgcl/F5UMUIs1DiXDRmZBN5BPuSQ841w7mYlaghTMGSy0H+kAwFa7388e7/5COTiX8EUxFo5w== + version "3.15.8" + resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-3.15.8.tgz#e8ec5b46b0b7d7ce57e509280d03745109411e05" + integrity sha512-k3uimHGhl5svqD7SEak+iI6c5DxeLOaOXzCufI9Ic0ST3nJr69v71TGR4cXCTXdgCff3tLec5HgoBnfyWjgn5A== dependencies: cron-parser "^4.6.0" glob "^8.0.3" @@ -3428,10 +3437,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001502: - version "1.0.30001502" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001502.tgz#f7e4a76eb1d2d585340f773767be1fefc118dca8" - integrity sha512-AZ+9tFXw1sS0o0jcpJQIXvFTOB/xGiQ4OQ2t98QX3NDn2EZTSRBC801gxrsGgViuq2ak/NLkNgSNEPtCr5lfKg== +caniuse-lite@^1.0.30001503: + version "1.0.30001517" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz#90fabae294215c3495807eb24fc809e11dc2f0a8" + integrity sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA== case@1.6.3: version "1.6.3" @@ -3599,9 +3608,9 @@ co@^4.6.0: integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== color-convert@^1.9.0: version "1.9.3" @@ -3654,7 +3663,7 @@ commander@^10.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== -commander@^9.1.0, commander@^9.4.1: +commander@^9.4.1: version "9.5.0" resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== @@ -3739,11 +3748,11 @@ cookie@0.4.2: integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.25.1: - version "3.31.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1" - integrity sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw== + version "3.31.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.1.tgz#5084ad1a46858df50ff89ace152441a63ba7aae0" + integrity sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA== dependencies: - browserslist "^4.21.5" + browserslist "^4.21.9" core-util-is@1.0.2: version "1.0.2" @@ -3842,6 +3851,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +dayjs@^1.11.9: + version "1.11.9" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" + integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== + db-errors@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/db-errors/-/db-errors-0.2.3.tgz#a6a38952e00b20e790f2695a6446b3c65497ffa2" @@ -3995,9 +4009,9 @@ dot-prop@^5.1.0: is-obj "^2.0.0" dotenv@^16.0.3: - version "16.1.4" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.1.4.tgz#67ac1a10cd9c25f5ba604e4e08bc77c0ebe0ca8c" - integrity sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw== + version "16.3.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" + integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== dotty@0.1.2: version "0.1.2" @@ -4029,10 +4043,10 @@ ejs@3.1.7: dependencies: jake "^10.8.5" -electron-to-chromium@^1.4.428: - version "1.4.428" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.428.tgz#c31fc88e854f49d8305cdabf6ec934ff1588a902" - integrity sha512-L7uUknyY286of0AYC8CKfgWstD0Smk2DvHDi9F0GWQhSH90Bzi7iDrmCbZKz75tYJxeGSAc7TYeKpmbjMDoh1w== +electron-to-chromium@^1.4.431: + version "1.4.466" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.466.tgz#17193d70f203da3d52a89c653b8d89f47a51d79d" + integrity sha512-TSkRvbXRXD8BwhcGlZXDsbI2lRoP8dvqR7LQnqQNk9KxXBc4tG8O+rTuXgTyIpEdiqSGKEBSqrxdqEntnjNncA== elliptic@^6.5.4: version "6.5.4" @@ -4075,17 +4089,18 @@ error-ex@^1.3.1: is-arrayish "^0.2.1" es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.21.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" - integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== + version "1.22.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" + integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== dependencies: array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.1" available-typed-arrays "^1.0.5" call-bind "^1.0.2" es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.2.0" + get-intrinsic "^1.2.1" get-symbol-description "^1.0.0" globalthis "^1.0.3" gopd "^1.0.1" @@ -4105,14 +4120,18 @@ es-abstract@^1.19.0, es-abstract@^1.20.4: object-inspect "^1.12.3" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" + regexp.prototype.flags "^1.5.0" + safe-array-concat "^1.0.0" safe-regex-test "^1.0.0" string.prototype.trim "^1.2.7" string.prototype.trimend "^1.0.6" string.prototype.trimstart "^1.0.6" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" typed-array-length "^1.0.4" unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" + which-typed-array "^1.1.10" es-set-tostringtag@^2.0.1: version "2.0.1" @@ -4243,9 +4262,9 @@ eslint-config-airbnb-base@^15.0.0: semver "^6.3.0" eslint-config-airbnb-typescript@^17.0.0: - version "17.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.0.0.tgz#360dbcf810b26bbcf2ff716198465775f1c49a07" - integrity sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g== + version "17.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz#fda960eee4a510f092a9a1c139035ac588937ddc" + integrity sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig== dependencies: eslint-config-airbnb-base "^15.0.0" @@ -4307,9 +4326,9 @@ eslint-scope@^5.1.1: estraverse "^4.1.1" eslint-scope@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" - integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== + version "7.2.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.1.tgz#936821d3462675f25a18ac5fd88a67cc15b393bd" + integrity sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -4320,14 +4339,14 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== eslint@^8.25.0: - version "8.42.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.42.0.tgz#7bebdc3a55f9ed7167251fe7259f75219cade291" - integrity sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A== + version "8.45.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.45.0.tgz#bab660f90d18e1364352c0a6b7c6db8edb458b78" + integrity sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.3" - "@eslint/js" "8.42.0" + "@eslint/eslintrc" "^2.1.0" + "@eslint/js" "8.44.0" "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -4339,7 +4358,7 @@ eslint@^8.25.0: escape-string-regexp "^4.0.0" eslint-scope "^7.2.0" eslint-visitor-keys "^3.4.1" - espree "^9.5.2" + espree "^9.6.0" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -4349,7 +4368,6 @@ eslint@^8.25.0: globals "^13.19.0" graphemer "^1.4.0" ignore "^5.2.0" - import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" @@ -4359,9 +4377,8 @@ eslint@^8.25.0: lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" + optionator "^0.9.3" strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" text-table "^0.2.0" esm@^3.2.25: @@ -4369,12 +4386,12 @@ esm@^3.2.25: resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^9.5.2: - version "9.5.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" - integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== +espree@^9.6.0: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^8.8.0" + acorn "^8.9.0" acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" @@ -4475,16 +4492,17 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" - integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== +expect@^29.0.0, expect@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.1.tgz#64dd1c8f75e2c0b209418f2b8d36a07921adfdf1" + integrity sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g== dependencies: - "@jest/expect-utils" "^29.5.0" + "@jest/expect-utils" "^29.6.1" + "@types/node" "*" jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-matcher-utils "^29.6.1" + jest-message-util "^29.6.1" + jest-util "^29.6.1" express@4.17.3: version "4.17.3" @@ -4573,9 +4591,9 @@ fast-diff@^1.1.2: integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + version "3.3.0" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0" + integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -4593,7 +4611,7 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fastest-validator@^1.16.0: +fastest-validator@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/fastest-validator/-/fastest-validator-1.17.0.tgz#0c032e9c42c40a237d24b20be187f732ebdcc062" integrity sha512-37U/JDP72QSFqcvNnO81f0Aeu9og+5I3mc55b2v2RbV0S2I7KvQEdBtrFeIvaYVgam1bDUgy9F9AK9HolByogA== @@ -4803,7 +4821,7 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== @@ -4897,15 +4915,15 @@ glob@8.0.3: once "^1.3.0" glob@^10.0.0: - version "10.2.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.7.tgz#9dd2828cd5bc7bd861e7738d91e7113dda41d7d8" - integrity sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA== + version "10.3.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.3.tgz#8360a4ffdd6ed90df84aa8d52f21f452e86a123b" + integrity sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw== dependencies: foreground-child "^3.1.0" jackspeak "^2.0.3" minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2" - path-scurry "^1.7.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@^7.2.0: version "7.2.3" @@ -4980,11 +4998,6 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - graphemer@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" @@ -4998,9 +5011,9 @@ graphql-tag@^2.12.6: tslib "^2.1.0" graphql@^16.6.0: - version "16.6.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" - integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== + version "16.7.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.7.1.tgz#11475b74a7bff2aefd4691df52a0eca0abd9b642" + integrity sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg== har-schema@^2.0.0: version "2.0.0" @@ -5343,7 +5356,7 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -ipaddr.js@^2.0.1: +ipaddr.js@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== @@ -5532,15 +5545,11 @@ is-text-path@^1.0.1: text-extensions "^1.0.0" is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: - version "1.1.10" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" + which-typed-array "^1.1.11" is-typedarray@~1.0.0: version "1.0.0" @@ -5564,6 +5573,11 @@ isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -5648,87 +5662,87 @@ jest-changed-files@^29.5.0: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.0.1, jest-circus@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317" - integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== +jest-circus@^29.0.1, jest-circus@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.1.tgz#861dab37e71a89907d1c0fabc54a0019738ed824" + integrity sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.6.1" + "@jest/expect" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^29.5.0" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-each "^29.6.1" + jest-matcher-utils "^29.6.1" + jest-message-util "^29.6.1" + jest-runtime "^29.6.1" + jest-snapshot "^29.6.1" + jest-util "^29.6.1" p-limit "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.6.1" pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67" - integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== +jest-cli@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.1.tgz#99d9afa7449538221c71f358f0fdd3e9c6e89f72" + integrity sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing== dependencies: - "@jest/core" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/types" "^29.6.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-config "^29.6.1" + jest-util "^29.6.1" + jest-validate "^29.6.1" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da" - integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== +jest-config@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.1.tgz#d785344509065d53a238224c6cdc0ed8e2f2f0dd" + integrity sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.5.0" - "@jest/types" "^29.5.0" - babel-jest "^29.5.0" + "@jest/test-sequencer" "^29.6.1" + "@jest/types" "^29.6.1" + babel-jest "^29.6.1" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.5.0" - jest-environment-node "^29.5.0" + jest-circus "^29.6.1" + jest-environment-node "^29.6.1" jest-get-type "^29.4.3" jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-runner "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-resolve "^29.6.1" + jest-runner "^29.6.1" + jest-util "^29.6.1" + jest-validate "^29.6.1" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.5.0" + pretty-format "^29.6.1" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" - integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== +jest-diff@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.1.tgz#13df6db0a89ee6ad93c747c75c85c70ba941e545" + integrity sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg== dependencies: chalk "^4.0.0" diff-sequences "^29.4.3" jest-get-type "^29.4.3" - pretty-format "^29.5.0" + pretty-format "^29.6.1" jest-docblock@^29.4.3: version "29.4.3" @@ -5737,28 +5751,28 @@ jest-docblock@^29.4.3: dependencies: detect-newline "^3.0.0" -jest-each@^29.0.1, jest-each@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" - integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== +jest-each@^29.0.1, jest-each@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.1.tgz#975058e5b8f55c6780beab8b6ab214921815c89c" + integrity sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" chalk "^4.0.0" jest-get-type "^29.4.3" - jest-util "^29.5.0" - pretty-format "^29.5.0" + jest-util "^29.6.1" + pretty-format "^29.6.1" -jest-environment-node@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967" - integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== +jest-environment-node@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.1.tgz#08a122dece39e58bc388da815a2166c58b4abec6" + integrity sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.6.1" + "@jest/fake-timers" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-mock "^29.6.1" + jest-util "^29.6.1" jest-get-type@^29.4.3: version "29.4.3" @@ -5784,32 +5798,32 @@ jest-haste-map@^28.1.3: optionalDependencies: fsevents "^2.3.2" -jest-haste-map@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de" - integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== +jest-haste-map@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.1.tgz#62655c7a1c1b349a3206441330fb2dbdb4b63803" + integrity sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" jest-regex-util "^29.4.3" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-util "^29.6.1" + jest-worker "^29.6.1" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" - integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== +jest-leak-detector@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz#66a902c81318e66e694df7d096a95466cb962f8e" + integrity sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ== dependencies: jest-get-type "^29.4.3" - pretty-format "^29.5.0" + pretty-format "^29.6.1" jest-light-runner@^0.4.1: version "0.4.1" @@ -5824,39 +5838,39 @@ jest-light-runner@^0.4.1: piscina "^3.1.0" supports-color "^9.2.1" -jest-matcher-utils@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" - integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== +jest-matcher-utils@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz#6c60075d84655d6300c5d5128f46531848160b53" + integrity sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA== dependencies: chalk "^4.0.0" - jest-diff "^29.5.0" + jest-diff "^29.6.1" jest-get-type "^29.4.3" - pretty-format "^29.5.0" + pretty-format "^29.6.1" -jest-message-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" - integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== +jest-message-util@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.1.tgz#d0b21d87f117e1b9e165e24f245befd2ff34ff8d" + integrity sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.5.0" + pretty-format "^29.6.1" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.0.1, jest-mock@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" - integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== +jest-mock@^29.0.1, jest-mock@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.1.tgz#049ee26aea8cbf54c764af649070910607316517" + integrity sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.6.1" jest-pnp-resolver@^1.2.2: version "1.2.3" @@ -5873,112 +5887,110 @@ jest-regex-util@^29.4.3: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== -jest-resolve-dependencies@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4" - integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== +jest-resolve-dependencies@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz#b85b06670f987a62515bbf625d54a499e3d708f5" + integrity sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw== dependencies: jest-regex-util "^29.4.3" - jest-snapshot "^29.5.0" + jest-snapshot "^29.6.1" -jest-resolve@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc" - integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== +jest-resolve@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.1.tgz#4c3324b993a85e300add2f8609f51b80ddea39ee" + integrity sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.6.1" jest-pnp-resolver "^1.2.2" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-util "^29.6.1" + jest-validate "^29.6.1" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8" - integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== - dependencies: - "@jest/console" "^29.5.0" - "@jest/environment" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" +jest-runner@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.1.tgz#54557087e7972d345540d622ab5bfc3d8f34688c" + integrity sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ== + dependencies: + "@jest/console" "^29.6.1" + "@jest/environment" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" jest-docblock "^29.4.3" - jest-environment-node "^29.5.0" - jest-haste-map "^29.5.0" - jest-leak-detector "^29.5.0" - jest-message-util "^29.5.0" - jest-resolve "^29.5.0" - jest-runtime "^29.5.0" - jest-util "^29.5.0" - jest-watcher "^29.5.0" - jest-worker "^29.5.0" + jest-environment-node "^29.6.1" + jest-haste-map "^29.6.1" + jest-leak-detector "^29.6.1" + jest-message-util "^29.6.1" + jest-resolve "^29.6.1" + jest-runtime "^29.6.1" + jest-util "^29.6.1" + jest-watcher "^29.6.1" + jest-worker "^29.6.1" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420" - integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/globals" "^29.5.0" - "@jest/source-map" "^29.4.3" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" +jest-runtime@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.1.tgz#8a0fc9274ef277f3d70ba19d238e64334958a0dc" + integrity sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ== + dependencies: + "@jest/environment" "^29.6.1" + "@jest/fake-timers" "^29.6.1" + "@jest/globals" "^29.6.1" + "@jest/source-map" "^29.6.0" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" + jest-haste-map "^29.6.1" + jest-message-util "^29.6.1" + jest-mock "^29.6.1" jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-resolve "^29.6.1" + jest-snapshot "^29.6.1" + jest-util "^29.6.1" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.0.1, jest-snapshot@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce" - integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== +jest-snapshot@^29.0.1, jest-snapshot@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.1.tgz#0d083cb7de716d5d5cdbe80d598ed2fbafac0239" + integrity sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/babel__traverse" "^7.0.6" + "@jest/expect-utils" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.5.0" + expect "^29.6.1" graceful-fs "^4.2.9" - jest-diff "^29.5.0" + jest-diff "^29.6.1" jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-matcher-utils "^29.6.1" + jest-message-util "^29.6.1" + jest-util "^29.6.1" natural-compare "^1.4.0" - pretty-format "^29.5.0" - semver "^7.3.5" + pretty-format "^29.6.1" + semver "^7.5.3" jest-util@^28.1.3: version "28.1.3" @@ -5992,42 +6004,42 @@ jest-util@^28.1.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.0.0, jest-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" - integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== +jest-util@^29.0.0, jest-util@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.1.tgz#c9e29a87a6edbf1e39e6dee2b4689b8a146679cb" + integrity sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc" - integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== +jest-validate@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.1.tgz#765e684af6e2c86dce950aebefbbcd4546d69f7b" + integrity sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" camelcase "^6.2.0" chalk "^4.0.0" jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.6.1" -jest-watcher@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363" - integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== +jest-watcher@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.1.tgz#7c0c43ddd52418af134c551c92c9ea31e5ec942e" + integrity sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA== dependencies: - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/test-result" "^29.6.1" + "@jest/types" "^29.6.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.5.0" + jest-util "^29.6.1" string-length "^4.0.1" jest-worker@^28.1.3: @@ -6039,25 +6051,25 @@ jest-worker@^28.1.3: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d" - integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== +jest-worker@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.1.tgz#64b015f0e985ef3a8ad049b61fe92b3db74a5319" + integrity sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA== dependencies: "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.6.1" merge-stream "^2.0.0" supports-color "^8.0.0" jest@^29.3.1: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" - integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.1.tgz#74be1cb719c3abe439f2d94aeb18e6540a5b02ad" + integrity sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw== dependencies: - "@jest/core" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.6.1" + "@jest/types" "^29.6.1" import-local "^3.0.2" - jest-cli "^29.5.0" + jest-cli "^29.6.1" jmespath@0.16.0: version "0.16.0" @@ -6181,12 +6193,12 @@ kleur@^4.1.4, kleur@^4.1.5: integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== knex@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/knex/-/knex-2.4.2.tgz#a34a289d38406dc19a0447a78eeaf2d16ebedd61" - integrity sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg== + version "2.5.1" + resolved "https://registry.yarnpkg.com/knex/-/knex-2.5.1.tgz#a6c6b449866cf4229f070c17411f23871ba52ef9" + integrity sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA== dependencies: colorette "2.0.19" - commander "^9.1.0" + commander "^10.0.0" debug "4.3.4" escalade "^3.1.1" esm "^3.2.25" @@ -6194,7 +6206,7 @@ knex@^2.3.0: getopts "2.3.0" interpret "^2.2.0" lodash "^4.17.21" - pg-connection-string "2.5.0" + pg-connection-string "2.6.1" rechoir "^0.8.0" resolve-from "^5.0.0" tarn "^3.0.2" @@ -6258,9 +6270,9 @@ lines-and-columns@^1.1.6: integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== lint-staged@^13.1.0: - version "13.2.2" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.2.2.tgz#5e711d3139c234f73402177be2f8dd312e6508ca" - integrity sha512-71gSwXKy649VrSU09s10uAT0rWCcY3aewhMaHyl2N84oBk4Xs9HgxvUp3AYu+bNsK4NrOYYxvSgg7FyGJ+jGcA== + version "13.2.3" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.2.3.tgz#f899aad6c093473467e9c9e316e3c2d8a28f87a7" + integrity sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg== dependencies: chalk "5.2.0" cli-truncate "^3.1.0" @@ -6438,10 +6450,10 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@^9.1.1: - version "9.1.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.2.tgz#255fdbc14b75589d6d0e73644ca167a8db506835" - integrity sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ== +"lru-cache@^9.1.1 || ^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" + integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw== lru-queue@0.1, lru-queue@^0.1.0: version "0.1.0" @@ -6635,9 +6647,9 @@ minimatch@^5.0.1: brace-expansion "^2.0.1" minimatch@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" - integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: brace-expansion "^2.0.1" @@ -6660,10 +6672,10 @@ minimist@1.2.8, minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -"minipass@^5.0.0 || ^6.0.2": - version "6.0.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-6.0.2.tgz#542844b6c4ce95b202c0995b0a471f1229de4c81" - integrity sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.2.tgz#58a82b7d81c7010da5bd4b2c0c85ac4b4ec5131e" + integrity sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA== mkdirp@1.0.4, mkdirp@^1.0.4: version "1.0.4" @@ -6691,9 +6703,9 @@ moleculer-bull@^0.3.1: lodash "^4.17.21" moleculer-db@^0.8.21: - version "0.8.23" - resolved "https://registry.yarnpkg.com/moleculer-db/-/moleculer-db-0.8.23.tgz#f38aa4597917d40e2c7550150544e095c8af17c0" - integrity sha512-jjdRmdLBJGF8ZXDVcaS65dfXPffz4dKU1Hmw/z7J4kF/a2mYvg92/k8LNlX5j3uGYPMXFiSl3IDVXMERTmWWsA== + version "0.8.24" + resolved "https://registry.yarnpkg.com/moleculer-db/-/moleculer-db-0.8.24.tgz#9b6a39056c41f3a116d56767c6d4dc8c407067ad" + integrity sha512-ABJOTPTJFXta0w/nn5zY/o3cbkqUGFrfdPeY+VaurOt/wzVuCZ89wme+sgSRamnwhPbfmqUrDQG8MGy7BG0uTQ== dependencies: "@seald-io/nedb" "^3.0.0" bluebird "^3.7.2" @@ -6718,9 +6730,9 @@ moleculer-repl@^0.7.3: yargs-parser "^21.1.1" moleculer-web@^0.10.5: - version "0.10.5" - resolved "https://registry.yarnpkg.com/moleculer-web/-/moleculer-web-0.10.5.tgz#76be7bf8f7200aff45c6ca61bb96feba6f412b44" - integrity sha512-b4LFl67ESo8tmYtfuu91r8nRyLV158URAbf21dsNvf4hiVJW0u0BFwkkQF7Bn1Y+KRYCnAXNjGt4B+q5+cKnIw== + version "0.10.6" + resolved "https://registry.yarnpkg.com/moleculer-web/-/moleculer-web-0.10.6.tgz#a8bc99b7f153b882018aec9fa553496a86b864f5" + integrity sha512-MGNIH6mXLU2Wj63bAgoVzdhMKXALp99F5UHuiBgS2ywakdWEUl/q7GlMblvscioCCkXuUWezId85J0yioYxedg== dependencies: "@fastify/busboy" "^1.0.0" body-parser "^1.19.0" @@ -6731,19 +6743,19 @@ moleculer-web@^0.10.5: kleur "^4.1.4" lodash "^4.17.21" path-to-regexp "^3.1.0" - qs "^6.10.1" + qs "^6.11.0" serve-static "^1.14.1" moleculer@^0.14.16, moleculer@^0.14.27: - version "0.14.29" - resolved "https://registry.yarnpkg.com/moleculer/-/moleculer-0.14.29.tgz#5ac1fe45c6f2492ddb4601002a46ed7b14e6aa87" - integrity sha512-19SbGgZGL6tHpgQAiEg8ZTkPR3IagJ/ruZr3038fZchTUHZ0wMZSrh62Or3iUJBMEOFqZ5S5nHpKtV40jpoeew== + version "0.14.30" + resolved "https://registry.yarnpkg.com/moleculer/-/moleculer-0.14.30.tgz#ab735b80b05f5d5b2450bc0c5bf10b891cc719f8" + integrity sha512-XxM2oYPofd5FhHTnu8+gzEcEMy92rHw2ey6f4bd+aMCWNFQpueg2eKURZPn5rSpif7mS2AwKMPGGgEqoRnT7+A== dependencies: args "^5.0.3" eventemitter2 "^6.4.9" - fastest-validator "^1.16.0" + fastest-validator "^1.17.0" glob "^7.2.0" - ipaddr.js "^2.0.1" + ipaddr.js "^2.1.0" kleur "^4.1.5" lodash "^4.17.21" lru-cache "^6.0.0" @@ -6854,9 +6866,9 @@ node-addon-api@^3.0.0: integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== node-fetch@^2.6.7: - version "2.6.11" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" - integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== + version "2.6.12" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" + integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== dependencies: whatwg-url "^5.0.0" @@ -6876,9 +6888,9 @@ node-int64@^0.4.0: integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.12: - version "2.0.12" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" - integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== normalize-package-data@^2.5.0: version "2.5.0" @@ -6968,9 +6980,9 @@ object.values@^1.1.6: es-abstract "^1.20.4" objection@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/objection/-/objection-3.0.1.tgz#f67dc698187d10524e5d1b5d37a54e5bba49a42a" - integrity sha512-rqNnyQE+C55UHjdpTOJEKQHJGZ/BGtBBtgxdUpKG4DQXRUmqxfmgS/MhPWxB9Pw0mLSVLEltr6soD4c0Sddy0Q== + version "3.0.4" + resolved "https://registry.yarnpkg.com/objection/-/objection-3.0.4.tgz#d2f60cec49f7f917c4b97260add9e2c2cdb52c0b" + integrity sha512-0XaStHtOBcux4nlffUj8gOpxUsAPE+sLDZPml6n79WQhInYLBvjctU/uv2kEUPPybAJK8YL6ETkLtft9UOj7nQ== dependencies: ajv "^8.6.2" db-errors "^0.2.3" @@ -7017,17 +7029,17 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" ora@^5.4.1: version "5.4.1" @@ -7161,13 +7173,13 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.7.0: - version "1.9.2" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.9.2.tgz#90f9d296ac5e37e608028e28a447b11d385b3f63" - integrity sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg== +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== dependencies: - lru-cache "^9.1.1" - minipass "^5.0.0 || ^6.0.2" + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-to-regexp@0.1.7: version "0.1.7" @@ -7194,20 +7206,15 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -pg-cloudflare@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.0.tgz#833d70870d610d14bf9df7afb40e1cba310c17a0" - integrity sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA== - -pg-connection-string@2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" - integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== -pg-connection-string@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.0.tgz#12a36cc4627df19c25cc1b9b736cc39ee1f73ae8" - integrity sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg== +pg-connection-string@2.6.1, pg-connection-string@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb" + integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg== pg-hstore@^2.3.4: version "2.3.4" @@ -7221,10 +7228,10 @@ pg-int8@1.0.1: resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-pool@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e" - integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ== +pg-pool@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" + integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== pg-protocol@^1.6.0: version "1.6.0" @@ -7243,19 +7250,19 @@ pg-types@^2.1.0: postgres-interval "^1.1.0" pg@^8.8.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.0.tgz#a37e534e94b57a7ed811e926f23a7c56385f55d9" - integrity sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA== + version "8.11.1" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.1.tgz#297e0eb240306b1e9e4f55af8a3bae76ae4810b1" + integrity sha512-utdq2obft07MxaDg0zBJI+l/M3mBRfIpEN3iSemsz0G5F2/VXx+XzqF4oxrbIZXQxt2AZzIUzyVg/YM6xOP/WQ== dependencies: buffer-writer "2.0.0" packet-reader "1.0.0" - pg-connection-string "^2.6.0" - pg-pool "^3.6.0" + pg-connection-string "^2.6.1" + pg-pool "^3.6.1" pg-protocol "^1.6.0" pg-types "^2.1.0" pgpass "1.x" optionalDependencies: - pg-cloudflare "^1.1.0" + pg-cloudflare "^1.1.1" pgpass@1.x: version "1.0.5" @@ -7280,9 +7287,9 @@ pidtree@^0.6.0: integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== pirates@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== piscina@^3.1.0: version "3.2.0" @@ -7346,12 +7353,12 @@ pretty-bytes@^5.6.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -pretty-format@^29.0.0, pretty-format@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" - integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== +pretty-format@^29.0.0, pretty-format@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.1.tgz#ec838c288850b7c4f9090b867c2d4f4edbfb0f3e" + integrity sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog== dependencies: - "@jest/schemas" "^29.4.3" + "@jest/schemas" "^29.6.0" ansi-styles "^5.0.0" react-is "^18.0.0" @@ -7388,9 +7395,9 @@ protobufjs@^6.11.2, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: long "^4.0.0" protobufjs@^7.1.2: - version "7.2.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.3.tgz#01af019e40d9c6133c49acbb3ff9e30f4f0f70b2" - integrity sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg== + version "7.2.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.4.tgz#3fc1ec0cdc89dd91aef9ba6037ba07408485c3ae" + integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -7455,7 +7462,7 @@ qs@6.9.7: resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== -qs@^6.10.1: +qs@^6.11.0: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== @@ -7647,7 +7654,7 @@ regenerator-transform@^0.15.1: dependencies: "@babel/runtime" "^7.8.4" -regexp.prototype.flags@^1.4.3: +regexp.prototype.flags@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== @@ -7838,6 +7845,16 @@ rxjs@^7.8.0: dependencies: tslib "^2.1.0" +safe-array-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.0.tgz#2064223cba3c08d2ee05148eedbc563cd6d84060" + integrity sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -7873,29 +7890,29 @@ sax@>=0.6.0: integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== "semver@2 || 3 || 4 || 5": - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@7.5.0: - version "7.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" - integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== +semver@7.5.2: + version "7.5.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.2.tgz#5b851e66d1be07c1cdaf37dfc856f543325a2beb" + integrity sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ== dependencies: lru-cache "^6.0.0" -semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: - version "7.5.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" - integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - send@0.17.2: version "0.17.2" resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" @@ -8287,7 +8304,7 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -8327,9 +8344,9 @@ supports-color@^8.0.0: has-flag "^4.0.0" supports-color@^9.2.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.3.1.tgz#34e4ad3c71c9a39dae3254ecc46c9b74e89e15a6" - integrity sha512-knBY82pjmnIzK3NifMo3RxEIRD9E0kIzV4BKcyTZ9+9kWgLMxd4PrsTSMoFQUabgRBbF8KOLRDCyKgNV+iK44Q== + version "9.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954" + integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw== supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" @@ -8486,9 +8503,9 @@ trim-newlines@^3.0.0: integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== ts-jest@^29.0.3: - version "29.1.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.0.tgz#4a9db4104a49b76d2b368ea775b6c9535c603891" - integrity sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA== + version "29.1.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" + integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" @@ -8496,7 +8513,7 @@ ts-jest@^29.0.3: json5 "^2.2.3" lodash.memoize "4.x" make-error "1.x" - semver "7.x" + semver "^7.5.3" yargs-parser "^21.0.1" ts-node@^10.8.1, ts-node@^10.9.1: @@ -8543,9 +8560,9 @@ tslib@^1.8.1, tslib@^1.9.0: integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.0.0, tslib@^2.1.0: - version "2.5.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" - integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== + version "2.6.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" + integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== tsutils@^3.21.0: version "3.21.0" @@ -8626,6 +8643,36 @@ type@^2.7.2: resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" @@ -8636,9 +8683,9 @@ typed-array-length@^1.0.4: is-typed-array "^1.1.9" "typescript@^4.6.4 || ^5.0.0": - version "5.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" - integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== + version "5.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" + integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== typescript@^4.8.4: version "4.9.5" @@ -8850,17 +8897,16 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-typed-array@^1.1.2, which-typed-array@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== +which-typed-array@^1.1.10, which-typed-array@^1.1.11, which-typed-array@^1.1.2: + version "1.1.11" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" + integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== dependencies: available-typed-arrays "^1.0.5" call-bind "^1.0.2" for-each "^0.3.3" gopd "^1.0.1" has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" which@^2.0.1: version "2.0.2" @@ -8869,11 +8915,6 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" From 8c01c5c1afc92c2869c8b41e8bd53987356defae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Th=C3=A0nh=20Phong?= <49814372+phamphong9981@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:48:28 +0700 Subject: [PATCH 12/33] refactor: migrate cw721 activity missing smart contract event ( main ) (#322) * refactor: migrate cw721 activity missing smart contract event * feat: api * test: test params * test: test params * test: test params * test: test params * test: test params * test: test params * refactor: code * refactor: code * refactor: code * refactor: code * refactor: code * fix: ci --- ci/network.json.ci | 3 +- src/models/cw721_tx.ts | 2 +- .../api-gateways/api_gateway.service.ts | 2 +- .../api-gateways/cw721_admin.service.ts | 57 ++++++++++ src/services/cw20/cw20_reindexing.service.ts | 2 +- .../cw721/cw721-reindexing.service.ts | 105 +++++++++++++----- src/services/cw721/cw721.service.ts | 1 + .../services/api-gateways/cw721_admin.spec.ts | 59 ++++++++++ .../services/cw20/cw20_reindexing.spec.ts | 10 +- .../cw721/cw721-missing-contract.spec.ts | 41 ++++++- 10 files changed, 246 insertions(+), 36 deletions(-) create mode 100644 src/services/api-gateways/cw721_admin.service.ts create mode 100644 test/unit/services/api-gateways/cw721_admin.spec.ts diff --git a/ci/network.json.ci b/ci/network.json.ci index 7297b053b..e43b64657 100644 --- a/ci/network.json.ci +++ b/ci/network.json.ci @@ -3,6 +3,7 @@ "chainId": "aura-testnet-2", "RPC": ["http://localhost:26657"], "LCD": ["http://localhost:1317"], - "databaseName": "horoscope_dev_auratestnet1" + "databaseName": "horoscope_dev_auratestnet1", + "moleculerNamespace": "namespace-auratestnet" } ] diff --git a/src/models/cw721_tx.ts b/src/models/cw721_tx.ts index 7ec84fbc3..3f20ce128 100644 --- a/src/models/cw721_tx.ts +++ b/src/models/cw721_tx.ts @@ -29,7 +29,7 @@ export default class CW721Activity extends BaseModel { to?: string; - height?: number; + height!: number; smart_contract_event_id!: number; diff --git a/src/services/api-gateways/api_gateway.service.ts b/src/services/api-gateways/api_gateway.service.ts index 9ac09560a..1f4183319 100644 --- a/src/services/api-gateways/api_gateway.service.ts +++ b/src/services/api-gateways/api_gateway.service.ts @@ -37,7 +37,7 @@ import { bullBoardMixin } from '../../mixins/bullBoard/bullBoard.mixin'; path: '/admin', autoAliases: true, // allow generate rest info (GET/PUT/POST...) in the services mappingPolicy: 'restrict', // allow action called with exact method - whitelist: ['v1.cw20-admin.*'], + whitelist: ['v1.cw721-admin.*', 'v1.cw20-admin.*'], }, ], // empty cors object will have moleculer to generate handler for preflight request and CORS header which allow all origin diff --git a/src/services/api-gateways/cw721_admin.service.ts b/src/services/api-gateways/cw721_admin.service.ts new file mode 100644 index 000000000..10ca84530 --- /dev/null +++ b/src/services/api-gateways/cw721_admin.service.ts @@ -0,0 +1,57 @@ +import { Post, Service } from '@ourparentcenter/moleculer-decorators-extended'; +import { Context, ServiceBroker } from 'moleculer'; +import networks from '../../../network.json' assert { type: 'json' }; +import BaseService from '../../base/base.service'; +import { REINDEX_TYPE } from '../cw721/cw721-reindexing.service'; + +@Service({ + name: 'cw721-admin', + version: 1, +}) +export default class Cw721AdminService extends BaseService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @Post('/cw721-reindexing', { + name: 'cw721Reindexing', + params: { + chainid: { + type: 'string', + optional: false, + enum: networks.map((network) => network.chainId), + }, + contractAddresses: { + type: 'array', + optional: false, + items: 'string', + }, + type: { + type: 'enum', + optional: false, + values: Object.values(REINDEX_TYPE), + }, + }, + }) + async cw721Reindexing( + ctx: Context< + { + chainid: string; + contractAddresses: string[]; + type: string; + }, + Record + > + ) { + const selectedChain = networks.find( + (network) => network.chainId === ctx.params.chainid + ); + return this.broker.call( + `v1.Cw721ReindexingService.reindexing@${selectedChain?.moleculerNamespace}`, + { + contractAddresses: ctx.params.contractAddresses, + type: ctx.params.type, + } + ); + } +} diff --git a/src/services/cw20/cw20_reindexing.service.ts b/src/services/cw20/cw20_reindexing.service.ts index b2caf4cb8..b0caab4fc 100644 --- a/src/services/cw20/cw20_reindexing.service.ts +++ b/src/services/cw20/cw20_reindexing.service.ts @@ -96,7 +96,7 @@ export default class Cw20ReindexingContract extends BullableService { const maxUpdatedHeightOwner = _.max(initBalances.map((holder) => holder.event_height)) || 0; let id = -1; - let lastUpdatedHeight = -1; + let lastUpdatedHeight = -1; await knex.transaction(async (trx) => { if (cw20Contract) { await Cw20Event.query() diff --git a/src/services/cw721/cw721-reindexing.service.ts b/src/services/cw721/cw721-reindexing.service.ts index 29f5ad442..274a9a074 100644 --- a/src/services/cw721/cw721-reindexing.service.ts +++ b/src/services/cw721/cw721-reindexing.service.ts @@ -19,14 +19,21 @@ import CW721Activity from '../../models/cw721_tx'; import { ICw721ReindexingHistoryParams } from './cw721.service'; export interface IAddressParam { - contractAddress: string; + contractAddresses: string[]; + type: string; } interface ICw721ReindexingServiceParams { contractAddress: string; smartContractId: number; + type: string; } +export const REINDEX_TYPE = { + ALL: 'all', + HISTORY: 'history', +}; + @Service({ name: SERVICE.V1.CW721ReindexingService.key, version: 1, @@ -41,7 +48,15 @@ export default class CW721ReindexingService extends BullableService { jobName: BULL_JOB_NAME.REINDEX_CW721_CONTRACT, }) async jobHandler(_payload: ICw721ReindexingServiceParams): Promise { - const { smartContractId, contractAddress } = _payload; + const { smartContractId, contractAddress, type } = _payload; + if (type === REINDEX_TYPE.ALL) { + await this.handleReindexAll(smartContractId, contractAddress); + } else if (type === REINDEX_TYPE.HISTORY) { + await this.handleReindexHistory(smartContractId, contractAddress); + } + } + + async handleReindexAll(smartContractId: number, contractAddress: string) { const cw721Contract = await CW721Contract.query() .withGraphJoined('smart_contract') .where('smart_contract.address', contractAddress) @@ -118,37 +133,77 @@ export default class CW721ReindexingService extends BullableService { ); } + async handleReindexHistory(smartContractId: number, contractAddress: string) { + const cw721Contract = await CW721Contract.query() + .withGraphJoined('smart_contract') + .where('smart_contract.address', contractAddress) + .select(['cw721_contract.id']) + .first() + .throwIfNotFound(); + const currentHeight = ( + await CW721Activity.query().max('height as height').throwIfNotFound() + )[0].height; + await CW721Activity.query() + .delete() + .where('cw721_contract_id', cw721Contract.id) + .andWhere('height', '<=', currentHeight); + // insert histories + await this.createJob( + BULL_JOB_NAME.REINDEX_CW721_HISTORY, + BULL_JOB_NAME.REINDEX_CW721_HISTORY, + { + smartContractId, + startBlock: config.crawlBlock.startBlock, + endBlock: currentHeight, + prevId: 0, + contractAddress, + } satisfies ICw721ReindexingHistoryParams, + { + removeOnComplete: true, + } + ); + } + @Action({ name: SERVICE.V1.CW721ReindexingService.Reindexing.key, params: { - contractAddress: 'string', + contractAddresses: { + type: 'array', + items: 'string', + optional: false, + }, + type: { + type: 'string', + optional: false, + }, }, }) public async reindexing(ctx: Context) { - const { contractAddress } = ctx.params; - const smartContract = await SmartContract.query() + const { contractAddresses, type } = ctx.params; + const smartContracts = await SmartContract.query() .withGraphJoined('code') - .where('address', contractAddress) - .first() - .throwIfNotFound(); - // check whether contract is CW721 type -> throw error to user - if (smartContract.code.type === 'CW721') { - await this.createJob( - BULL_JOB_NAME.REINDEX_CW721_CONTRACT, - BULL_JOB_NAME.REINDEX_CW721_CONTRACT, - { - contractAddress, - smartContractId: smartContract.id, - } satisfies ICw721ReindexingServiceParams, - { - jobId: contractAddress, + .whereIn('address', contractAddresses); + await Promise.all( + smartContracts.reduce((acc: Promise[], smartContract) => { + if (smartContract.code.type === 'CW721') { + acc.push( + this.createJob( + BULL_JOB_NAME.REINDEX_CW721_CONTRACT, + BULL_JOB_NAME.REINDEX_CW721_CONTRACT, + { + contractAddress: smartContract.address, + smartContractId: smartContract.id, + type, + } satisfies ICw721ReindexingServiceParams, + { + jobId: smartContract.address, + } + ) + ); } - ); - } else { - throw new Error( - `Smart contract ${ctx.params.contractAddress} is not CW721 type` - ); - } + return acc; + }, []) + ); } async _start(): Promise { diff --git a/src/services/cw721/cw721.service.ts b/src/services/cw721/cw721.service.ts index 0a505d672..b924d27c5 100644 --- a/src/services/cw721/cw721.service.ts +++ b/src/services/cw721/cw721.service.ts @@ -567,6 +567,7 @@ export default class Cw721HandlerService extends BullableService { BULL_JOB_NAME.REINDEX_CW721_CONTRACT ); await queue.remove(contractAddress); + this.logger.info('Reindex cw721 history done!!!'); } } diff --git a/test/unit/services/api-gateways/cw721_admin.spec.ts b/test/unit/services/api-gateways/cw721_admin.spec.ts new file mode 100644 index 000000000..0e2489c02 --- /dev/null +++ b/test/unit/services/api-gateways/cw721_admin.spec.ts @@ -0,0 +1,59 @@ +import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; +import { Errors, ServiceBroker } from 'moleculer'; +import Cw721AdminService from '../../../../src/services/api-gateways/cw721_admin.service'; +import CW721ReindexingService from '../../../../src/services/cw721/cw721-reindexing.service'; +import Cw721HandlerService from '../../../../src/services/cw721/cw721.service'; + +@Describe('Test cw721 admin api service') +export default class Cw721AdminTest { + broker = new ServiceBroker({ + logger: false, + }); + + cw721Admin = this.broker.createService( + Cw721AdminService + ) as Cw721AdminService; + + cw721Reindex = this.broker.createService( + CW721ReindexingService + ) as CW721ReindexingService; + + cw721HandlerService = this.broker.createService( + Cw721HandlerService + ) as Cw721HandlerService; + + @BeforeAll() + async initSuite() { + await this.broker.start(); + } + + @AfterAll() + async tearDown() { + await this.broker.stop(); + } + + @Test('Invalid query format') + public async testInvalidQueryFormat() { + expect( + this.broker.call('v1.cw721-admin.cw721Reindexing', { + chainid: undefined, + contractAddress: 'abc', + type: 'all', + }) + ).rejects.toBeInstanceOf(Errors.ValidationError); + expect( + this.broker.call('v1.cw721-admin.cw721Reindexing', { + chainid: 'euphoria-2', + contractAddress: undefined, + type: 'all', + }) + ).rejects.toBeInstanceOf(Errors.ValidationError); + expect( + this.broker.call('v1.cw721-admin.cw721Reindexing', { + chainid: 'euphoria-2', + contractAddress: 'abc', + type: 'hihi', + }) + ).rejects.toBeInstanceOf(Errors.ValidationError); + } +} diff --git a/test/unit/services/cw20/cw20_reindexing.spec.ts b/test/unit/services/cw20/cw20_reindexing.spec.ts index 8d6eba414..868b7321c 100644 --- a/test/unit/services/cw20/cw20_reindexing.spec.ts +++ b/test/unit/services/cw20/cw20_reindexing.spec.ts @@ -151,11 +151,11 @@ export default class TestCw20ReindexingService { .orderBy('address', 'asc'); expect( cw20Holders.map((cw20Holder) => ({ - address: cw20Holder.address, - amount: cw20Holder.amount, - event_height: cw20Holder.last_updated_height, - contract_address: this.codeId.contracts[0].address, - })) + address: cw20Holder.address, + amount: cw20Holder.amount, + event_height: cw20Holder.last_updated_height, + contract_address: this.codeId.contracts[0].address, + })) ).toEqual(mockHolders); } } diff --git a/test/unit/services/cw721/cw721-missing-contract.spec.ts b/test/unit/services/cw721/cw721-missing-contract.spec.ts index 9ec072f55..fbe0ffec0 100644 --- a/test/unit/services/cw721/cw721-missing-contract.spec.ts +++ b/test/unit/services/cw721/cw721-missing-contract.spec.ts @@ -1,12 +1,14 @@ import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; -import { ServiceBroker } from 'moleculer'; +import { Errors, ServiceBroker } from 'moleculer'; import { BULL_JOB_NAME } from '../../../../src/common'; import knex from '../../../../src/common/utils/db_connection'; import { BlockCheckpoint, Code } from '../../../../src/models'; import CW721Contract from '../../../../src/models/cw721_contract'; import CW721Token from '../../../../src/models/cw721_token'; import { SmartContractEvent } from '../../../../src/models/smart_contract_event'; -import Cw721MissingContractService from '../../../../src/services/cw721/cw721-reindexing.service'; +import Cw721MissingContractService, { + REINDEX_TYPE, +} from '../../../../src/services/cw721/cw721-reindexing.service'; import Cw721HandlerService, { CW721_ACTION, } from '../../../../src/services/cw721/cw721.service'; @@ -331,6 +333,7 @@ export default class TestCw721MissingContractService { await this.cw721MissingContractService.jobHandler({ contractAddress: this.codeId.contracts[0].address, smartContractId: 1, + type: REINDEX_TYPE.ALL, }); const cw721Contract = await CW721Contract.query() .withGraphJoined('smart_contract') @@ -524,4 +527,38 @@ export default class TestCw721MissingContractService { expect(tokens[0].last_updated_height).toEqual(missingHistories[3].height); expect(tokens[0].burned).toEqual(true); } + + @Test('test action params') + public async testActionParams() { + expect( + this.broker.call('v1.Cw721ReindexingService.reindexing', { + contractAddresses: undefined, + type: 'all', + }) + ).rejects.toBeInstanceOf(Errors.ValidationError); + expect( + this.broker.call('v1.Cw721ReindexingService.reindexing', { + contractAddresses: this.codeId.contracts[1].address, + type: 'heell', + }) + ).rejects.toBeInstanceOf(Errors.ValidationError); + expect( + this.broker.call('v1.Cw721ReindexingService.reindexing', { + contractAddresses: [this.codeId.contracts[1].address], + type: 'heell', + }) + ).toBeDefined(); + expect( + this.broker.call('v1.Cw721ReindexingService.reindexing', { + contractAddresses: [this.codeId.contracts[1].address], + type: REINDEX_TYPE.ALL, + }) + ).toBeDefined(); + expect( + this.broker.call('v1.Cw721ReindexingService.reindexing', { + contractAddresses: [this.codeId.contracts[1].address], + type: REINDEX_TYPE.HISTORY, + }) + ).toBeDefined(); + } } From 3c325dc6a85ac38fc7278412948220b37e0f959b Mon Sep 17 00:00:00 2001 From: Tuan Phan Anh <38557844+fibonacci998@users.noreply.github.com> Date: Wed, 23 Aug 2023 16:49:14 +0700 Subject: [PATCH 13/33] Fix/account stat with feegrant (#337) * fix: get use_feegrant_grantee if has, other get tx_fee_payer * fix: remove code not used --- src/services/statistics/account_statistics.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/statistics/account_statistics.service.ts b/src/services/statistics/account_statistics.service.ts index dd97d64bf..81731b6e9 100644 --- a/src/services/statistics/account_statistics.service.ts +++ b/src/services/statistics/account_statistics.service.ts @@ -259,8 +259,9 @@ export default class AccountStatisticsService extends BullableService { .map((event) => Object.assign({}, ...event.jsonb_agg)) .map( (event) => - event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.TX_FEE_PAYER] ?? - event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.USE_FEEGRANT_GRANTEE] + event[ + EventAttribute.ATTRIBUTE_COMPOSITE_KEY.USE_FEEGRANT_GRANTEE + ] ?? event[EventAttribute.ATTRIBUTE_COMPOSITE_KEY.TX_FEE_PAYER] ) .forEach((address) => { if (!accountStats[address]) { From 4adfdf0d346d64f637f84651ee0d6cfc686e3bb1 Mon Sep 17 00:00:00 2001 From: Tuan Phan Anh <38557844+fibonacci998@users.noreply.github.com> Date: Wed, 23 Aug 2023 16:49:49 +0700 Subject: [PATCH 14/33] Update api_gateway.service.ts (#336) --- src/services/api-gateways/api_gateway.service.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/services/api-gateways/api_gateway.service.ts b/src/services/api-gateways/api_gateway.service.ts index 1f4183319..8fd7ddb66 100644 --- a/src/services/api-gateways/api_gateway.service.ts +++ b/src/services/api-gateways/api_gateway.service.ts @@ -31,14 +31,8 @@ import { bullBoardMixin } from '../../mixins/bullBoard/bullBoard.mixin'; path: '/admin', autoAliases: true, // allow generate rest info (GET/PUT/POST...) in the services mappingPolicy: 'restrict', // allow action called with exact method - whitelist: ['v2.statistics.syncPrevDateStatsByChainId'], - }, - { - path: '/admin', - autoAliases: true, // allow generate rest info (GET/PUT/POST...) in the services - mappingPolicy: 'restrict', // allow action called with exact method - whitelist: ['v1.cw721-admin.*', 'v1.cw20-admin.*'], - }, + whitelist: ['v2.statistics.syncPrevDateStatsByChainId', 'v1.cw20-admin.*', 'v1.cw721-admin.*'], + } ], // empty cors object will have moleculer to generate handler for preflight request and CORS header which allow all origin cors: {}, From 5c86a9f799b0860d303b8c9998f145342ffe02b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Th=C3=A0nh=20Phong?= <49814372+phamphong9981@users.noreply.github.com> Date: Mon, 28 Aug 2023 14:01:36 +0700 Subject: [PATCH 15/33] fix: cw721 activity missing from/to ( main ) (#294) * fix: cw721 activity missing from/to * fix: fill from/to cw721 activity * fix: fill from/to cw721 activity * fix: fill from/to cw721 activity * fix: fill from/to cw721 activity * refactor: code * fix: code * refactor: code --------- Co-authored-by: Vu Ngoc Quang --- ...230808072704_update_data_cw721_activity.ts | 55 +++++++++++++++++++ src/models/cw721_tx.ts | 4 +- src/models/event_attribute.ts | 1 + src/models/smart_contract_event.ts | 2 + src/services/cw721/cw721.service.ts | 13 +++++ test/unit/services/cw721/cw721.spec.ts | 37 ++++++++++++- 6 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 migrations/20230808072704_update_data_cw721_activity.ts diff --git a/migrations/20230808072704_update_data_cw721_activity.ts b/migrations/20230808072704_update_data_cw721_activity.ts new file mode 100644 index 000000000..c78964cef --- /dev/null +++ b/migrations/20230808072704_update_data_cw721_activity.ts @@ -0,0 +1,55 @@ +import { Knex } from 'knex'; +import config from '../config.json' assert { type: 'json' }; +import CW721Activity from '../src/models/cw721_tx'; +import { getAttributeFrom } from '../src/common/utils/smart_contract'; +import { EventAttribute } from '../src/models'; +const UPDATE_CW721_ACTIONS = ['mint', 'burn', 'transfer_nft', 'send_nft']; +export async function up(knex: Knex): Promise { + await knex.transaction(async (trx) => { + let prevId = 0; + while (true) { + const handleRecords = await CW721Activity.query() + .withGraphFetched('smart_contract_event.attributes') + .where('cw721_activity.from', null) + .andWhere('cw721_activity.to', null) + .andWhere('cw721_activity.id', '>', prevId) + .whereIn('cw721_activity.action', UPDATE_CW721_ACTIONS) + .whereNotNull('smart_contract_event_id') + .orderBy('cw721_activity.id') + .limit(100) + .transacting(trx); + + if (handleRecords.length > 0) { + const patchQueries: any[] = []; + handleRecords.forEach((record) => { + patchQueries.push( + CW721Activity.query() + .patch({ + from: getAttributeFrom( + record.smart_contract_event.attributes, + EventAttribute.ATTRIBUTE_KEY.SENDER + ), + to: + getAttributeFrom( + record.smart_contract_event.attributes, + EventAttribute.ATTRIBUTE_KEY.OWNER + ) || + getAttributeFrom( + record.smart_contract_event.attributes, + EventAttribute.ATTRIBUTE_KEY.RECIPIENT + ), + }) + .where('id', record.id) + .transacting(trx) + ); + }); + await Promise.all(patchQueries); + prevId = handleRecords[handleRecords.length - 1].id; + } else { + break; + } + } + }); +} + +export async function down(knex: Knex): Promise {} diff --git a/src/models/cw721_tx.ts b/src/models/cw721_tx.ts index 3f20ce128..241949888 100644 --- a/src/models/cw721_tx.ts +++ b/src/models/cw721_tx.ts @@ -9,7 +9,9 @@ import { Event } from './event'; export default class CW721Activity extends BaseModel { static softDelete = false; - id?: number; + smart_contract_event!: SmartContractEvent; + + id!: number; action?: string; diff --git a/src/models/event_attribute.ts b/src/models/event_attribute.ts index 2fb47b872..16227d816 100644 --- a/src/models/event_attribute.ts +++ b/src/models/event_attribute.ts @@ -96,6 +96,7 @@ export class EventAttribute extends BaseModel { GRANTER: 'granter', GRANTEE: 'grantee', FROM: 'from', + MINTER: 'minter', FEE: 'fee', FEE_PAYER: 'fee_payer', }; diff --git a/src/models/smart_contract_event.ts b/src/models/smart_contract_event.ts index c7d592c1d..bdf66e80a 100644 --- a/src/models/smart_contract_event.ts +++ b/src/models/smart_contract_event.ts @@ -10,6 +10,8 @@ import { TransactionMessage } from './transaction_message'; export class SmartContractEvent extends BaseModel { [relation: string]: any; + attributes!: SmartContractEventAttribute; + id!: number; smart_contract_id!: number; diff --git a/src/services/cw721/cw721.service.ts b/src/services/cw721/cw721.service.ts index b924d27c5..5f9b4653a 100644 --- a/src/services/cw721/cw721.service.ts +++ b/src/services/cw721/cw721.service.ts @@ -332,6 +332,19 @@ export default class Cw721HandlerService extends BullableService { cw721_token_id: cw721TokenId, height: cw721Event.height, smart_contract_event_id: cw721Event.smart_contract_event_id, + from: getAttributeFrom( + cw721Event.attributes, + EventAttribute.ATTRIBUTE_KEY.SENDER + ), + to: + getAttributeFrom( + cw721Event.attributes, + EventAttribute.ATTRIBUTE_KEY.OWNER + ) || + getAttributeFrom( + cw721Event.attributes, + EventAttribute.ATTRIBUTE_KEY.RECIPIENT + ), }) ) .onConflict(['smart_contract_event_id']) diff --git a/test/unit/services/cw721/cw721.spec.ts b/test/unit/services/cw721/cw721.spec.ts index 3a670e090..60c95a64f 100644 --- a/test/unit/services/cw721/cw721.spec.ts +++ b/test/unit/services/cw721/cw721.spec.ts @@ -2,8 +2,16 @@ import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; import { ServiceBroker } from 'moleculer'; import { BULL_JOB_NAME } from '../../../../src/common'; import knex from '../../../../src/common/utils/db_connection'; -import { getContractActivities } from '../../../../src/common/utils/smart_contract'; -import { Block, BlockCheckpoint, Transaction } from '../../../../src/models'; +import { + getAttributeFrom, + getContractActivities, +} from '../../../../src/common/utils/smart_contract'; +import { + Block, + BlockCheckpoint, + EventAttribute, + Transaction, +} from '../../../../src/models'; import { Code } from '../../../../src/models/code'; import CW721Contract from '../../../../src/models/cw721_contract'; import CW721Token from '../../../../src/models/cw721_token'; @@ -1296,6 +1304,31 @@ export default class AssetIndexerTest { expect(cw721Activities[0].cw721_token_id).toEqual(1); expect(cw721Activities[1].cw721_token_id).toEqual(0); expect(cw721Activities[2].cw721_token_id).toEqual(2); + expect(cw721Activities[0].from).toEqual(null); + expect(cw721Activities[0].to).toEqual( + getAttributeFrom( + mockActivityMsgs[0].attributes, + EventAttribute.ATTRIBUTE_KEY.OWNER + ) + ); + expect(cw721Activities[1].from).toEqual( + getAttributeFrom( + mockActivityMsgs[1].attributes, + EventAttribute.ATTRIBUTE_KEY.SENDER + ) + ); + expect(cw721Activities[1].to).toEqual( + getAttributeFrom( + mockActivityMsgs[1].attributes, + EventAttribute.ATTRIBUTE_KEY.RECIPIENT + ) + ); + expect(cw721Activities[2].from).toEqual( + getAttributeFrom( + mockActivityMsgs[2].attributes, + EventAttribute.ATTRIBUTE_KEY.SENDER + ) + ); } @Test('test handle multi contract events') From dcec038f79d9afe4be72eb498364ba9d8f46f845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Th=C3=A0nh=20Phong?= <49814372+phamphong9981@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:02:08 +0700 Subject: [PATCH 16/33] feat: crawl ibc tao ( main ) (#278) * feat: crawl ibc tao * feat: crawl genesis ibc tao * fix: code * fix: code --- ci/config.json.ci | 5 + config.json | 5 + migrations/20230802071931_ibc_tao_model.ts | 36 ++ src/common/constant.ts | 6 + src/models/event.ts | 10 +- src/models/event_attribute.ts | 9 + src/models/ibc_channel.ts | 64 +++ src/models/ibc_client.ts | 40 ++ src/models/ibc_connection.ts | 51 +++ src/models/index.ts | 3 + .../api-gateways/api_gateway.service.ts | 8 +- .../crawl-genesis/crawl_genesis.service.ts | 114 +++++ src/services/ibc/crawl_ibc_tao.service.ts | 259 +++++++++++ test/unit/services/ibc/crawl_ibc_tao.spec.ts | 428 ++++++++++++++++++ 14 files changed, 1035 insertions(+), 3 deletions(-) create mode 100644 migrations/20230802071931_ibc_tao_model.ts create mode 100644 src/models/ibc_channel.ts create mode 100644 src/models/ibc_client.ts create mode 100644 src/models/ibc_connection.ts create mode 100644 src/services/ibc/crawl_ibc_tao.service.ts create mode 100644 test/unit/services/ibc/crawl_ibc_tao.spec.ts diff --git a/ci/config.json.ci b/ci/config.json.ci index bf53142cf..afc6cd564 100644 --- a/ci/config.json.ci +++ b/ci/config.json.ci @@ -174,5 +174,10 @@ }, "jobRedecodeTx": { "limitRecordGet": 100 + }, + "crawlIbcTao": { + "key": "crawlIbcTao", + "millisecondRepeatJob": 2000, + "blocksPerCall": 100 } } diff --git a/config.json b/config.json index d6766950a..686cbb866 100644 --- a/config.json +++ b/config.json @@ -174,5 +174,10 @@ }, "jobRedecodeTx": { "limitRecordGet": 100 + }, + "crawlIbcTao": { + "key": "crawlIbcTao", + "millisecondRepeatJob": 2000, + "blocksPerCall": 100 } } diff --git a/migrations/20230802071931_ibc_tao_model.ts b/migrations/20230802071931_ibc_tao_model.ts new file mode 100644 index 000000000..a1b2a03b4 --- /dev/null +++ b/migrations/20230802071931_ibc_tao_model.ts @@ -0,0 +1,36 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.createTable('ibc_client', (table) => { + table.increments(); + table.string('client_id').notNullable().unique(); + table.string('counterparty_chain_id').notNullable(); + table.jsonb('client_state').notNullable(); + table.jsonb('consensus_state').notNullable(); + table.string('client_type').notNullable(); + }); + await knex.schema.createTable('ibc_connection', (table) => { + table.increments(); + table.integer('ibc_client_id').index(); + table.string('connection_id').notNullable().unique(); + table.string('counterparty_client_id').notNullable(); + table.string('counterparty_connection_id').notNullable(); + table.foreign('ibc_client_id').references('ibc_client.id'); + }); + await knex.schema.createTable('ibc_channel', (table) => { + table.increments(); + table.integer('ibc_connection_id').index(); + table.string('channel_id').notNullable().unique(); + table.string('port_id').notNullable().index(); + table.string('counterparty_port_id').notNullable(); + table.string('counterparty_channel_id').notNullable(); + table.string('state').notNullable(); + table.foreign('ibc_connection_id').references('ibc_connection.id'); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.dropTableIfExists('ibc_client'); + await knex.schema.dropTableIfExists('ibc_connection'); + await knex.schema.dropTableIfExists('ibc_channel'); +} diff --git a/src/common/constant.ts b/src/common/constant.ts index 9e08c3600..465d9b5ad 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -71,6 +71,8 @@ export const BULL_JOB_NAME = { REINDEX_CW721_HISTORY: 'reindex:cw721-history', HANDLE_MIGRATE_CONTRACT: 'handle:migrate-contract', JOB_REDECODE_TX: 'job:redecode-tx', + CRAWL_IBC_TAO: 'crawl:ibc-tao', + CRAWL_GENESIS_IBC_TAO: 'crawl:genesis-ibc-tao', REINDEX_CW20_CONTRACT: 'reindex:cw20-contract', REINDEX_CW20_HISTORY: 'reindex:cw20-history', }; @@ -230,6 +232,10 @@ export const SERVICE = { path: 'v1.ReDecodeTx', }, }, + CrawlIBCTaoService: { + key: 'CrawlIBCTaoService', + name: 'v1.CrawlIBCTaoService', + }, DailyStatisticsService: { key: 'DailyStatisticsService', name: 'v1.DailyStatisticsService', diff --git a/src/models/event.ts b/src/models/event.ts index 3f983faea..da9a05b6b 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -6,7 +6,7 @@ import { EventAttribute } from './event_attribute'; import { TransactionMessage } from './transaction_message'; export class Event extends BaseModel { - [relation: string]: any; + [relation: string]: any | any[]; id!: string; @@ -94,5 +94,13 @@ export class Event extends BaseModel { TX: 'tx', TRANSFER: 'transfer', MIGRATE: 'migrate', + CREATE_CLIENT: 'create_client', + CONNECTION_OPEN_ACK: 'connection_open_ack', + CONNECTION_OPEN_CONFIRM: 'connection_open_confirm', + CHANNEL_OPEN_ACK: 'channel_open_ack', + CHANNEL_OPEN_CONFIRM: 'channel_open_confirm', + CHANNEL_CLOSE_INIT: 'channel_close_init', + CHANNEL_CLOSE_CONFIRM: 'channel_close_confirm', + CHANNEL_CLOSE: 'channel_close', }; } diff --git a/src/models/event_attribute.ts b/src/models/event_attribute.ts index 16227d816..48a8d4f7d 100644 --- a/src/models/event_attribute.ts +++ b/src/models/event_attribute.ts @@ -96,6 +96,15 @@ export class EventAttribute extends BaseModel { GRANTER: 'granter', GRANTEE: 'grantee', FROM: 'from', + CLIENT_ID: 'client_id', + CLIENT_TYPE: 'client_type', + CONNECTION_ID: 'connection_id', + COUNTERPARTY_CLIENT_ID: 'counterparty_client_id', + COUNTERPARTY_CONNECTION_ID: 'counterparty_connection_id', + CHANNEL_ID: 'channel_id', + PORT_ID: 'port_id', + COUNTERPARTY_PORT_ID: 'counterparty_port_id', + COUNTERPARTY_CHANNEL_ID: 'counterparty_channel_id', MINTER: 'minter', FEE: 'fee', FEE_PAYER: 'fee_payer', diff --git a/src/models/ibc_channel.ts b/src/models/ibc_channel.ts new file mode 100644 index 000000000..b1720ccaa --- /dev/null +++ b/src/models/ibc_channel.ts @@ -0,0 +1,64 @@ +/* eslint-disable import/no-cycle */ +import { Model } from 'objection'; +import BaseModel from './base'; +import { IbcConnection } from './ibc_connection'; + +export class IbcChannel extends BaseModel { + id!: number; + + ibc_connection_id!: number; + + channel_id!: string; + + port_id!: string; + + counterparty_port_id!: string; + + counterparty_channel_id!: string; + + state!: string; + + static get tableName() { + return 'ibc_channel'; + } + + static get jsonSchema() { + return { + type: 'object', + required: [ + 'ibc_connection_id', + 'channel_id', + 'port_id', + 'counterparty_port_id', + 'counterparty_channel_id', + 'state', + ], + properties: { + ibc_connection_id: { type: 'number' }, + channel_id: { type: 'string' }, + port_id: { type: 'string' }, + counterparty_port_id: { type: 'string' }, + counterparty_channel_id: { type: 'string' }, + state: { type: 'string' }, + }, + }; + } + + static get relationMappings() { + return { + ibc_connection: { + relation: Model.BelongsToOneRelation, + modelClass: IbcConnection, + join: { + from: 'ibc_channel.ibc_connection_id', + to: 'ibc_connection.id', + }, + }, + }; + } + + static STATUS = { + OPEN: 'OPEN', + CLOSE: 'CLOSE', + }; +} diff --git a/src/models/ibc_client.ts b/src/models/ibc_client.ts new file mode 100644 index 000000000..4575e446d --- /dev/null +++ b/src/models/ibc_client.ts @@ -0,0 +1,40 @@ +/* eslint-disable import/no-cycle */ +import BaseModel from './base'; + +export class IbcClient extends BaseModel { + id!: number; + + client_id!: string; + + counterparty_chain_id!: string; + + client_state!: any; + + consensus_state!: any; + + client_type!: string; + + static get tableName() { + return 'ibc_client'; + } + + static get jsonSchema() { + return { + type: 'object', + required: [ + 'client_id', + 'counterparty_chain_id', + 'client_state', + 'consensus_state', + 'client_type', + ], + properties: { + client_id: { type: 'string' }, + counterparty_chain_id: { type: 'string' }, + client_state: { type: 'object' }, + consensus_state: { type: 'object' }, + client_type: { type: 'string' }, + }, + }; + } +} diff --git a/src/models/ibc_connection.ts b/src/models/ibc_connection.ts new file mode 100644 index 000000000..df9ed2e24 --- /dev/null +++ b/src/models/ibc_connection.ts @@ -0,0 +1,51 @@ +/* eslint-disable import/no-cycle */ +import { Model } from 'objection'; +import BaseModel from './base'; +import { IbcClient } from './ibc_client'; + +export class IbcConnection extends BaseModel { + id!: number; + + ibc_client_id!: number; + + connection_id!: string; + + counterparty_client_id!: string; + + counterparty_connection_id!: string; + + static get tableName() { + return 'ibc_connection'; + } + + static get jsonSchema() { + return { + type: 'object', + required: [ + 'ibc_client_id', + 'connection_id', + 'counterparty_client_id', + 'counterparty_connection_id', + ], + properties: { + ibc_client_id: { type: 'number' }, + connection_id: { type: 'string' }, + counterparty_client_id: { type: 'string' }, + counterparty_connection_id: { type: 'string' }, + }, + }; + } + + static get relationMappings() { + return { + ibc_client: { + relation: Model.BelongsToOneRelation, + modelClass: IbcClient, + join: { + from: 'ibc_connection.ibc_client_id', + to: 'ibc_client.id', + }, + }, + }; + } +} diff --git a/src/models/index.ts b/src/models/index.ts index 9ee83f061..02310cefe 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -25,3 +25,6 @@ export * from './feegrant_history'; export * from './daily_statistics'; export * from './account_statistics'; export * from './cw20_total_holder_stats'; +export * from './ibc_client'; +export * from './ibc_connection'; +export * from './ibc_channel'; diff --git a/src/services/api-gateways/api_gateway.service.ts b/src/services/api-gateways/api_gateway.service.ts index 8fd7ddb66..5021b71ab 100644 --- a/src/services/api-gateways/api_gateway.service.ts +++ b/src/services/api-gateways/api_gateway.service.ts @@ -31,8 +31,12 @@ import { bullBoardMixin } from '../../mixins/bullBoard/bullBoard.mixin'; path: '/admin', autoAliases: true, // allow generate rest info (GET/PUT/POST...) in the services mappingPolicy: 'restrict', // allow action called with exact method - whitelist: ['v2.statistics.syncPrevDateStatsByChainId', 'v1.cw20-admin.*', 'v1.cw721-admin.*'], - } + whitelist: [ + 'v2.statistics.syncPrevDateStatsByChainId', + 'v1.cw20-admin.*', + 'v1.cw721-admin.*', + ], + }, ], // empty cors object will have moleculer to generate handler for preflight request and CORS header which allow all origin cors: {}, diff --git a/src/services/crawl-genesis/crawl_genesis.service.ts b/src/services/crawl-genesis/crawl_genesis.service.ts index 03824afea..9a3465984 100644 --- a/src/services/crawl-genesis/crawl_genesis.service.ts +++ b/src/services/crawl-genesis/crawl_genesis.service.ts @@ -22,6 +22,9 @@ import { SmartContract, Validator, Feegrant, + IbcClient, + IbcConnection, + IbcChannel, } from '../../models'; import BullableService, { QueueHandler } from '../../base/bullable.service'; import { @@ -52,6 +55,7 @@ export default class CrawlGenesisService extends BullableService { BULL_JOB_NAME.CRAWL_GENESIS_CODE, BULL_JOB_NAME.CRAWL_GENESIS_CONTRACT, BULL_JOB_NAME.CRAWL_GENESIS_FEEGRANT, + BULL_JOB_NAME.CRAWL_GENESIS_IBC_TAO, ]; public constructor(public broker: ServiceBroker) { @@ -669,6 +673,116 @@ export default class CrawlGenesisService extends BullableService { await this.terminateProcess(); } + @QueueHandler({ + queueName: BULL_JOB_NAME.CRAWL_GENESIS_IBC_TAO, + jobName: BULL_JOB_NAME.CRAWL_GENESIS_IBC_TAO, + // prefix: `horoscope-v2-${config.chainId}`, + }) + public async crawlGenesisIbcTao(_payload: object): Promise { + this.logger.info('Crawl genesis Ibc Tao'); + + const genesisProcess = await this.checkGenesisJobProcess( + BULL_JOB_NAME.CRAWL_GENESIS_IBC_TAO + ); + if (genesisProcess !== 0) return; + await knex + .transaction(async (trx) => { + // crawl genesis ibc client + const genClients: any[] = await this.readStreamGenesis( + 'app_state.ibc.client_genesis.clients' + ); + const genClientsConsensus: any[] = await this.readStreamGenesis( + 'app_state.ibc.client_genesis.clients_consensus' + ); + const ibcClients: IbcClient[] = genClients.map((genClient: any) => { + const consensusStates = genClientsConsensus.find( + (clientConsensus) => + clientConsensus.client_id === genClient.client_id + ); + return IbcClient.fromJson({ + client_id: genClient.client_id, + counterparty_chain_id: genClient.client_state.chain_id, + client_state: genClient.client_state, + consensus_state: + consensusStates.consensus_states[ + consensusStates.consensus_states.length - 1 + ], + client_type: genClient.client_id.substring( + 0, + genClient.client_id.lastIndexOf('-') + ), + }); + }); + let newClients: IbcClient[] = []; + if (ibcClients.length > 0) { + newClients = await IbcClient.query() + .insert(ibcClients) + .transacting(trx); + } + this.logger.info('Done client'); + // crawl genesis ibc connections + const genConnections: any[] = await this.readStreamGenesis( + 'app_state.ibc.connection_genesis.connections' + ); + const ibcConnections: IbcConnection[] = genConnections.map( + (genConnection: any) => + IbcConnection.fromJson({ + ibc_client_id: newClients.find( + (client) => client.client_id === genConnection.client_id + )?.id, + connection_id: genConnection.id, + counterparty_client_id: genConnection.counterparty.client_id, + counterparty_connection_id: + genConnection.counterparty.connection_id, + }) + ); + let newConnections: IbcConnection[] = []; + if (ibcConnections.length > 0) { + newConnections = await IbcConnection.query() + .insert(ibcConnections) + .transacting(trx); + } + this.logger.info('Done connections'); + // crawl genesis ibc channels + const genChannels: any[] = await this.readStreamGenesis( + 'app_state.ibc.channel_genesis.channels' + ); + const IbcChannels: IbcChannel[] = genChannels.map((genChannel: any) => + IbcChannel.fromJson({ + ibc_connection_id: newConnections.find( + (connection) => + connection.connection_id === genChannel.connection_hops[0] + )?.id, + channel_id: genChannel.channel_id, + port_id: genChannel.port_id, + counterparty_port_id: genChannel.counterparty.port_id, + counterparty_channel_id: genChannel.counterparty.channel_id, + state: genChannel.state, + }) + ); + if (IbcChannels.length > 0) { + await IbcChannel.query().insert(IbcChannels).transacting(trx); + } + this.logger.info('Done channel'); + await BlockCheckpoint.query() + .insert( + BlockCheckpoint.fromJson({ + job_name: BULL_JOB_NAME.CRAWL_GENESIS_IBC_TAO, + height: 1, + }) + ) + .onConflict('job_name') + .merge() + .returning('id') + .transacting(trx); + }) + .catch((error) => { + this.logger.error(error); + throw error; + }); + await this.terminateProcess(); + } + private async handleIbcDenom(accounts: Account[]): Promise { if (accounts.length === 0) return []; diff --git a/src/services/ibc/crawl_ibc_tao.service.ts b/src/services/ibc/crawl_ibc_tao.service.ts new file mode 100644 index 000000000..4f047f58c --- /dev/null +++ b/src/services/ibc/crawl_ibc_tao.service.ts @@ -0,0 +1,259 @@ +import { Service } from '@ourparentcenter/moleculer-decorators-extended'; +import { ServiceBroker } from 'moleculer'; +import { Knex } from 'knex'; +import _ from 'lodash'; +import knex from '../../common/utils/db_connection'; +import config from '../../../config.json' assert { type: 'json' }; +import BullableService, { QueueHandler } from '../../base/bullable.service'; +import { BULL_JOB_NAME, SERVICE } from '../../common'; +import { + BlockCheckpoint, + Event, + EventAttribute, + IbcChannel, + IbcClient, + IbcConnection, +} from '../../models'; +import { getAttributeFrom } from '../../common/utils/smart_contract'; + +@Service({ + name: SERVICE.V1.CrawlIBCTaoService.key, + version: 1, +}) +export default class CrawlIbcTaoService extends BullableService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.CRAWL_IBC_TAO, + jobName: BULL_JOB_NAME.CRAWL_IBC_TAO, + }) + public async crawlIbcTao(): Promise { + const [startHeight, endHeight, updateBlockCheckpoint] = + await BlockCheckpoint.getCheckpoint( + BULL_JOB_NAME.CRAWL_IBC_TAO, + [BULL_JOB_NAME.HANDLE_TRANSACTION], + config.crawlIbcTao.key + ); + this.logger.info( + `Handle IBC/TAO, startHeight: ${startHeight}, endHeight: ${endHeight}` + ); + if (startHeight > endHeight) return; + const events = await Event.query() + .withGraphFetched('attributes') + .joinRelated('message') + .select('event.id', 'event.type', 'message.content') + .whereIn('event.type', [ + Event.EVENT_TYPE.CREATE_CLIENT, + Event.EVENT_TYPE.CONNECTION_OPEN_ACK, + Event.EVENT_TYPE.CONNECTION_OPEN_CONFIRM, + Event.EVENT_TYPE.CHANNEL_OPEN_ACK, + Event.EVENT_TYPE.CHANNEL_OPEN_CONFIRM, + Event.EVENT_TYPE.CHANNEL_CLOSE_INIT, + Event.EVENT_TYPE.CHANNEL_CLOSE_CONFIRM, + Event.EVENT_TYPE.CHANNEL_CLOSE, + ]) + .andWhere('event.block_height', '>', startHeight) + .andWhere('event.block_height', '<=', endHeight) + .orderBy('event.id'); + await knex.transaction(async (trx) => { + await this.handleNewIbcClient( + events.filter((event) => event.type === Event.EVENT_TYPE.CREATE_CLIENT), + trx + ); + await this.handleNewIbcConnection( + events.filter( + (event) => + event.type === Event.EVENT_TYPE.CONNECTION_OPEN_CONFIRM || + event.type === Event.EVENT_TYPE.CONNECTION_OPEN_ACK + ), + trx + ); + await this.handleNewIbcChannel( + events.filter( + (event) => + event.type === Event.EVENT_TYPE.CHANNEL_OPEN_ACK || + event.type === Event.EVENT_TYPE.CHANNEL_OPEN_CONFIRM + ), + trx + ); + await this.handleCloseIbcChannel( + events.filter( + (event) => + event.type === Event.EVENT_TYPE.CHANNEL_CLOSE || + event.type === Event.EVENT_TYPE.CHANNEL_CLOSE_CONFIRM || + event.type === Event.EVENT_TYPE.CHANNEL_CLOSE_INIT + ), + trx + ); + updateBlockCheckpoint.height = endHeight; + await BlockCheckpoint.query() + .transacting(trx) + .insert(updateBlockCheckpoint) + .onConflict('job_name') + .merge(); + }); + } + + async handleNewIbcClient(events: Event[], trx: Knex.Transaction) { + if (events.length > 0) { + const newClients: IbcClient[] = events.map((event) => + IbcClient.fromJson({ + client_id: getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.CLIENT_ID + ), + counterparty_chain_id: event.content.client_state.chain_id, + client_state: event.content.client_state, + consensus_state: event.content.consensus_state, + client_type: getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.CLIENT_TYPE + ), + }) + ); + this.logger.info('New IBC Clients:'); + this.logger.info(newClients); + await IbcClient.query().insert(newClients).transacting(trx); + } + } + + async handleNewIbcConnection(events: Event[], trx: Knex.Transaction) { + if (events.length > 0) { + const ibcClientsByClientId = _.keyBy( + await IbcClient.query() + .whereIn( + 'client_id', + events.map((event) => + getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.CLIENT_ID + ) + ) + ) + .transacting(trx), + 'client_id' + ); + const newConnections: IbcConnection[] = events.map((event) => + IbcConnection.fromJson({ + ibc_client_id: + ibcClientsByClientId[ + getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.CLIENT_ID + ) + ].id, + connection_id: getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.CONNECTION_ID + ), + counterparty_client_id: getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.COUNTERPARTY_CLIENT_ID + ), + counterparty_connection_id: getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.COUNTERPARTY_CONNECTION_ID + ), + }) + ); + this.logger.info('New IBC Connections:'); + this.logger.info(newConnections); + await IbcConnection.query().insert(newConnections).transacting(trx); + } + } + + async handleNewIbcChannel(events: Event[], trx: Knex.Transaction) { + if (events.length > 0) { + const ibcConnectionsByConnetionId = _.keyBy( + await IbcConnection.query() + .whereIn( + 'connection_id', + events.map((event) => + getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.CONNECTION_ID + ) + ) + ) + .transacting(trx), + 'connection_id' + ); + const newChannels: IbcChannel[] = events.map((event) => + IbcChannel.fromJson({ + ibc_connection_id: + ibcConnectionsByConnetionId[ + getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.CONNECTION_ID + ) + ].id, + channel_id: getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.CHANNEL_ID + ), + port_id: getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.PORT_ID + ), + counterparty_port_id: getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.COUNTERPARTY_PORT_ID + ), + counterparty_channel_id: getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.COUNTERPARTY_CHANNEL_ID + ), + state: IbcChannel.STATUS.OPEN, + }) + ); + this.logger.info('New IBC Channels:'); + this.logger.info(newChannels); + await IbcChannel.query().insert(newChannels).transacting(trx); + } + } + + async handleCloseIbcChannel(events: Event[], trx: Knex.Transaction) { + if (events.length > 0) { + const queries = events.map((event) => { + const channelId = getAttributeFrom( + event.attributes, + EventAttribute.ATTRIBUTE_KEY.CHANNEL_ID + ); + if (!channelId) { + throw new Error( + `Event close channel not found channelId: ${event.id}` + ); + } + return IbcChannel.query() + .patch({ + state: IbcChannel.STATUS.CLOSE, + }) + .where({ + channel_id: channelId, + }) + .transacting(trx); + }); + await Promise.all(queries); + } + } + + async _start(): Promise { + await this.createJob( + BULL_JOB_NAME.CRAWL_IBC_TAO, + BULL_JOB_NAME.CRAWL_IBC_TAO, + {}, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + repeat: { + every: config.crawlIbcTao.millisecondRepeatJob, + }, + } + ); + return super._start(); + } +} diff --git a/test/unit/services/ibc/crawl_ibc_tao.spec.ts b/test/unit/services/ibc/crawl_ibc_tao.spec.ts new file mode 100644 index 000000000..699a6d02c --- /dev/null +++ b/test/unit/services/ibc/crawl_ibc_tao.spec.ts @@ -0,0 +1,428 @@ +import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; +import { ServiceBroker } from 'moleculer'; +import knex from '../../../../src/common/utils/db_connection'; +import { + Block, + Event, + IbcChannel, + IbcClient, + IbcConnection, + Transaction, +} from '../../../../src/models'; +import CrawlIbcTaoService from '../../../../src/services/ibc/crawl_ibc_tao.service'; + +@Describe('Test crawl ibc service') +export default class CrawlIbcTest { + broker = new ServiceBroker({ logger: false }); + + crawlIbcTaoSerivce = this.broker.createService( + CrawlIbcTaoService + ) as CrawlIbcTaoService; + + block: Block = Block.fromJson({ + height: 1300000, + hash: '4801997745BDD354C8F11CE4A4137237194099E664CD8F83A5FBA9041C43FE9F', + time: '2023-01-12T01:53:57.216Z', + proposer_address: 'auraomd;cvpio3j4eg', + data: {}, + }); + + transaction: Transaction = Transaction.fromJson({ + height: this.block.height, + hash: '4A8B0DE950F563553A81360D4782F6EC451F6BEF7AC50E2459D1997FA168997D', + codespace: '', + code: 0, + gas_used: '123035', + gas_wanted: '141106', + gas_limit: '141106', + fee: 353, + timestamp: '2023-01-12T01:53:57.000Z', + index: 0, + data: { + tx_response: { + logs: [], + }, + }, + }); + + @BeforeAll() + async initSuite() { + this.crawlIbcTaoSerivce.getQueueManager().stopAll(); + await knex.raw('TRUNCATE TABLE block, ibc_client RESTART IDENTITY CASCADE'); + await Block.query().insert(this.block); + } + + @AfterAll() + async tearDown() { + await this.broker.stop(); + } + + @Test('Test handleNewIbcClient') + async testHandleNewIbcClient() { + await knex.transaction(async (trx) => { + const events = Event.fromJson({ + type: Event.EVENT_TYPE.CREATE_CLIENT, + attributes: [ + { + value: '07-tendermint-21', + key: 'client_id', + event_id: '1', + }, + { + value: '07-tendermint', + key: 'client_type', + event_id: '1', + }, + { + value: '3-5086858', + key: 'consensus_height', + event_id: '1', + }, + ], + content: { + '@type': '/ibc.core.client.v1.MsgCreateClient', + signer: 'aura1gypt2w7xg5t9yr76hx6zemwd4xv72jckk03r6t', + client_state: { + '@type': '/ibc.lightclients.tendermint.v1.ClientState', + chain_id: 'axelar-testnet-lisbon-3', + proof_specs: [ + { + leaf_spec: { + hash: 'SHA256', + length: 'VAR_PROTO', + prefix: 'AA==', + prehash_key: 'NO_HASH', + prehash_value: 'SHA256', + }, + max_depth: 0, + min_depth: 0, + inner_spec: { + hash: 'SHA256', + child_size: 33, + child_order: [0, 1], + empty_child: '', + max_prefix_length: 12, + min_prefix_length: 4, + }, + }, + { + leaf_spec: { + hash: 'SHA256', + length: 'VAR_PROTO', + prefix: 'AA==', + prehash_key: 'NO_HASH', + prehash_value: 'SHA256', + }, + max_depth: 0, + min_depth: 0, + inner_spec: { + hash: 'SHA256', + child_size: 32, + child_order: [0, 1], + empty_child: '', + max_prefix_length: 1, + min_prefix_length: 1, + }, + }, + ], + trust_level: { + numerator: '1', + denominator: '3', + }, + upgrade_path: ['upgrade', 'upgradedIBCState'], + frozen_height: { + revision_height: '0', + revision_number: '0', + }, + latest_height: { + revision_height: '5086858', + revision_number: '3', + }, + max_clock_drift: '40000000000', + trusting_period: '403200000000000', + unbonding_period: '604800000000000', + allow_update_after_expiry: true, + allow_update_after_misbehaviour: true, + }, + consensus_state: { + root: { + hash: '/iST1C+vhywD9qgGNqSQvs0NIPlQMqIDwgIJKMIylUI=', + }, + '@type': '/ibc.lightclients.tendermint.v1.ConsensusState', + timestamp: '2022-12-01T07:22:43.523Z', + next_validators_hash: + 'RY9Nf/qtdDMVQK7LMjoVgrS1CkZaEVj02CDlkdgzutM=', + }, + }, + }); + await this.crawlIbcTaoSerivce.handleNewIbcClient([events], trx); + const newClient = await IbcClient.query() + .transacting(trx) + .first() + .throwIfNotFound(); + expect(newClient.client_id).toEqual(events.attributes[0].value); + expect(newClient.client_type).toEqual(events.attributes[1].value); + expect(newClient.client_state).toEqual(events.content.client_state); + expect(newClient.consensus_state).toEqual(events.content.consensus_state); + expect(newClient.counterparty_chain_id).toEqual( + events.content.client_state.chain_id + ); + // await trx.rollback(); + }); + } + + @Test('Test handleNewIbcConnection') + async testHandleNewIbcConnection() { + await knex.transaction(async (trx) => { + const events = Event.fromJson({ + type: Event.EVENT_TYPE.CONNECTION_OPEN_ACK, + attributes: [ + { + value: 'connection-72', + key: 'connection_id', + event_id: '1', + }, + { + value: '07-tendermint-21', + key: 'client_id', + event_id: '1', + }, + { + value: '07-tendermint-45', + key: 'counterparty_client_id', + event_id: '1', + }, + { + value: 'connection-27', + key: 'counterparty_connection_id', + event_id: '1', + }, + ], + content: { + '@type': '/ibc.core.connection.v1.MsgConnectionOpenAck', + signer: 'aura1gypt2w7xg5t9yr76hx6zemwd4xv72jckk03r6t', + version: { + features: ['ORDER_ORDERED', 'ORDER_UNORDERED'], + identifier: '1', + }, + proof_try: + 'CsQICsEIChljb25uZWN0aW9ucy9jb25uZWN0aW9uLTI3EmMKEDA3LXRlbmRlcm1pbnQtNDUSIwoBMRINT1JERVJfT1JERVJFRBIPT1JERVJfVU5PUkRFUkVEGAIiKAoQMDctdGVuZGVybWludC04MBINY29ubmVjdGlvbi03MhoFCgNpYmMaDggBGAEgASoGAAKa8pYEIi4IARIHAgSa8pYEIBohIB/PBZ4uljQRWTUoJWTvlXAf+Sf3geF7LsWKI7+WpaUwIiwIARIoBAia8pYEIEIyNKRwbo7UewNW1Bg6gWp+7QriRoCvyv6ZbKjZHs9wICIsCAESKAYMmvKWBCCeMewQOVwuaeBrU7LaB5kuNW/4JAlZjbrMXHi+1d1mlSAiLAgBEigIGprylgQg3u9esjU846jXfzCL7RGmbCTrgcWgML7vMgDjjXy8LkUgIi4IARIHCiaa8pYEIBohIDGrQ5aJZsN6Ut9GQ3wNVnStZ8Dr7mrIPRFRG/Zv3Z5CIiwIARIoDDia8pYEIHbOF4x7UN1YqA+xoo7KL7w0/rXENvBouFTaVqaJPA7gICIsCAESKA5gmvKWBCA9QpxrAfbqqlM63etC3YtsbTzTJAWBnIGK430UOkRK2CAiLQgBEikQ0AGi8pYEICUsHnwDn1/px2K6DyMm9lISpOrK4TNSLLpbJKbmXp6OICItCAESKRKuA6LylgQgXH14FUNckJ7q6yDzHxVWwkawlgmkdaa9650oE2k2OjsgIi0IARIpFKYFovKWBCAkaa0OlC4pKo4m2xRQeVbx0tieLhfak6Xsu5QVp9cXCCAiLQgBEikWxAqi8pYEIIOT6BYqI5lA+xTBTIRO+dPHH+MnF/7FXoFVz1/cEdVoICItCAESKRj0D6LylgQgxiwkb7P1fNqTdZUSduYl+OfDeXURD69QiFtfjNelu5UgIi0IARIpGqodovKWBCAuUcxazpKF/ptiPz3WEHJuhQNHrYxXUohbInjviGhzCCAiLQgBEikc2F6i8pYEIGlpNHXMBW35F/0aor1DBEL5XBEKxhQigsCTP1pmNykLICIuCAESKh7YlAGi8pYEINa1ZObRgViKtwVE+LVx59w3/oF2pnB0sjlIYyPhwFu2ICIuCAESKiDY7AKi8pYEIPFQOkaEFsZuc5VCL3XLMvndPVA1pCpQVUqq1MAtCRxOICIuCAESKiLq7wWi8pYEIG+vkeRtqUosbXbXctdffMe0oJ0VwyLp+b6J21UuXLBOICIuCAESKibe5Rai8pYEIMdjCIRMYnQf4Nph+jj/sgQbJOO/p/fXqVBXy3hKUr7TICIwCAESCSr+mCai8pYEIBohIKJhx7HKM0nG82d3MAl9cguF5EaDS8VfAQ6E2jwPqc88Ii4IARIqLNiOQaLylgQgZGldPsQJXlzRlduhZIdTRH4SHfo5Xbh/ouka5hktIfcgCv4BCvsBCgNpYmMSIAD8TsE9shScVyP/CHb+hr1siAWPBMHaduu4dWdgj1rBGgkIARgBIAEqAQAiJwgBEgEBGiD5Vt5/BBmvQQnMGXTh8TcjCr0rjLn1J3de7MoaiBcPIiIlCAESIQEvzAtKPUXmsWa73ufs4PS4NzfxMUJ/f7tJz7zRzeuU8CInCAESAQEaIAeJGXGcGsRVvSn5H69yS0J+ru1UFKAa8tkcM2iQ3uOEIiUIARIhAWVN4MYZgTIpdDlaD9rFq8YDvzV9R+cVZEWff23oGt7iIicIARIBARogbDU0NT+iBtS39EPHJu5GTrbal9trUT5GhRdwLYLr+WQ=', + client_state: { + '@type': '/ibc.lightclients.tendermint.v1.ClientState', + chain_id: 'euphoria-2', + proof_specs: [ + { + leaf_spec: { + hash: 'SHA256', + length: 'VAR_PROTO', + prefix: 'AA==', + prehash_key: 'NO_HASH', + prehash_value: 'SHA256', + }, + max_depth: 0, + min_depth: 0, + inner_spec: { + hash: 'SHA256', + child_size: 33, + child_order: [0, 1], + empty_child: '', + max_prefix_length: 12, + min_prefix_length: 4, + }, + }, + { + leaf_spec: { + hash: 'SHA256', + length: 'VAR_PROTO', + prefix: 'AA==', + prehash_key: 'NO_HASH', + prehash_value: 'SHA256', + }, + max_depth: 0, + min_depth: 0, + inner_spec: { + hash: 'SHA256', + child_size: 32, + child_order: [0, 1], + empty_child: '', + max_prefix_length: 1, + min_prefix_length: 1, + }, + }, + ], + trust_level: { + numerator: '1', + denominator: '3', + }, + upgrade_path: ['upgrade', 'upgradedIBCState'], + frozen_height: { + revision_height: '0', + revision_number: '0', + }, + latest_height: { + revision_height: '5744576', + revision_number: '2', + }, + max_clock_drift: '20000000000', + trusting_period: '115200000000000', + unbonding_period: '172800000000000', + allow_update_after_expiry: true, + allow_update_after_misbehaviour: true, + }, + proof_client: + 'CqMJCqAJCiRjbGllbnRzLzA3LXRlbmRlcm1pbnQtNDUvY2xpZW50U3RhdGUSsgEKKy9pYmMubGlnaHRjbGllbnRzLnRlbmRlcm1pbnQudjEuQ2xpZW50U3RhdGUSggEKCmV1cGhvcmlhLTISBAgBEAMaBAiAhAciBAiAxgoqAggUMgA6BwgCEMDP3gJCGQoJCAEYASABKgEAEgwKAgABECEYBCAMMAFCGQoJCAEYASABKgEAEgwKAgABECAYASABMAFKB3VwZ3JhZGVKEHVwZ3JhZGVkSUJDU3RhdGVQAVgBGg4IARgBIAEqBgACovKWBCIuCAESBwIEovKWBCAaISBAlbewzvl2PjgUtyznqYRFOiNhxJp+KAq2YmJaxmAAsiIsCAESKAQGovKWBCDjWwUrygy8PnWb8yoN4Xwomockb9eYporo/dYJRHBDziAiLAgBEigGDqLylgQgjCxek5E7g4E/jZ/z9ayx2Rz8Wlwd5SourZeBm+l7ORkgIiwIARIoCByi8pYEIBzZYxppGpUUOli49o63SLSJv24Pv0bfhHrQL4/b7xR9ICIuCAESBwo0ovKWBCAaISCDdwKGH9QJ5iwCx763LCpf7/QE2iVdkRd52M3o9zDyOSIuCAESBwxGovKWBCAaISDhzb/9iqmZ90/la2ecG8xfrP1g49sNRKF7eHRoZ41hQyIsCAESKA5wovKWBCBbVdBjKBpebgwZFVLIitdMRdO0N0BkfjDLFp9EJMqrNCAiLwgBEggQ0AGi8pYEIBohIJfTNDSJ0SRz/JI1ivIeLCdm3ejk0yl2A7EwNe9AfAYZIi0IARIpEq4DovKWBCBcfXgVQ1yQnurrIPMfFVbCRrCWCaR1pr3rnSgTaTY6OyAiLQgBEikUpgWi8pYEICRprQ6ULikqjibbFFB5VvHS2J4uF9qTpey7lBWn1xcIICItCAESKRbECqLylgQgg5PoFiojmUD7FMFMhE7508cf4ycX/sVegVXPX9wR1WggIi0IARIpGPQPovKWBCDGLCRvs/V82pN1lRJ25iX458N5dREPr1CIW1+M16W7lSAiLQgBEikaqh2i8pYEIC5RzFrOkoX+m2I/PdYQcm6FA0etjFdSiFsieO+IaHMIICItCAESKRzYXqLylgQgaWk0dcwFbfkX/RqivUMEQvlcEQrGFCKCwJM/WmY3KQsgIi4IARIqHtiUAaLylgQg1rVk5tGBWIq3BUT4tXHn3Df+gXamcHSyOUhjI+HAW7YgIi4IARIqINjsAqLylgQg8VA6RoQWxm5zlUIvdcsy+d09UDWkKlBVSqrUwC0JHE4gIi4IARIqIurvBaLylgQgb6+R5G2pSixtdtdy1198x7SgnRXDIun5vonbVS5csE4gIi4IARIqJt7lFqLylgQgx2MIhExidB/g2mH6OP+yBBsk47+n99epUFfLeEpSvtMgIjAIARIJKv6YJqLylgQgGiEgomHHscozScbzZ3cwCX1yC4XkRoNLxV8BDoTaPA+pzzwiLggBEios2I5BovKWBCBkaV0+xAleXNGV26Fkh1NEfhId+jlduH+i6RrmGS0h9yAK/gEK+wEKA2liYxIgAPxOwT2yFJxXI/8Idv6GvWyIBY8Ewdp267h1Z2CPWsEaCQgBGAEgASoBACInCAESAQEaIPlW3n8EGa9BCcwZdOHxNyMKvSuMufUnd17syhqIFw8iIiUIARIhAS/MC0o9ReaxZrve5+zg9Lg3N/ExQn9/u0nPvNHN65TwIicIARIBARogB4kZcZwaxFW9Kfkfr3JLQn6u7VQUoBry2RwzaJDe44QiJQgBEiEBZU3gxhmBMil0OVoP2sWrxgO/NX1H5xVkRZ9/bega3uIiJwgBEgEBGiBsNTQ1P6IG1Lf0Q8cm7kZOttqX22tRPkaFF3Atguv5ZA==', + proof_height: { + revision_height: '4381842', + revision_number: '0', + }, + connection_id: 'connection-72', + proof_consensus: + 'CtUICtIICjJjbGllbnRzLzA3LXRlbmRlcm1pbnQtNDUvY29uc2Vuc3VzU3RhdGVzLzItNTc0NDU3NhKGAQouL2liYy5saWdodGNsaWVudHMudGVuZGVybWludC52MS5Db25zZW5zdXNTdGF0ZRJUCgwIn4D+pQYQ9PKBuAMSIgogtPl77yi9LvzW1gkeO+teMVcM1E6qcv7Xp3okSVvtGAcaIMe9/GKdHdD4dPFEauon+h3QEdH1oZ6jP4DEH2ZWn3ROGg4IARgBIAEqBgACovKWBCIsCAESKAQGovKWBCAHjrwjqZJaxGGLEXaH55ia064+0SLWxo3XLQqnH3PnDSAiLAgBEigGDqLylgQgWMaksHVViO8rzGDk6rbntj3jGE+rHofgIGu7zk8ojukgIi4IARIHCBii8pYEIBohIJgUvjsUDyjaulIls+YyOVYO793IQ59F4DtEoCEKLZTfIiwIARIoCjSi8pYEIEzSRrihNkes+Q5IuNa5gpyhkva9yby1DMC1p8VrYlUUICIuCAESBwxGovKWBCAaISDhzb/9iqmZ90/la2ecG8xfrP1g49sNRKF7eHRoZ41hQyIsCAESKA5wovKWBCBbVdBjKBpebgwZFVLIitdMRdO0N0BkfjDLFp9EJMqrNCAiLwgBEggQ0AGi8pYEIBohIJfTNDSJ0SRz/JI1ivIeLCdm3ejk0yl2A7EwNe9AfAYZIi0IARIpEq4DovKWBCBcfXgVQ1yQnurrIPMfFVbCRrCWCaR1pr3rnSgTaTY6OyAiLQgBEikUpgWi8pYEICRprQ6ULikqjibbFFB5VvHS2J4uF9qTpey7lBWn1xcIICItCAESKRbECqLylgQgg5PoFiojmUD7FMFMhE7508cf4ycX/sVegVXPX9wR1WggIi0IARIpGPQPovKWBCDGLCRvs/V82pN1lRJ25iX458N5dREPr1CIW1+M16W7lSAiLQgBEikaqh2i8pYEIC5RzFrOkoX+m2I/PdYQcm6FA0etjFdSiFsieO+IaHMIICItCAESKRzYXqLylgQgaWk0dcwFbfkX/RqivUMEQvlcEQrGFCKCwJM/WmY3KQsgIi4IARIqHtiUAaLylgQg1rVk5tGBWIq3BUT4tXHn3Df+gXamcHSyOUhjI+HAW7YgIi4IARIqINjsAqLylgQg8VA6RoQWxm5zlUIvdcsy+d09UDWkKlBVSqrUwC0JHE4gIi4IARIqIurvBaLylgQgb6+R5G2pSixtdtdy1198x7SgnRXDIun5vonbVS5csE4gIi4IARIqJt7lFqLylgQgx2MIhExidB/g2mH6OP+yBBsk47+n99epUFfLeEpSvtMgIjAIARIJKv6YJqLylgQgGiEgomHHscozScbzZ3cwCX1yC4XkRoNLxV8BDoTaPA+pzzwiLggBEios2I5BovKWBCBkaV0+xAleXNGV26Fkh1NEfhId+jlduH+i6RrmGS0h9yAK/gEK+wEKA2liYxIgAPxOwT2yFJxXI/8Idv6GvWyIBY8Ewdp267h1Z2CPWsEaCQgBGAEgASoBACInCAESAQEaIPlW3n8EGa9BCcwZdOHxNyMKvSuMufUnd17syhqIFw8iIiUIARIhAS/MC0o9ReaxZrve5+zg9Lg3N/ExQn9/u0nPvNHN65TwIicIARIBARogB4kZcZwaxFW9Kfkfr3JLQn6u7VQUoBry2RwzaJDe44QiJQgBEiEBZU3gxhmBMil0OVoP2sWrxgO/NX1H5xVkRZ9/bega3uIiJwgBEgEBGiBsNTQ1P6IG1Lf0Q8cm7kZOttqX22tRPkaFF3Atguv5ZA==', + consensus_height: { + revision_height: '5744576', + revision_number: '2', + }, + counterparty_connection_id: 'connection-27', + }, + }); + await this.crawlIbcTaoSerivce.handleNewIbcConnection([events], trx); + const newConnection = await IbcConnection.query() + .transacting(trx) + .first() + .throwIfNotFound(); + expect(newConnection.connection_id).toEqual(events.attributes[0].value); + expect(newConnection.counterparty_client_id).toEqual( + events.attributes[2].value + ); + expect(newConnection.counterparty_connection_id).toEqual( + events.attributes[3].value + ); + }); + } + + @Test('Test handleNewIbcChannel') + async testHandleNewIbcChannel() { + await knex.transaction(async (trx) => { + const events = Event.fromJson({ + type: Event.EVENT_TYPE.CHANNEL_OPEN_ACK, + attributes: [ + { + value: + 'wasm.aura1s42mq5xz5et3fhs0cxvf8ds6vmy5u6d27u23ydq28r2hgmednw5s3u7kjf', + key: 'port_id', + event_id: '1', + }, + { + value: 'channel-79', + key: 'channel_id', + event_id: '1', + }, + { + value: + 'wasm.nois1xwde9rzqk5u36fke0r9ddmtwvh43n4fv53c5vc462wz8xlnqjhls6d90xc', + key: 'counterparty_port_id', + event_id: '1', + }, + { + value: 'channel-44', + key: 'counterparty_channel_id', + event_id: '1', + }, + { + value: 'connection-72', + key: 'connection_id', + event_id: '1', + }, + ], + content: { + '@type': '/ibc.core.channel.v1.MsgChannelOpenAck', + signer: 'aura1teguu4gyk002q74rdw8xk6wxz663d3e0da44vv', + port_id: + 'wasm.aura1s42mq5xz5et3fhs0cxvf8ds6vmy5u6d27u23ydq28r2hgmednw5s3u7kjf', + proof_try: + 'CqsJCqgJCmpjaGFubmVsRW5kcy9wb3J0cy93YXNtLm5vaXMxeHdkZTlyenFrNXUzNmZrZTByOWRkbXR3dmg0M240ZnY1M2M1dmM0NjJ3ejh4bG5xamhsczZkOTB4Yy9jaGFubmVscy9jaGFubmVsLTQ0EnAIAhABGlIKRHdhc20uYXVyYTFzNDJtcTV4ejVldDNmaHMwY3h2ZjhkczZ2bXk1dTZkMjd1MjN5ZHEyOHIyaGdtZWRudzVzM3U3a2pmEgpjaGFubmVsLTc5Ig1jb25uZWN0aW9uLTIwKgdub2lzLXY3Gg4IARgBIAEqBgACqNbwAyIsCAESKAIEqNbwAyApVTv4ZHdG03r0dv2FLRgljarFXhPCzLxe5U794Dxg7iAiLAgBEigEBqjW8AMgbTQCakVAjVARrysKAAPM1e1tiTHofZa81TN6VdrZryMgIiwIARIoBgqo1vADIH+PzOA8CN5w3mj/yRWJffIKzoh1l9E7sKZ5XNBpk36XICIuCAESBwgQqNbwAyAaISDJYlIFbAyC1KK1ZZBsZZFdBLoWBTAPcvdp1IOv5E9HLSIuCAESBwowqNbwAyAaISCEaKaAA8yFOHGODccYN+9HuImlZxYWqoTxIN/J8kBiayIsCAESKAxUqNbwAyD3SAJRP1mO4LVWbHvRyD2webWn9TkNbkJZy7jIPzhrMiAiLwgBEggO1AGo1vADIBohICKuXv3N+wd3GBXhJZ27wd4v6vIL8XZUP8OoZE/aOUysIi0IARIpEO4CqNbwAyBgaG7cgNsElUcgrf9Mp2Gmzo0/AsmRHPw5FTwX9Kr0MSAiLQgBEikSqASo1vADIFQDyXDfKzS3OL1ZkYfELpPLhFtz8T5WII7GUvojwrwsICItCAESKRScBqjW8AMgfo0r4iAG6Q26x4Y6zuXHZJc5TgywPhYCp408vX7zDBQgIi0IARIpFtIMqNbwAyDqu2q/0ILlZYXdrphZJmwZtcf8/aHxsqR3dX84m9TTeiAiLQgBEikYgBqo1vADIIPtczfmGBBRreb3CDJB7Wm0ebRH+HR88bBCcXaUupjcICIvCAESCBrAJ6jW8AMgGiEgcpa1SruIq6LeCV4xG7rX2arPRTP/iEbXQ8D5WnWUzwAiLwgBEggc1mGo1vADIBohIDXxpaT6Ugv6U6oOS5S0EDChcoYZW0+my6CUTsXNzvvGIi4IARIqHoqRAajW8AMgBRWQCNEouEeVb1Jgy9Tt+qq891z0wHVNBzVj20RLaGggIi4IARIqItjHAqjW8AMgUvOkgI+dIkIugdSRweG9eleBh/pswvTETxdw6FY4uTwgIjAIARIJJPDCDajW8AMgGiEgyhYnMC8oHxxof8aeyKInF9dJIbxw7e5YkEFV7dhRRWMiLggBEiom8rYSqNbwAyD0JrFmOhwOvx0p9bY869JVWAT2YOnKOMAZ2aBBy0VO/CAiLggBEioosvQZqNbwAyCz9+zU1Wu3buyW7azM+yVxZPfqMxKFHWdwLoOIS5JhYyAiMAgBEgkq9oM/qNbwAyAaISAPNNwvlr7Cghq7eoYfeWqJEKPOMwjXnB66fWb3O1uckQr+AQr7AQoDaWJjEiDfSyEEYUNHoexjzxEoS8ca//0GRWkbX/BhVhlfyDT5zBoJCAEYASABKgEAIicIARIBARog+VbefwQZr0EJzBl04fE3Iwq9K4y59Sd3XuzKGogXDyIiJQgBEiEBL8wLSj1F5rFmu97n7OD0uDc38TFCf3+7Sc+80c3rlPAiJwgBEgEBGiDFznslp7K5oVZJG04oLeQXNU3ws7wFeEH4qB+XS4Q9zyIlCAESIQEtpn507xvLNnr4cRXICIMm+qvT86qeswagi/7S7aB1riInCAESAQEaILASAaS4z/TvmnniGlkXMUOeTYKHWs4RWU6MlCMFBTA3', + channel_id: 'channel-79', + proof_height: { + revision_height: '4068757', + revision_number: '0', + }, + counterparty_version: 'nois-v7', + counterparty_channel_id: 'channel-44', + }, + }); + await this.crawlIbcTaoSerivce.handleNewIbcChannel([events], trx); + const newChannel = await IbcChannel.query() + .transacting(trx) + .first() + .throwIfNotFound(); + expect(newChannel.channel_id).toEqual(events.attributes[1].value); + expect(newChannel.counterparty_channel_id).toEqual( + events.attributes[3].value + ); + expect(newChannel.counterparty_port_id).toEqual( + events.attributes[2].value + ); + expect(newChannel.port_id).toEqual(events.attributes[0].value); + expect(newChannel.state).toEqual(IbcChannel.STATUS.OPEN); + }); + } + + @Test('Test handleCloseIbcChannel') + async testHandleCloseIbcChannel() { + await knex.transaction(async (trx) => { + const events = Event.fromJson({ + type: Event.EVENT_TYPE.CHANNEL_CLOSE_CONFIRM, + attributes: [ + { + value: + 'wasm.aura1s42mq5xz5et3fhs0cxvf8ds6vmy5u6d27u23ydq28r2hgmednw5s3u7kjf', + key: 'port_id', + event_id: '1', + }, + { + value: 'channel-79', + key: 'channel_id', + event_id: '1', + }, + { + value: + 'wasm.nois1xwde9rzqk5u36fke0r9ddmtwvh43n4fv53c5vc462wz8xlnqjhls6d90xc', + key: 'counterparty_port_id', + event_id: '1', + }, + { + value: 'channel-44', + key: 'counterparty_channel_id', + event_id: '1', + }, + { + value: 'connection-72', + key: 'connection_id', + event_id: '1', + }, + ], + content: { + '@type': '/ibc.core.channel.v1.MsgChannelOpenAck', + signer: 'aura1teguu4gyk002q74rdw8xk6wxz663d3e0da44vv', + port_id: + 'wasm.aura1s42mq5xz5et3fhs0cxvf8ds6vmy5u6d27u23ydq28r2hgmednw5s3u7kjf', + proof_try: + 'CqsJCqgJCmpjaGFubmVsRW5kcy9wb3J0cy93YXNtLm5vaXMxeHdkZTlyenFrNXUzNmZrZTByOWRkbXR3dmg0M240ZnY1M2M1dmM0NjJ3ejh4bG5xamhsczZkOTB4Yy9jaGFubmVscy9jaGFubmVsLTQ0EnAIAhABGlIKRHdhc20uYXVyYTFzNDJtcTV4ejVldDNmaHMwY3h2ZjhkczZ2bXk1dTZkMjd1MjN5ZHEyOHIyaGdtZWRudzVzM3U3a2pmEgpjaGFubmVsLTc5Ig1jb25uZWN0aW9uLTIwKgdub2lzLXY3Gg4IARgBIAEqBgACqNbwAyIsCAESKAIEqNbwAyApVTv4ZHdG03r0dv2FLRgljarFXhPCzLxe5U794Dxg7iAiLAgBEigEBqjW8AMgbTQCakVAjVARrysKAAPM1e1tiTHofZa81TN6VdrZryMgIiwIARIoBgqo1vADIH+PzOA8CN5w3mj/yRWJffIKzoh1l9E7sKZ5XNBpk36XICIuCAESBwgQqNbwAyAaISDJYlIFbAyC1KK1ZZBsZZFdBLoWBTAPcvdp1IOv5E9HLSIuCAESBwowqNbwAyAaISCEaKaAA8yFOHGODccYN+9HuImlZxYWqoTxIN/J8kBiayIsCAESKAxUqNbwAyD3SAJRP1mO4LVWbHvRyD2webWn9TkNbkJZy7jIPzhrMiAiLwgBEggO1AGo1vADIBohICKuXv3N+wd3GBXhJZ27wd4v6vIL8XZUP8OoZE/aOUysIi0IARIpEO4CqNbwAyBgaG7cgNsElUcgrf9Mp2Gmzo0/AsmRHPw5FTwX9Kr0MSAiLQgBEikSqASo1vADIFQDyXDfKzS3OL1ZkYfELpPLhFtz8T5WII7GUvojwrwsICItCAESKRScBqjW8AMgfo0r4iAG6Q26x4Y6zuXHZJc5TgywPhYCp408vX7zDBQgIi0IARIpFtIMqNbwAyDqu2q/0ILlZYXdrphZJmwZtcf8/aHxsqR3dX84m9TTeiAiLQgBEikYgBqo1vADIIPtczfmGBBRreb3CDJB7Wm0ebRH+HR88bBCcXaUupjcICIvCAESCBrAJ6jW8AMgGiEgcpa1SruIq6LeCV4xG7rX2arPRTP/iEbXQ8D5WnWUzwAiLwgBEggc1mGo1vADIBohIDXxpaT6Ugv6U6oOS5S0EDChcoYZW0+my6CUTsXNzvvGIi4IARIqHoqRAajW8AMgBRWQCNEouEeVb1Jgy9Tt+qq891z0wHVNBzVj20RLaGggIi4IARIqItjHAqjW8AMgUvOkgI+dIkIugdSRweG9eleBh/pswvTETxdw6FY4uTwgIjAIARIJJPDCDajW8AMgGiEgyhYnMC8oHxxof8aeyKInF9dJIbxw7e5YkEFV7dhRRWMiLggBEiom8rYSqNbwAyD0JrFmOhwOvx0p9bY869JVWAT2YOnKOMAZ2aBBy0VO/CAiLggBEioosvQZqNbwAyCz9+zU1Wu3buyW7azM+yVxZPfqMxKFHWdwLoOIS5JhYyAiMAgBEgkq9oM/qNbwAyAaISAPNNwvlr7Cghq7eoYfeWqJEKPOMwjXnB66fWb3O1uckQr+AQr7AQoDaWJjEiDfSyEEYUNHoexjzxEoS8ca//0GRWkbX/BhVhlfyDT5zBoJCAEYASABKgEAIicIARIBARog+VbefwQZr0EJzBl04fE3Iwq9K4y59Sd3XuzKGogXDyIiJQgBEiEBL8wLSj1F5rFmu97n7OD0uDc38TFCf3+7Sc+80c3rlPAiJwgBEgEBGiDFznslp7K5oVZJG04oLeQXNU3ws7wFeEH4qB+XS4Q9zyIlCAESIQEtpn507xvLNnr4cRXICIMm+qvT86qeswagi/7S7aB1riInCAESAQEaILASAaS4z/TvmnniGlkXMUOeTYKHWs4RWU6MlCMFBTA3', + channel_id: 'channel-79', + proof_height: { + revision_height: '4068757', + revision_number: '0', + }, + counterparty_version: 'nois-v7', + counterparty_channel_id: 'channel-44', + }, + }); + await this.crawlIbcTaoSerivce.handleCloseIbcChannel([events], trx); + const newChannel = await IbcChannel.query() + .transacting(trx) + .first() + .throwIfNotFound(); + expect(newChannel.state).toEqual(IbcChannel.STATUS.CLOSE); + }); + } +} From 7e184832b9f6555bcff08ad560b6c6fc59f046f9 Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Wed, 30 Aug 2023 14:23:37 +0700 Subject: [PATCH 17/33] fix: cw721 activity save owner at each time --- ...024631_cw721_activity_add_current_owner.ts | 13 ++ src/common/constant.ts | 6 + src/models/cw721_tx.ts | 8 +- .../cw721-activity-update-owner.service.ts | 148 ++++++++++++++++++ 4 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 migrations/20230830024631_cw721_activity_add_current_owner.ts create mode 100644 src/services/cw721/cw721-activity-update-owner.service.ts diff --git a/migrations/20230830024631_cw721_activity_add_current_owner.ts b/migrations/20230830024631_cw721_activity_add_current_owner.ts new file mode 100644 index 000000000..3303354c6 --- /dev/null +++ b/migrations/20230830024631_cw721_activity_add_current_owner.ts @@ -0,0 +1,13 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable('cw721_activity', (table) => { + table.string('owner'); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable('cw721_activity', (table) => { + table.dropColumn('owner'); + }); +} diff --git a/src/common/constant.ts b/src/common/constant.ts index 465d9b5ad..35840e5ee 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -75,6 +75,8 @@ export const BULL_JOB_NAME = { CRAWL_GENESIS_IBC_TAO: 'crawl:genesis-ibc-tao', REINDEX_CW20_CONTRACT: 'reindex:cw20-contract', REINDEX_CW20_HISTORY: 'reindex:cw20-history', + UPDATE_CW721_ACTIVITY_OWNER: 'update:cw721-activity-owner', + UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN: 'update:cw721-activity-owner-by-token', }; export const SERVICE = { @@ -264,6 +266,10 @@ export const SERVICE = { path: 'v1.Cw20ReindexingService.reindexing', }, }, + Cw721ActivityUpdateOwnerService: { + key: 'Cw721ActivityUpdateOwnerService', + name: 'v1.Cw721ActivityUpdateOwnerService', + }, }, }; diff --git a/src/models/cw721_tx.ts b/src/models/cw721_tx.ts index 241949888..c1d0f8a35 100644 --- a/src/models/cw721_tx.ts +++ b/src/models/cw721_tx.ts @@ -21,18 +21,20 @@ export default class CW721Activity extends BaseModel { cw721_contract_id!: number; - cw721_token_id?: number; + cw721_token_id!: number; created_at?: Date; updated_at?: Date; - from?: string; + from!: string; - to?: string; + to!: string; height!: number; + owner!: string; + smart_contract_event_id!: number; static get tableName() { diff --git a/src/services/cw721/cw721-activity-update-owner.service.ts b/src/services/cw721/cw721-activity-update-owner.service.ts new file mode 100644 index 000000000..24a2711f3 --- /dev/null +++ b/src/services/cw721/cw721-activity-update-owner.service.ts @@ -0,0 +1,148 @@ +import { Service } from '@ourparentcenter/moleculer-decorators-extended'; +import _ from 'lodash'; +import { ServiceBroker } from 'moleculer'; +import config from '../../../config.json' assert { type: 'json' }; +import BullableService, { QueueHandler } from '../../base/bullable.service'; +import { Config } from '../../common'; +import { BULL_JOB_NAME, SERVICE } from '../../common/constant'; +import CW721Activity from '../../models/cw721_tx'; +import { CW721_ACTION } from './cw721.service'; + +const { NODE_ENV } = Config; + +@Service({ + name: SERVICE.V1.Cw721ActivityUpdateOwnerService.key, + version: 1, +}) +export default class Cw721ActivityUpdateOwnerService extends BullableService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, + jobName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, + }) + async updateCw721ActOwnerByToken(_payload: { + cw721ContractId: number; + cw721TokenId: number; + }): Promise { + const { cw721ContractId, cw721TokenId } = _payload; + const activities = await CW721Activity.query() + .whereIn('action', [ + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + CW721_ACTION.BURN, + ]) + .whereNull('owner') + .andWhere('cw721_token_id', cw721TokenId) + .andWhere('cw721_contract_id', cw721ContractId) + .orderBy('id') + .limit(100); + const lastOwnerActivity = await CW721Activity.query() + .whereIn('action', [CW721_ACTION.MINT, CW721_ACTION.TRANSFER]) + .andWhere('cw721_token_id', cw721TokenId) + .andWhere('cw721_contract_id', cw721ContractId) + .whereNotNull('owner') + .orderBy('height', 'DESC') + .first() + .throwIfNotFound(); + const sortedActivities = _.sortBy( + [...activities, lastOwnerActivity], + (e) => e.height + ); + for (let index = 1; index < sortedActivities.length; index += 1) { + sortedActivities[index].owner = sortedActivities[index - 1].to; + } + await CW721Activity.query() + .insert(sortedActivities) + .onConflict('id') + .merge(); + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, + jobName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, + }) + async jobHandler(): Promise { + const unprocessedActivities = await CW721Activity.query() + .whereIn('action', [ + CW721_ACTION.MINT, + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + CW721_ACTION.BURN, + ]) + .whereNull('owner') + .orderBy('id') + .limit(100); + await this.updateOwnerForMintActs( + unprocessedActivities.filter((e) => e.action === CW721_ACTION.MINT) + ); + const tokens = unprocessedActivities.reduce( + (acc: { cw721ContractId: number; cw721TokenId: number }[], curr) => { + if ( + acc.find( + (e) => + e.cw721ContractId === curr.cw721_contract_id && + e.cw721TokenId === curr.cw721_token_id + ) === undefined + ) { + acc.push({ + cw721ContractId: curr.cw721_contract_id, + cw721TokenId: curr.cw721_token_id, + }); + } + return acc; + }, + [] + ); + await Promise.all( + tokens.map(async (token) => + this.createJob( + BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, + BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, + token, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + } + ) + ) + ); + } + + async updateOwnerForMintActs(activities: CW721Activity[]) { + const patchQueries = activities.map((act) => + CW721Activity.query() + .patch({ + owner: act.to, + }) + .where('id', act.id) + ); + if (patchQueries.length > 0) { + await Promise.all(patchQueries); + } + } + + async _start(): Promise { + if (NODE_ENV !== 'test') { + await this.createJob( + BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, + BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, + {}, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + repeat: { + every: config.cw721.millisecondRepeatJob, + }, + } + ); + } + return super._start(); + } +} From 35d094d59dded65a3c3c457219f3299e145d12fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Th=C3=A0nh=20Phong?= <49814372+phamphong9981@users.noreply.github.com> Date: Mon, 28 Aug 2023 14:01:36 +0700 Subject: [PATCH 18/33] fix: cw721 activity missing from/to ( main ) (#294) * fix: cw721 activity missing from/to * fix: fill from/to cw721 activity * fix: fill from/to cw721 activity * fix: fill from/to cw721 activity * fix: fill from/to cw721 activity * refactor: code * fix: code * refactor: code --------- Co-authored-by: Vu Ngoc Quang --- src/models/event_attribute.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/event_attribute.ts b/src/models/event_attribute.ts index f67aad3b2..caed40064 100644 --- a/src/models/event_attribute.ts +++ b/src/models/event_attribute.ts @@ -96,6 +96,7 @@ export class EventAttribute extends BaseModel { GRANTER: 'granter', GRANTEE: 'grantee', FROM: 'from', + MINTER: 'minter', FEE: 'fee', FEE_PAYER: 'fee_payer', CLIENT_ID: 'client_id', From 3c7c2872e6873d0a202b8842141b507638e19928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=E1=BA=A1m=20Th=C3=A0nh=20Phong?= <49814372+phamphong9981@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:02:08 +0700 Subject: [PATCH 19/33] feat: crawl ibc tao ( main ) (#278) * feat: crawl ibc tao * feat: crawl genesis ibc tao * fix: code * fix: code --- src/common/constant.ts | 4 ++++ src/models/event_attribute.ts | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/common/constant.ts b/src/common/constant.ts index efd0c19be..bfd7b4f4d 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -233,6 +233,10 @@ export const SERVICE = { path: 'v1.ReDecodeTx', }, }, + CrawlIBCTaoService: { + key: 'CrawlIBCTaoService', + name: 'v1.CrawlIBCTaoService', + }, DailyStatisticsService: { key: 'DailyStatisticsService', name: 'v1.DailyStatisticsService', diff --git a/src/models/event_attribute.ts b/src/models/event_attribute.ts index caed40064..b6e1d8106 100644 --- a/src/models/event_attribute.ts +++ b/src/models/event_attribute.ts @@ -96,6 +96,15 @@ export class EventAttribute extends BaseModel { GRANTER: 'granter', GRANTEE: 'grantee', FROM: 'from', + CLIENT_ID: 'client_id', + CLIENT_TYPE: 'client_type', + CONNECTION_ID: 'connection_id', + COUNTERPARTY_CLIENT_ID: 'counterparty_client_id', + COUNTERPARTY_CONNECTION_ID: 'counterparty_connection_id', + CHANNEL_ID: 'channel_id', + PORT_ID: 'port_id', + COUNTERPARTY_PORT_ID: 'counterparty_port_id', + COUNTERPARTY_CHANNEL_ID: 'counterparty_channel_id', MINTER: 'minter', FEE: 'fee', FEE_PAYER: 'fee_payer', From 85c4dd98b3fdccfefc3338f88726862e21b47070 Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Wed, 30 Aug 2023 14:23:37 +0700 Subject: [PATCH 20/33] fix: cw721 activity save owner at each time --- ...024631_cw721_activity_add_current_owner.ts | 13 ++ src/common/constant.ts | 6 + src/models/cw721_tx.ts | 8 +- .../cw721-activity-update-owner.service.ts | 148 ++++++++++++++++++ 4 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 migrations/20230830024631_cw721_activity_add_current_owner.ts create mode 100644 src/services/cw721/cw721-activity-update-owner.service.ts diff --git a/migrations/20230830024631_cw721_activity_add_current_owner.ts b/migrations/20230830024631_cw721_activity_add_current_owner.ts new file mode 100644 index 000000000..3303354c6 --- /dev/null +++ b/migrations/20230830024631_cw721_activity_add_current_owner.ts @@ -0,0 +1,13 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable('cw721_activity', (table) => { + table.string('owner'); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable('cw721_activity', (table) => { + table.dropColumn('owner'); + }); +} diff --git a/src/common/constant.ts b/src/common/constant.ts index bfd7b4f4d..15ccc979e 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -76,6 +76,8 @@ export const BULL_JOB_NAME = { REINDEX_CW20_CONTRACT: 'reindex:cw20-contract', REINDEX_CW20_HISTORY: 'reindex:cw20-history', CRAWL_IBC_APP: 'crawl:ibc-app', + UPDATE_CW721_ACTIVITY_OWNER: 'update:cw721-activity-owner', + UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN: 'update:cw721-activity-owner-by-token', }; export const SERVICE = { @@ -273,6 +275,10 @@ export const SERVICE = { key: 'CrawlIBCAppService', name: 'v1.CrawlIBCAppService', }, + Cw721ActivityUpdateOwnerService: { + key: 'Cw721ActivityUpdateOwnerService', + name: 'v1.Cw721ActivityUpdateOwnerService', + }, }, }; diff --git a/src/models/cw721_tx.ts b/src/models/cw721_tx.ts index 241949888..c1d0f8a35 100644 --- a/src/models/cw721_tx.ts +++ b/src/models/cw721_tx.ts @@ -21,18 +21,20 @@ export default class CW721Activity extends BaseModel { cw721_contract_id!: number; - cw721_token_id?: number; + cw721_token_id!: number; created_at?: Date; updated_at?: Date; - from?: string; + from!: string; - to?: string; + to!: string; height!: number; + owner!: string; + smart_contract_event_id!: number; static get tableName() { diff --git a/src/services/cw721/cw721-activity-update-owner.service.ts b/src/services/cw721/cw721-activity-update-owner.service.ts new file mode 100644 index 000000000..24a2711f3 --- /dev/null +++ b/src/services/cw721/cw721-activity-update-owner.service.ts @@ -0,0 +1,148 @@ +import { Service } from '@ourparentcenter/moleculer-decorators-extended'; +import _ from 'lodash'; +import { ServiceBroker } from 'moleculer'; +import config from '../../../config.json' assert { type: 'json' }; +import BullableService, { QueueHandler } from '../../base/bullable.service'; +import { Config } from '../../common'; +import { BULL_JOB_NAME, SERVICE } from '../../common/constant'; +import CW721Activity from '../../models/cw721_tx'; +import { CW721_ACTION } from './cw721.service'; + +const { NODE_ENV } = Config; + +@Service({ + name: SERVICE.V1.Cw721ActivityUpdateOwnerService.key, + version: 1, +}) +export default class Cw721ActivityUpdateOwnerService extends BullableService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, + jobName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, + }) + async updateCw721ActOwnerByToken(_payload: { + cw721ContractId: number; + cw721TokenId: number; + }): Promise { + const { cw721ContractId, cw721TokenId } = _payload; + const activities = await CW721Activity.query() + .whereIn('action', [ + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + CW721_ACTION.BURN, + ]) + .whereNull('owner') + .andWhere('cw721_token_id', cw721TokenId) + .andWhere('cw721_contract_id', cw721ContractId) + .orderBy('id') + .limit(100); + const lastOwnerActivity = await CW721Activity.query() + .whereIn('action', [CW721_ACTION.MINT, CW721_ACTION.TRANSFER]) + .andWhere('cw721_token_id', cw721TokenId) + .andWhere('cw721_contract_id', cw721ContractId) + .whereNotNull('owner') + .orderBy('height', 'DESC') + .first() + .throwIfNotFound(); + const sortedActivities = _.sortBy( + [...activities, lastOwnerActivity], + (e) => e.height + ); + for (let index = 1; index < sortedActivities.length; index += 1) { + sortedActivities[index].owner = sortedActivities[index - 1].to; + } + await CW721Activity.query() + .insert(sortedActivities) + .onConflict('id') + .merge(); + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, + jobName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, + }) + async jobHandler(): Promise { + const unprocessedActivities = await CW721Activity.query() + .whereIn('action', [ + CW721_ACTION.MINT, + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + CW721_ACTION.BURN, + ]) + .whereNull('owner') + .orderBy('id') + .limit(100); + await this.updateOwnerForMintActs( + unprocessedActivities.filter((e) => e.action === CW721_ACTION.MINT) + ); + const tokens = unprocessedActivities.reduce( + (acc: { cw721ContractId: number; cw721TokenId: number }[], curr) => { + if ( + acc.find( + (e) => + e.cw721ContractId === curr.cw721_contract_id && + e.cw721TokenId === curr.cw721_token_id + ) === undefined + ) { + acc.push({ + cw721ContractId: curr.cw721_contract_id, + cw721TokenId: curr.cw721_token_id, + }); + } + return acc; + }, + [] + ); + await Promise.all( + tokens.map(async (token) => + this.createJob( + BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, + BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, + token, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + } + ) + ) + ); + } + + async updateOwnerForMintActs(activities: CW721Activity[]) { + const patchQueries = activities.map((act) => + CW721Activity.query() + .patch({ + owner: act.to, + }) + .where('id', act.id) + ); + if (patchQueries.length > 0) { + await Promise.all(patchQueries); + } + } + + async _start(): Promise { + if (NODE_ENV !== 'test') { + await this.createJob( + BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, + BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, + {}, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + repeat: { + every: config.cw721.millisecondRepeatJob, + }, + } + ); + } + return super._start(); + } +} From 48731275d46342d81eaa248b10d4617b21293169 Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Wed, 30 Aug 2023 14:39:05 +0700 Subject: [PATCH 21/33] fix: resolve conflict --- src/models/event_attribute.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/models/event_attribute.ts b/src/models/event_attribute.ts index b6e1d8106..6f6a28f5f 100644 --- a/src/models/event_attribute.ts +++ b/src/models/event_attribute.ts @@ -108,16 +108,6 @@ export class EventAttribute extends BaseModel { MINTER: 'minter', FEE: 'fee', FEE_PAYER: 'fee_payer', - CLIENT_ID: 'client_id', - CLIENT_TYPE: 'client_type', - CONNECTION_ID: 'connection_id', - COUNTERPARTY_CLIENT_ID: 'counterparty_client_id', - COUNTERPARTY_CONNECTION_ID: 'counterparty_connection_id', - CHANNEL_ID: 'channel_id', - PORT_ID: 'port_id', - COUNTERPARTY_PORT_ID: 'counterparty_port_id', - COUNTERPARTY_CHANNEL_ID: 'counterparty_channel_id', - MINTER: 'minter', DATA_HEX: 'packet_data_hex', SEQUENCE: 'packet_sequence', SRC_PORT: 'packet_src_port', From 8baa6b3b73821df6f1f6de2ec985f211226b5b0e Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Wed, 30 Aug 2023 14:41:26 +0700 Subject: [PATCH 22/33] fix: resolve conflict --- src/common/constant.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/common/constant.ts b/src/common/constant.ts index 15ccc979e..f1ced675e 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -259,10 +259,6 @@ export const SERVICE = { key: 'DailyStatsJobsService', name: 'v1.DailyStatsJobsService', }, - CrawlIBCTaoService: { - key: 'CrawlIBCTaoService', - name: 'v1.CrawlIBCTaoService', - }, Cw20ReindexingService: { key: 'Cw20ReindexingService', name: 'v1.Cw20ReindexingService', From ed03ca38ea26d797e5da3c896d80cac4b79661f4 Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Thu, 31 Aug 2023 15:14:16 +0700 Subject: [PATCH 23/33] feat: config json --- ci/config.json.ci | 3 ++- config.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ci/config.json.ci b/ci/config.json.ci index afc6cd564..06cb1e544 100644 --- a/ci/config.json.ci +++ b/ci/config.json.ci @@ -20,7 +20,8 @@ "timeRefreshCw721Stats": "1 * * * *", "reindexing": { "limitRecordGet": 500 - } + }, + "limitTokensActivityUpdate":10 }, "crawlBlock": { "millisecondCrawl": 5000, diff --git a/config.json b/config.json index 686cbb866..45239bf85 100644 --- a/config.json +++ b/config.json @@ -20,7 +20,8 @@ "timeRefreshCw721Stats": "1 * * * *", "reindexing": { "limitRecordGet": 500 - } + }, + "limitTokensActivityUpdate":10 }, "crawlBlock": { "millisecondCrawl": 5000, From b26cf9ccba41964e10ee29fd06eeb0128acdfdc5 Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Thu, 31 Aug 2023 15:17:08 +0700 Subject: [PATCH 24/33] fix: update owner for each cw721 activity --- .../cw721-activity-update-owner.service.ts | 73 +++++++------------ 1 file changed, 25 insertions(+), 48 deletions(-) diff --git a/src/services/cw721/cw721-activity-update-owner.service.ts b/src/services/cw721/cw721-activity-update-owner.service.ts index 24a2711f3..b73989122 100644 --- a/src/services/cw721/cw721-activity-update-owner.service.ts +++ b/src/services/cw721/cw721-activity-update-owner.service.ts @@ -30,6 +30,7 @@ export default class Cw721ActivityUpdateOwnerService extends BullableService { const { cw721ContractId, cw721TokenId } = _payload; const activities = await CW721Activity.query() .whereIn('action', [ + CW721_ACTION.MINT, CW721_ACTION.TRANSFER, CW721_ACTION.SEND_NFT, CW721_ACTION.BURN, @@ -37,20 +38,26 @@ export default class Cw721ActivityUpdateOwnerService extends BullableService { .whereNull('owner') .andWhere('cw721_token_id', cw721TokenId) .andWhere('cw721_contract_id', cw721ContractId) - .orderBy('id') - .limit(100); + .orderBy('height'); const lastOwnerActivity = await CW721Activity.query() - .whereIn('action', [CW721_ACTION.MINT, CW721_ACTION.TRANSFER]) + .whereIn('action', [ + CW721_ACTION.MINT, + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + CW721_ACTION.BURN, + ]) .andWhere('cw721_token_id', cw721TokenId) .andWhere('cw721_contract_id', cw721ContractId) .whereNotNull('owner') .orderBy('height', 'DESC') - .first() - .throwIfNotFound(); - const sortedActivities = _.sortBy( - [...activities, lastOwnerActivity], - (e) => e.height - ); + .first(); + if (!lastOwnerActivity) { + activities[0].owner = activities[0].to; + } + const allActivities = lastOwnerActivity + ? [...activities, lastOwnerActivity] + : activities; + const sortedActivities = _.sortBy(allActivities, (e) => e.height); for (let index = 1; index < sortedActivities.length; index += 1) { sortedActivities[index].owner = sortedActivities[index - 1].to; } @@ -65,67 +72,37 @@ export default class Cw721ActivityUpdateOwnerService extends BullableService { jobName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, }) async jobHandler(): Promise { - const unprocessedActivities = await CW721Activity.query() + const tokens = await CW721Activity.query() .whereIn('action', [ - CW721_ACTION.MINT, CW721_ACTION.TRANSFER, CW721_ACTION.SEND_NFT, CW721_ACTION.BURN, ]) .whereNull('owner') - .orderBy('id') - .limit(100); - await this.updateOwnerForMintActs( - unprocessedActivities.filter((e) => e.action === CW721_ACTION.MINT) - ); - const tokens = unprocessedActivities.reduce( - (acc: { cw721ContractId: number; cw721TokenId: number }[], curr) => { - if ( - acc.find( - (e) => - e.cw721ContractId === curr.cw721_contract_id && - e.cw721TokenId === curr.cw721_token_id - ) === undefined - ) { - acc.push({ - cw721ContractId: curr.cw721_contract_id, - cw721TokenId: curr.cw721_token_id, - }); - } - return acc; - }, - [] - ); + .groupBy('cw721_contract_id', 'cw721_token_id') + .select('cw721_contract_id', 'cw721_token_id') + .limit(config.cw721.limitTokensActivityUpdate); await Promise.all( tokens.map(async (token) => this.createJob( BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, - token, + { + cw721ContractId: token.cw721_contract_id, + cw721TokenId: token.cw721_token_id, + }, { removeOnComplete: true, removeOnFail: { count: 3, }, + jobId: `${token.cw721_contract_id }_${ token.cw721_token_id}`, } ) ) ); } - async updateOwnerForMintActs(activities: CW721Activity[]) { - const patchQueries = activities.map((act) => - CW721Activity.query() - .patch({ - owner: act.to, - }) - .where('id', act.id) - ); - if (patchQueries.length > 0) { - await Promise.all(patchQueries); - } - } - async _start(): Promise { if (NODE_ENV !== 'test') { await this.createJob( From c93f9957c99445d7217720e7d6e706cd0124ca0a Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Thu, 31 Aug 2023 15:20:04 +0700 Subject: [PATCH 25/33] fix: update owner for each cw721 activity --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 45239bf85..5363ba5a1 100644 --- a/config.json +++ b/config.json @@ -21,7 +21,7 @@ "reindexing": { "limitRecordGet": 500 }, - "limitTokensActivityUpdate":10 + "limitTokensActivityUpdate": 10 }, "crawlBlock": { "millisecondCrawl": 5000, From 0d2c5efa626b1cc935b83335f3618613ef28214e Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Tue, 5 Sep 2023 10:03:49 +0700 Subject: [PATCH 26/33] test: test --- .../cw721/cw721-activity-update-owner.spec.ts | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 test/unit/services/cw721/cw721-activity-update-owner.spec.ts diff --git a/test/unit/services/cw721/cw721-activity-update-owner.spec.ts b/test/unit/services/cw721/cw721-activity-update-owner.spec.ts new file mode 100644 index 000000000..9132a044e --- /dev/null +++ b/test/unit/services/cw721/cw721-activity-update-owner.spec.ts @@ -0,0 +1,235 @@ +import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; +import { ServiceBroker } from 'moleculer'; +import knex from '../../../../src/common/utils/db_connection'; +import CW721Contract from '../../../../src/models/cw721_contract'; +import { Code } from '../../../../src/models'; +import Cw721ActivityUpdateOwnerService from '../../../../src/services/cw721/cw721-activity-update-owner.service'; +import CW721Activity from '../../../../src/models/cw721_tx'; +import { CW721_ACTION } from '../../../../src/services/cw721/cw721.service'; + +@Describe('Test Cw721ActivityUpdateOwnerService') +export default class Cw721ActivityUpdateOwnerTest { + codeId = { + ...Code.fromJson({ + creator: 'code_id_creator', + code_id: 100, + data_hash: 'code_id_data_hash', + instantiate_permission: { permission: '', address: '', addresses: [] }, + store_hash: 'code_id_store_hash', + store_height: 1000, + type: 'CW721', + }), + contracts: [ + { + name: 'Base Contract 2', + address: 'mock_contract_address', + creator: 'phamphong_creator', + code_id: 100, + instantiate_hash: 'abc', + instantiate_height: 300000, + }, + ], + }; + + cw721Contract = { + ...CW721Contract.fromJson({ + contract_id: 1, + symbol: '', + name: 'jghdfgkjdfgjk', + track: true, + }), + tokens: [ + { + token_id: 'bhlvjdfkljkljg', + owner: 'ghgfhdgdsfgsdgfsd', + last_updated_height: 122120, + burned: false, + cw721_contract_id: 1, + }, + { + token_id: 'zzzvcxxb', + owner: 'xctgxgvxcgxx', + last_updated_height: 155, + burned: false, + cw721_contract_id: 1, + }, + { + token_id: 'vbmnnmn', + owner: 'ghgfhdgdsfgsdgfsd', + last_updated_height: 42424, + burned: false, + cw721_contract_id: 1, + }, + { + token_id: 'vcgasdfsdg', + owner: 'ghgfhdgdsfgsdgfsd', + last_updated_height: 121012, + burned: false, + cw721_contract_id: 1, + }, + { + token_id: 'cbvcxbvcxgbdv', + owner: 'ghgfhdgdsfgsdgfsd', + last_updated_height: 651651, + burned: false, + cw721_contract_id: 1, + }, + { + token_id: 'dghdfghdfgfdgdf', + owner: 'ghgfhdgdsfgsdgfsd', + last_updated_height: 2314, + burned: false, + cw721_contract_id: 1, + }, + ], + }; + + broker = new ServiceBroker({ logger: false }); + + cw721ActivityUpdateOwnerService = this.broker.createService( + Cw721ActivityUpdateOwnerService + ) as Cw721ActivityUpdateOwnerService; + + @BeforeAll() + async initSuite() { + this.cw721ActivityUpdateOwnerService.getQueueManager().stopAll(); + await this.broker.start(); + await knex.raw( + 'TRUNCATE TABLE code, cw721_contract, cw721_activity, block_checkpoint RESTART IDENTITY CASCADE' + ); + await Code.query().insertGraph(this.codeId); + await CW721Contract.query().insertGraph(this.cw721Contract); + } + + @AfterAll() + async tearDown() { + await this.broker.stop(); + } + + @Test('Test updateCw721ActOwnerByToken') + public async testUpdateCw721ActOwnerByToken() { + const activities = [ + { + action: 'mint', + sender: 'fsgsfgfs', + tx_hash: 'fgdfgdf', + cw721_contract_id: 1, + cw721_token_id: 1, + from: 'fgsfdghgj', + to: 'drsdrfsdfsd', + height: 100, + owner: null, + smart_contract_event_id: null, + }, + { + action: 'transfer_nft', + sender: 'hhhhhh', + tx_hash: 'vzxvxzc', + cw721_contract_id: 1, + cw721_token_id: 1, + from: 'fgfgfdg', + to: 'mmmmmmmm', + height: 200, + owner: null, + smart_contract_event_id: null, + }, + { + action: 'transfer_nft', + sender: 'xdfzf', + tx_hash: ' vgbhdghg', + cw721_contract_id: 1, + cw721_token_id: 1, + from: 'ertrtwerrewqr', + to: 'dsdasdasda', + height: 300, + owner: null, + smart_contract_event_id: null, + }, + { + action: 'send_nft', + sender: 'x cbcvbhcvdfzf', + tx_hash: ' fgdfytyertr', + cw721_contract_id: 1, + cw721_token_id: 1, + from: 'ertwrwrw', + to: 'xgdfbdndg', + height: 400, + owner: null, + smart_contract_event_id: null, + }, + { + action: 'burn', + sender: ' asaadsd', + tx_hash: ' xzcvxzvz', + cw721_contract_id: 1, + cw721_token_id: 1, + from: 'fdfasda', + to: 'tytytryrtyrt', + height: 500, + owner: null, + smart_contract_event_id: null, + }, + { + action: 'transfer_nft', + sender: 'ghfdyhtury', + tx_hash: 'kkkkkk', + cw721_contract_id: 1, + cw721_token_id: 2, + from: 'gfhvcgbdstg', + to: 'xgrtrfgh', + height: 200, + owner: null, + smart_contract_event_id: null, + }, + { + action: 'approve', + sender: ' asaadsd', + tx_hash: ' xzcvxzvz', + cw721_contract_id: 1, + cw721_token_id: 1, + from: 'fdfasda', + to: 'tytytryrtyrt', + height: 250, + owner: null, + smart_contract_event_id: null, + }, + ]; + await CW721Activity.query().insert( + activities.map((e) => CW721Activity.fromJson(e)) + ); + await this.cw721ActivityUpdateOwnerService.updateCw721ActOwnerByToken({ + cw721ContractId: 1, + cw721TokenId: 1, + }); + const updatedActivities = await CW721Activity.query() + .where('cw721_contract_id', 1) + .andWhere('cw721_token_id', 1) + .whereIn('action', [ + CW721_ACTION.MINT, + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + CW721_ACTION.BURN, + ]) + .orderBy('height'); + expect(updatedActivities[0].owner).toEqual(activities[0].to); + expect(updatedActivities[1].owner).toEqual(activities[0].to); + expect(updatedActivities[2].owner).toEqual(activities[1].to); + expect(updatedActivities[3].owner).toEqual(activities[2].to); + expect(updatedActivities[4].owner).toEqual(activities[3].to); + await this.cw721ActivityUpdateOwnerService.updateCw721ActOwnerByToken({ + cw721ContractId: 1, + cw721TokenId: 2, + }); + const updatedActivities2 = await CW721Activity.query() + .where('cw721_contract_id', 1) + .andWhere('cw721_token_id', 2) + .whereIn('action', [ + CW721_ACTION.MINT, + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + CW721_ACTION.BURN, + ]) + .orderBy('height'); + expect(updatedActivities2[0].owner).toEqual(activities[5].to); + } +} From cb66cf6b075c41ea2633ce474856425d4c66f62d Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Thu, 7 Sep 2023 10:12:27 +0700 Subject: [PATCH 27/33] refactor: code --- ci/config.json.ci | 3 +- config.json | 3 +- ...024631_cw721_activity_add_current_owner.ts | 37 ++- src/common/constant.ts | 6 - src/models/cw721_tx.ts | 2 +- .../cw721-activity-update-owner.service.ts | 125 ---------- src/services/cw721/cw721.service.ts | 115 ++++++--- .../cw721/cw721-activity-update-owner.spec.ts | 235 ------------------ test/unit/services/cw721/cw721.spec.ts | 91 ++++++- 9 files changed, 202 insertions(+), 415 deletions(-) delete mode 100644 src/services/cw721/cw721-activity-update-owner.service.ts delete mode 100644 test/unit/services/cw721/cw721-activity-update-owner.spec.ts diff --git a/ci/config.json.ci b/ci/config.json.ci index 06cb1e544..afc6cd564 100644 --- a/ci/config.json.ci +++ b/ci/config.json.ci @@ -20,8 +20,7 @@ "timeRefreshCw721Stats": "1 * * * *", "reindexing": { "limitRecordGet": 500 - }, - "limitTokensActivityUpdate":10 + } }, "crawlBlock": { "millisecondCrawl": 5000, diff --git a/config.json b/config.json index 5363ba5a1..686cbb866 100644 --- a/config.json +++ b/config.json @@ -20,8 +20,7 @@ "timeRefreshCw721Stats": "1 * * * *", "reindexing": { "limitRecordGet": 500 - }, - "limitTokensActivityUpdate": 10 + } }, "crawlBlock": { "millisecondCrawl": 5000, diff --git a/migrations/20230830024631_cw721_activity_add_current_owner.ts b/migrations/20230830024631_cw721_activity_add_current_owner.ts index 3303354c6..701ddd787 100644 --- a/migrations/20230830024631_cw721_activity_add_current_owner.ts +++ b/migrations/20230830024631_cw721_activity_add_current_owner.ts @@ -1,9 +1,44 @@ import { Knex } from 'knex'; - +import CW721Activity from '../src/models/cw721_tx'; +import _ from 'lodash'; +import { CW721_ACTION } from '../src/services/cw721/cw721.service'; export async function up(knex: Knex): Promise { await knex.schema.alterTable('cw721_activity', (table) => { table.string('owner'); }); + await knex.transaction(async (trx) => { + const activities = await CW721Activity.query() + .joinRelated('event') + .orderBy('event.block_height', 'asc') + .transacting(trx); + const done: CW721Activity[] = []; + activities.forEach((activity) => { + const latestActivity = _.findLast(done, function (e) { + return ( + [ + CW721_ACTION.MINT, + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + ].includes(e.action) && + e.cw721_contract_id === activity.cw721_contract_id && + e.cw721_token_id === activity.cw721_token_id + ); + }); + if (latestActivity) { + activity.owner = latestActivity.to; + } else { + activity.owner = activity.to; + } + done.push(activity); + }); + if (activities.length > 0) { + await CW721Activity.query() + .insert(activities) + .onConflict(['id']) + .merge() + .transacting(trx); + } + }); } export async function down(knex: Knex): Promise { diff --git a/src/common/constant.ts b/src/common/constant.ts index 35840e5ee..465d9b5ad 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -75,8 +75,6 @@ export const BULL_JOB_NAME = { CRAWL_GENESIS_IBC_TAO: 'crawl:genesis-ibc-tao', REINDEX_CW20_CONTRACT: 'reindex:cw20-contract', REINDEX_CW20_HISTORY: 'reindex:cw20-history', - UPDATE_CW721_ACTIVITY_OWNER: 'update:cw721-activity-owner', - UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN: 'update:cw721-activity-owner-by-token', }; export const SERVICE = { @@ -266,10 +264,6 @@ export const SERVICE = { path: 'v1.Cw20ReindexingService.reindexing', }, }, - Cw721ActivityUpdateOwnerService: { - key: 'Cw721ActivityUpdateOwnerService', - name: 'v1.Cw721ActivityUpdateOwnerService', - }, }, }; diff --git a/src/models/cw721_tx.ts b/src/models/cw721_tx.ts index c1d0f8a35..da20a5325 100644 --- a/src/models/cw721_tx.ts +++ b/src/models/cw721_tx.ts @@ -13,7 +13,7 @@ export default class CW721Activity extends BaseModel { id!: number; - action?: string; + action!: string; sender?: string; diff --git a/src/services/cw721/cw721-activity-update-owner.service.ts b/src/services/cw721/cw721-activity-update-owner.service.ts deleted file mode 100644 index b73989122..000000000 --- a/src/services/cw721/cw721-activity-update-owner.service.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Service } from '@ourparentcenter/moleculer-decorators-extended'; -import _ from 'lodash'; -import { ServiceBroker } from 'moleculer'; -import config from '../../../config.json' assert { type: 'json' }; -import BullableService, { QueueHandler } from '../../base/bullable.service'; -import { Config } from '../../common'; -import { BULL_JOB_NAME, SERVICE } from '../../common/constant'; -import CW721Activity from '../../models/cw721_tx'; -import { CW721_ACTION } from './cw721.service'; - -const { NODE_ENV } = Config; - -@Service({ - name: SERVICE.V1.Cw721ActivityUpdateOwnerService.key, - version: 1, -}) -export default class Cw721ActivityUpdateOwnerService extends BullableService { - public constructor(public broker: ServiceBroker) { - super(broker); - } - - @QueueHandler({ - queueName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, - jobName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, - }) - async updateCw721ActOwnerByToken(_payload: { - cw721ContractId: number; - cw721TokenId: number; - }): Promise { - const { cw721ContractId, cw721TokenId } = _payload; - const activities = await CW721Activity.query() - .whereIn('action', [ - CW721_ACTION.MINT, - CW721_ACTION.TRANSFER, - CW721_ACTION.SEND_NFT, - CW721_ACTION.BURN, - ]) - .whereNull('owner') - .andWhere('cw721_token_id', cw721TokenId) - .andWhere('cw721_contract_id', cw721ContractId) - .orderBy('height'); - const lastOwnerActivity = await CW721Activity.query() - .whereIn('action', [ - CW721_ACTION.MINT, - CW721_ACTION.TRANSFER, - CW721_ACTION.SEND_NFT, - CW721_ACTION.BURN, - ]) - .andWhere('cw721_token_id', cw721TokenId) - .andWhere('cw721_contract_id', cw721ContractId) - .whereNotNull('owner') - .orderBy('height', 'DESC') - .first(); - if (!lastOwnerActivity) { - activities[0].owner = activities[0].to; - } - const allActivities = lastOwnerActivity - ? [...activities, lastOwnerActivity] - : activities; - const sortedActivities = _.sortBy(allActivities, (e) => e.height); - for (let index = 1; index < sortedActivities.length; index += 1) { - sortedActivities[index].owner = sortedActivities[index - 1].to; - } - await CW721Activity.query() - .insert(sortedActivities) - .onConflict('id') - .merge(); - } - - @QueueHandler({ - queueName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, - jobName: BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, - }) - async jobHandler(): Promise { - const tokens = await CW721Activity.query() - .whereIn('action', [ - CW721_ACTION.TRANSFER, - CW721_ACTION.SEND_NFT, - CW721_ACTION.BURN, - ]) - .whereNull('owner') - .groupBy('cw721_contract_id', 'cw721_token_id') - .select('cw721_contract_id', 'cw721_token_id') - .limit(config.cw721.limitTokensActivityUpdate); - await Promise.all( - tokens.map(async (token) => - this.createJob( - BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, - BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER_BY_TOKEN, - { - cw721ContractId: token.cw721_contract_id, - cw721TokenId: token.cw721_token_id, - }, - { - removeOnComplete: true, - removeOnFail: { - count: 3, - }, - jobId: `${token.cw721_contract_id }_${ token.cw721_token_id}`, - } - ) - ) - ); - } - - async _start(): Promise { - if (NODE_ENV !== 'test') { - await this.createJob( - BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, - BULL_JOB_NAME.UPDATE_CW721_ACTIVITY_OWNER, - {}, - { - removeOnComplete: true, - removeOnFail: { - count: 3, - }, - repeat: { - every: config.cw721.millisecondRepeatJob, - }, - } - ); - } - return super._start(); - } -} diff --git a/src/services/cw721/cw721.service.ts b/src/services/cw721/cw721.service.ts index 5f9b4653a..5c7333438 100644 --- a/src/services/cw721/cw721.service.ts +++ b/src/services/cw721/cw721.service.ts @@ -296,9 +296,11 @@ export default class Cw721HandlerService extends BullableService { ), 'address' ); + const orderedEvents = _.orderBy(cw721Events, ['height'], ['asc']); + const cw721Activities: CW721Activity[] = []; await knex.transaction(async (trx) => { - const queries: any[] = []; - cw721Events.forEach((cw721Event) => { + // eslint-disable-next-line no-restricted-syntax + for (const cw721Event of orderedEvents) { // find the cw721 Event's smart contract id const cw721Contract = cw721ContractDbRecords[cw721Event.contractAddress]; @@ -321,49 +323,84 @@ export default class Cw721HandlerService extends BullableService { ); } } - queries.push( - CW721Activity.query() - .insert( - CW721Activity.fromJson({ - action: cw721Event.action, - sender: cw721Event.sender, - tx_hash: cw721Event.hash, - cw721_contract_id: cw721Contract.id, - cw721_token_id: cw721TokenId, - height: cw721Event.height, - smart_contract_event_id: cw721Event.smart_contract_event_id, - from: getAttributeFrom( - cw721Event.attributes, - EventAttribute.ATTRIBUTE_KEY.SENDER - ), - to: - getAttributeFrom( - cw721Event.attributes, - EventAttribute.ATTRIBUTE_KEY.OWNER - ) || - getAttributeFrom( - cw721Event.attributes, - EventAttribute.ATTRIBUTE_KEY.RECIPIENT - ), - }) - ) - .onConflict(['smart_contract_event_id']) - .merge() - .transacting(trx) + // eslint-disable-next-line no-await-in-loop + const owner = await this.getLatestOwnerForToken( + cw721Contract.id, + cw721TokenId, + cw721Activities + ); + const to = + getAttributeFrom( + cw721Event.attributes, + EventAttribute.ATTRIBUTE_KEY.OWNER + ) || + getAttributeFrom( + cw721Event.attributes, + EventAttribute.ATTRIBUTE_KEY.RECIPIENT + ); + cw721Activities.push( + CW721Activity.fromJson({ + action: cw721Event.action, + sender: cw721Event.sender, + tx_hash: cw721Event.hash, + cw721_contract_id: cw721Contract.id, + cw721_token_id: cw721TokenId, + height: cw721Event.height, + smart_contract_event_id: cw721Event.smart_contract_event_id, + from: getAttributeFrom( + cw721Event.attributes, + EventAttribute.ATTRIBUTE_KEY.SENDER + ), + to, + owner: owner || to, + }) ); } - }); - if (queries.length > 0) { - await Promise.all(queries) // Once every query is written - .then(trx.commit) // Try to execute all of them - .catch((e) => { - this.logger.error(e); - trx.rollback(); - }); // And rollback in case any of them goes wrong + } + if (cw721Activities.length > 0) { + await CW721Activity.query() + .insert(cw721Activities) + .onConflict(['smart_contract_event_id']) + .merge() + .transacting(trx); } }); } + async getLatestOwnerForToken( + cw721ContractId: number, + cw721TokenId: number | null, + others: CW721Activity[] + ) { + const latestActivity = _.findLast(others, (e) => ( + [ + CW721_ACTION.MINT, + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + ].includes(e.action) && + e.cw721_contract_id === cw721ContractId && + e.cw721_token_id === cw721TokenId + )); + if (latestActivity) { + return latestActivity.to; + } + const latestActivityDB = await CW721Activity.query() + .joinRelated('event') + .whereIn('cw721_activity.action', [ + CW721_ACTION.MINT, + CW721_ACTION.TRANSFER, + CW721_ACTION.SEND_NFT, + ]) + .andWhere('cw721_contract_id', cw721ContractId) + .andWhere('cw721_token_id', cw721TokenId) + .orderBy('event.block_height', 'desc') + .first(); + if (latestActivityDB) { + return latestActivityDB.to; + } + return null; + } + // handle Instantiate Msgs async handleInstantiateMsgs(msgsInstantiate: SmartContractEvent[]) { const cw721Contracts: any[] = await SmartContract.query() diff --git a/test/unit/services/cw721/cw721-activity-update-owner.spec.ts b/test/unit/services/cw721/cw721-activity-update-owner.spec.ts deleted file mode 100644 index 9132a044e..000000000 --- a/test/unit/services/cw721/cw721-activity-update-owner.spec.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; -import { ServiceBroker } from 'moleculer'; -import knex from '../../../../src/common/utils/db_connection'; -import CW721Contract from '../../../../src/models/cw721_contract'; -import { Code } from '../../../../src/models'; -import Cw721ActivityUpdateOwnerService from '../../../../src/services/cw721/cw721-activity-update-owner.service'; -import CW721Activity from '../../../../src/models/cw721_tx'; -import { CW721_ACTION } from '../../../../src/services/cw721/cw721.service'; - -@Describe('Test Cw721ActivityUpdateOwnerService') -export default class Cw721ActivityUpdateOwnerTest { - codeId = { - ...Code.fromJson({ - creator: 'code_id_creator', - code_id: 100, - data_hash: 'code_id_data_hash', - instantiate_permission: { permission: '', address: '', addresses: [] }, - store_hash: 'code_id_store_hash', - store_height: 1000, - type: 'CW721', - }), - contracts: [ - { - name: 'Base Contract 2', - address: 'mock_contract_address', - creator: 'phamphong_creator', - code_id: 100, - instantiate_hash: 'abc', - instantiate_height: 300000, - }, - ], - }; - - cw721Contract = { - ...CW721Contract.fromJson({ - contract_id: 1, - symbol: '', - name: 'jghdfgkjdfgjk', - track: true, - }), - tokens: [ - { - token_id: 'bhlvjdfkljkljg', - owner: 'ghgfhdgdsfgsdgfsd', - last_updated_height: 122120, - burned: false, - cw721_contract_id: 1, - }, - { - token_id: 'zzzvcxxb', - owner: 'xctgxgvxcgxx', - last_updated_height: 155, - burned: false, - cw721_contract_id: 1, - }, - { - token_id: 'vbmnnmn', - owner: 'ghgfhdgdsfgsdgfsd', - last_updated_height: 42424, - burned: false, - cw721_contract_id: 1, - }, - { - token_id: 'vcgasdfsdg', - owner: 'ghgfhdgdsfgsdgfsd', - last_updated_height: 121012, - burned: false, - cw721_contract_id: 1, - }, - { - token_id: 'cbvcxbvcxgbdv', - owner: 'ghgfhdgdsfgsdgfsd', - last_updated_height: 651651, - burned: false, - cw721_contract_id: 1, - }, - { - token_id: 'dghdfghdfgfdgdf', - owner: 'ghgfhdgdsfgsdgfsd', - last_updated_height: 2314, - burned: false, - cw721_contract_id: 1, - }, - ], - }; - - broker = new ServiceBroker({ logger: false }); - - cw721ActivityUpdateOwnerService = this.broker.createService( - Cw721ActivityUpdateOwnerService - ) as Cw721ActivityUpdateOwnerService; - - @BeforeAll() - async initSuite() { - this.cw721ActivityUpdateOwnerService.getQueueManager().stopAll(); - await this.broker.start(); - await knex.raw( - 'TRUNCATE TABLE code, cw721_contract, cw721_activity, block_checkpoint RESTART IDENTITY CASCADE' - ); - await Code.query().insertGraph(this.codeId); - await CW721Contract.query().insertGraph(this.cw721Contract); - } - - @AfterAll() - async tearDown() { - await this.broker.stop(); - } - - @Test('Test updateCw721ActOwnerByToken') - public async testUpdateCw721ActOwnerByToken() { - const activities = [ - { - action: 'mint', - sender: 'fsgsfgfs', - tx_hash: 'fgdfgdf', - cw721_contract_id: 1, - cw721_token_id: 1, - from: 'fgsfdghgj', - to: 'drsdrfsdfsd', - height: 100, - owner: null, - smart_contract_event_id: null, - }, - { - action: 'transfer_nft', - sender: 'hhhhhh', - tx_hash: 'vzxvxzc', - cw721_contract_id: 1, - cw721_token_id: 1, - from: 'fgfgfdg', - to: 'mmmmmmmm', - height: 200, - owner: null, - smart_contract_event_id: null, - }, - { - action: 'transfer_nft', - sender: 'xdfzf', - tx_hash: ' vgbhdghg', - cw721_contract_id: 1, - cw721_token_id: 1, - from: 'ertrtwerrewqr', - to: 'dsdasdasda', - height: 300, - owner: null, - smart_contract_event_id: null, - }, - { - action: 'send_nft', - sender: 'x cbcvbhcvdfzf', - tx_hash: ' fgdfytyertr', - cw721_contract_id: 1, - cw721_token_id: 1, - from: 'ertwrwrw', - to: 'xgdfbdndg', - height: 400, - owner: null, - smart_contract_event_id: null, - }, - { - action: 'burn', - sender: ' asaadsd', - tx_hash: ' xzcvxzvz', - cw721_contract_id: 1, - cw721_token_id: 1, - from: 'fdfasda', - to: 'tytytryrtyrt', - height: 500, - owner: null, - smart_contract_event_id: null, - }, - { - action: 'transfer_nft', - sender: 'ghfdyhtury', - tx_hash: 'kkkkkk', - cw721_contract_id: 1, - cw721_token_id: 2, - from: 'gfhvcgbdstg', - to: 'xgrtrfgh', - height: 200, - owner: null, - smart_contract_event_id: null, - }, - { - action: 'approve', - sender: ' asaadsd', - tx_hash: ' xzcvxzvz', - cw721_contract_id: 1, - cw721_token_id: 1, - from: 'fdfasda', - to: 'tytytryrtyrt', - height: 250, - owner: null, - smart_contract_event_id: null, - }, - ]; - await CW721Activity.query().insert( - activities.map((e) => CW721Activity.fromJson(e)) - ); - await this.cw721ActivityUpdateOwnerService.updateCw721ActOwnerByToken({ - cw721ContractId: 1, - cw721TokenId: 1, - }); - const updatedActivities = await CW721Activity.query() - .where('cw721_contract_id', 1) - .andWhere('cw721_token_id', 1) - .whereIn('action', [ - CW721_ACTION.MINT, - CW721_ACTION.TRANSFER, - CW721_ACTION.SEND_NFT, - CW721_ACTION.BURN, - ]) - .orderBy('height'); - expect(updatedActivities[0].owner).toEqual(activities[0].to); - expect(updatedActivities[1].owner).toEqual(activities[0].to); - expect(updatedActivities[2].owner).toEqual(activities[1].to); - expect(updatedActivities[3].owner).toEqual(activities[2].to); - expect(updatedActivities[4].owner).toEqual(activities[3].to); - await this.cw721ActivityUpdateOwnerService.updateCw721ActOwnerByToken({ - cw721ContractId: 1, - cw721TokenId: 2, - }); - const updatedActivities2 = await CW721Activity.query() - .where('cw721_contract_id', 1) - .andWhere('cw721_token_id', 2) - .whereIn('action', [ - CW721_ACTION.MINT, - CW721_ACTION.TRANSFER, - CW721_ACTION.SEND_NFT, - CW721_ACTION.BURN, - ]) - .orderBy('height'); - expect(updatedActivities2[0].owner).toEqual(activities[5].to); - } -} diff --git a/test/unit/services/cw721/cw721.spec.ts b/test/unit/services/cw721/cw721.spec.ts index 60c95a64f..baf45cfd7 100644 --- a/test/unit/services/cw721/cw721.spec.ts +++ b/test/unit/services/cw721/cw721.spec.ts @@ -1221,7 +1221,7 @@ export default class AssetIndexerTest { value: this.mockInitContract.tokens[0].token_id, }, ], - height: 100000, + height: 100, hash: 'cxvxcvxcvxcbvxcb', event_id: 100, }, @@ -1251,6 +1251,11 @@ export default class AssetIndexerTest { key: 'sender', value: 'aura1xahhax60fakwfng0sdd6wcxd0eeu00r5w3s49h', }, + { + smart_contract_event_id: '100', + key: 'token_id', + value: this.mockInitContract.tokens[0].token_id, + }, ], height: 100000, hash: 'fghgfhgfhfhdf', @@ -1280,13 +1285,86 @@ export default class AssetIndexerTest { { smart_contract_event_id: '100', key: 'token_id', - value: this.mockInitContract.tokens[1].token_id, + value: this.mockInitContract.tokens[0].token_id, }, ], height: 500000, hash: 'sdfdasrqewrasdEWEQE', event_id: 100, }, + { + contractAddress: this.mockInitContract.smart_contract.address, + sender: '', + action: 'mint', + content: + '{"mint": {"extension": {"image": "https://twilight.s3.ap-southeast-1.amazonaws.com/dev/p69ceVxdSNaslECBLbwN5gjHNYZSjQtb.png","name": "FEB24_1003","attributes": []},"owner": "aura1afuqcya9g59v0slx4e930gzytxvpx2c43xhvtx","token_id": "1677207819871"}}', + attributes: [ + { + smart_contract_event_id: '100', + key: '_contract_address', + value: this.mockInitContract.smart_contract.address, + }, + { + smart_contract_event_id: '100', + key: 'action', + value: 'mint', + }, + { + smart_contract_event_id: '100', + key: 'minter', + value: 'phamphong_test_re_mint_owner_haha', + }, + { + smart_contract_event_id: '100', + key: 'owner', + value: 'phamphong_test_re_mint_owner_haha', + }, + { + smart_contract_event_id: '100', + key: 'token_id', + value: this.mockInitContract.tokens[0].token_id, + }, + ], + height: 10000000, + hash: 'cxvxcvxcvxcbvxcb', + event_id: 100, + }, + { + contractAddress: this.mockInitContract.smart_contract.address, + sender: '', + action: 'transfer_nft', + content: '', + attributes: [ + { + smart_contract_event_id: '100', + key: '_contract_address', + value: this.mockInitContract.smart_contract.address, + }, + { + smart_contract_event_id: '100', + key: 'action', + value: 'transfer_nft', + }, + { + smart_contract_event_id: '100', + key: 'recipient', + value: 'phamphong_transfer', + }, + { + smart_contract_event_id: '100', + key: 'sender', + value: 'aura1xahhax60fakwfng0sdd6wcxd0eeu00r5w3s49h', + }, + { + smart_contract_event_id: '100', + key: 'token_id', + value: this.mockInitContract.tokens[0].token_id, + }, + ], + height: 10000001, + hash: 'fghgfhgfhfhdf', + event_id: 10, + }, ]; await this.cw721HandlerService.handleCW721Activity( mockActivityMsgs.map((burnEvent) => @@ -1302,8 +1380,8 @@ export default class AssetIndexerTest { expect(cw721Activity.tx_hash).toEqual(mockActivityMsgs[index].hash); }); expect(cw721Activities[0].cw721_token_id).toEqual(1); - expect(cw721Activities[1].cw721_token_id).toEqual(0); - expect(cw721Activities[2].cw721_token_id).toEqual(2); + expect(cw721Activities[1].cw721_token_id).toEqual(1); + expect(cw721Activities[2].cw721_token_id).toEqual(1); expect(cw721Activities[0].from).toEqual(null); expect(cw721Activities[0].to).toEqual( getAttributeFrom( @@ -1329,6 +1407,11 @@ export default class AssetIndexerTest { EventAttribute.ATTRIBUTE_KEY.SENDER ) ); + expect(cw721Activities[0].owner).toEqual(cw721Activities[0].to); + expect(cw721Activities[1].owner).toEqual(cw721Activities[0].to); + expect(cw721Activities[2].owner).toEqual(cw721Activities[1].to); + expect(cw721Activities[3].owner).toEqual(cw721Activities[1].to); + expect(cw721Activities[4].owner).toEqual(cw721Activities[3].to); } @Test('test handle multi contract events') From e1d23ff99179b728ba5b903225fdfe82d9fb57ab Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Thu, 7 Sep 2023 17:27:00 +0700 Subject: [PATCH 28/33] refactor: review code --- ...024631_cw721_activity_add_current_owner.ts | 36 +++++----- src/services/cw721/cw721.service.ts | 69 ++++++++++--------- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/migrations/20230830024631_cw721_activity_add_current_owner.ts b/migrations/20230830024631_cw721_activity_add_current_owner.ts index 701ddd787..399556ae8 100644 --- a/migrations/20230830024631_cw721_activity_add_current_owner.ts +++ b/migrations/20230830024631_cw721_activity_add_current_owner.ts @@ -1,7 +1,8 @@ import { Knex } from 'knex'; import CW721Activity from '../src/models/cw721_tx'; -import _ from 'lodash'; +import _, { Dictionary } from 'lodash'; import { CW721_ACTION } from '../src/services/cw721/cw721.service'; + export async function up(knex: Knex): Promise { await knex.schema.alterTable('cw721_activity', (table) => { table.string('owner'); @@ -9,27 +10,28 @@ export async function up(knex: Knex): Promise { await knex.transaction(async (trx) => { const activities = await CW721Activity.query() .joinRelated('event') - .orderBy('event.block_height', 'asc') + .orderBy('event.id', 'asc') .transacting(trx); - const done: CW721Activity[] = []; + const latestOwners: Dictionary = {}; activities.forEach((activity) => { - const latestActivity = _.findLast(done, function (e) { - return ( - [ - CW721_ACTION.MINT, - CW721_ACTION.TRANSFER, - CW721_ACTION.SEND_NFT, - ].includes(e.action) && - e.cw721_contract_id === activity.cw721_contract_id && - e.cw721_token_id === activity.cw721_token_id - ); - }); - if (latestActivity) { - activity.owner = latestActivity.to; + const latestOwner = + latestOwners[ + activity.cw721_contract_id + '_' + activity.cw721_token_id + ]; + if (latestOwner) { + activity.owner = latestOwner; } else { activity.owner = activity.to; } - done.push(activity); + if ( + activity.action === CW721_ACTION.MINT || + activity.action === CW721_ACTION.TRANSFER || + activity.action === CW721_ACTION.SEND_NFT + ) { + latestOwners[ + activity.cw721_contract_id + '_' + activity.cw721_token_id + ] = activity.to; + } }); if (activities.length > 0) { await CW721Activity.query() diff --git a/src/services/cw721/cw721.service.ts b/src/services/cw721/cw721.service.ts index df8ac909c..4e6fc1b34 100644 --- a/src/services/cw721/cw721.service.ts +++ b/src/services/cw721/cw721.service.ts @@ -3,7 +3,7 @@ import { Action, Service, } from '@ourparentcenter/moleculer-decorators-extended'; -import _ from 'lodash'; +import _, { Dictionary } from 'lodash'; import { Context, ServiceBroker } from 'moleculer'; import { Queue } from 'bullmq'; import config from '../../../config.json' assert { type: 'json' }; @@ -298,6 +298,7 @@ export default class Cw721HandlerService extends BullableService { ); const orderedEvents = _.orderBy(cw721Events, ['height'], ['asc']); const cw721Activities: CW721Activity[] = []; + const latestOwners: Dictionary = {}; await knex.transaction(async (trx) => { // eslint-disable-next-line no-restricted-syntax for (const cw721Event of orderedEvents) { @@ -327,7 +328,7 @@ export default class Cw721HandlerService extends BullableService { const owner = await this.getLatestOwnerForToken( cw721Contract.id, cw721TokenId, - cw721Activities + latestOwners ); const to = getAttributeFrom( @@ -338,23 +339,35 @@ export default class Cw721HandlerService extends BullableService { cw721Event.attributes, EventAttribute.ATTRIBUTE_KEY.RECIPIENT ); - cw721Activities.push( - CW721Activity.fromJson({ - action: cw721Event.action, - sender: cw721Event.sender, - tx_hash: cw721Event.hash, - cw721_contract_id: cw721Contract.id, - cw721_token_id: cw721TokenId, - height: cw721Event.height, - smart_contract_event_id: cw721Event.smart_contract_event_id, - from: getAttributeFrom( - cw721Event.attributes, - EventAttribute.ATTRIBUTE_KEY.SENDER - ), - to, - owner: owner || to, - }) - ); + const cw721Activity = CW721Activity.fromJson({ + action: cw721Event.action, + sender: cw721Event.sender, + tx_hash: cw721Event.hash, + cw721_contract_id: cw721Contract.id, + cw721_token_id: cw721TokenId, + height: cw721Event.height, + smart_contract_event_id: cw721Event.smart_contract_event_id, + from: getAttributeFrom( + cw721Event.attributes, + EventAttribute.ATTRIBUTE_KEY.SENDER + ), + to, + owner: owner || to, + }); + // push to update records + cw721Activities.push(cw721Activity); + // update latestActivity for this token + if ( + cw721Activity.action === CW721_ACTION.MINT || + cw721Activity.action === CW721_ACTION.TRANSFER || + cw721Activity.action === CW721_ACTION.SEND_NFT + ) { + latestOwners[ + `${cw721Activity.cw721_contract_id + }_${ + cw721Activity.cw721_token_id}` + ] = cw721Activity.to; + } } } if (cw721Activities.length > 0) { @@ -370,21 +383,11 @@ export default class Cw721HandlerService extends BullableService { async getLatestOwnerForToken( cw721ContractId: number, cw721TokenId: number | null, - others: CW721Activity[] + latestOwners: Dictionary ) { - const latestActivity = _.findLast( - others, - (e) => - [ - CW721_ACTION.MINT, - CW721_ACTION.TRANSFER, - CW721_ACTION.SEND_NFT, - ].includes(e.action) && - e.cw721_contract_id === cw721ContractId && - e.cw721_token_id === cw721TokenId - ); - if (latestActivity) { - return latestActivity.to; + const latestOwner = latestOwners[`${cw721ContractId }_${ cw721TokenId}`]; + if (latestOwner) { + return latestOwner; } const latestActivityDB = await CW721Activity.query() .joinRelated('event') From ced56b9ed50a78dc71175016d459988d3ab29f09 Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Thu, 7 Sep 2023 17:27:00 +0700 Subject: [PATCH 29/33] refactor: review code --- ...024631_cw721_activity_add_current_owner.ts | 36 +++++----- src/services/cw721/cw721.service.ts | 65 ++++++++++--------- 2 files changed, 53 insertions(+), 48 deletions(-) diff --git a/migrations/20230830024631_cw721_activity_add_current_owner.ts b/migrations/20230830024631_cw721_activity_add_current_owner.ts index 701ddd787..399556ae8 100644 --- a/migrations/20230830024631_cw721_activity_add_current_owner.ts +++ b/migrations/20230830024631_cw721_activity_add_current_owner.ts @@ -1,7 +1,8 @@ import { Knex } from 'knex'; import CW721Activity from '../src/models/cw721_tx'; -import _ from 'lodash'; +import _, { Dictionary } from 'lodash'; import { CW721_ACTION } from '../src/services/cw721/cw721.service'; + export async function up(knex: Knex): Promise { await knex.schema.alterTable('cw721_activity', (table) => { table.string('owner'); @@ -9,27 +10,28 @@ export async function up(knex: Knex): Promise { await knex.transaction(async (trx) => { const activities = await CW721Activity.query() .joinRelated('event') - .orderBy('event.block_height', 'asc') + .orderBy('event.id', 'asc') .transacting(trx); - const done: CW721Activity[] = []; + const latestOwners: Dictionary = {}; activities.forEach((activity) => { - const latestActivity = _.findLast(done, function (e) { - return ( - [ - CW721_ACTION.MINT, - CW721_ACTION.TRANSFER, - CW721_ACTION.SEND_NFT, - ].includes(e.action) && - e.cw721_contract_id === activity.cw721_contract_id && - e.cw721_token_id === activity.cw721_token_id - ); - }); - if (latestActivity) { - activity.owner = latestActivity.to; + const latestOwner = + latestOwners[ + activity.cw721_contract_id + '_' + activity.cw721_token_id + ]; + if (latestOwner) { + activity.owner = latestOwner; } else { activity.owner = activity.to; } - done.push(activity); + if ( + activity.action === CW721_ACTION.MINT || + activity.action === CW721_ACTION.TRANSFER || + activity.action === CW721_ACTION.SEND_NFT + ) { + latestOwners[ + activity.cw721_contract_id + '_' + activity.cw721_token_id + ] = activity.to; + } }); if (activities.length > 0) { await CW721Activity.query() diff --git a/src/services/cw721/cw721.service.ts b/src/services/cw721/cw721.service.ts index 5c7333438..43dc6eb83 100644 --- a/src/services/cw721/cw721.service.ts +++ b/src/services/cw721/cw721.service.ts @@ -3,7 +3,7 @@ import { Action, Service, } from '@ourparentcenter/moleculer-decorators-extended'; -import _ from 'lodash'; +import _, { Dictionary } from 'lodash'; import { Context, ServiceBroker } from 'moleculer'; import { Queue } from 'bullmq'; import config from '../../../config.json' assert { type: 'json' }; @@ -298,6 +298,7 @@ export default class Cw721HandlerService extends BullableService { ); const orderedEvents = _.orderBy(cw721Events, ['height'], ['asc']); const cw721Activities: CW721Activity[] = []; + const latestOwners: Dictionary = {}; await knex.transaction(async (trx) => { // eslint-disable-next-line no-restricted-syntax for (const cw721Event of orderedEvents) { @@ -327,7 +328,7 @@ export default class Cw721HandlerService extends BullableService { const owner = await this.getLatestOwnerForToken( cw721Contract.id, cw721TokenId, - cw721Activities + latestOwners ); const to = getAttributeFrom( @@ -338,23 +339,33 @@ export default class Cw721HandlerService extends BullableService { cw721Event.attributes, EventAttribute.ATTRIBUTE_KEY.RECIPIENT ); - cw721Activities.push( - CW721Activity.fromJson({ - action: cw721Event.action, - sender: cw721Event.sender, - tx_hash: cw721Event.hash, - cw721_contract_id: cw721Contract.id, - cw721_token_id: cw721TokenId, - height: cw721Event.height, - smart_contract_event_id: cw721Event.smart_contract_event_id, - from: getAttributeFrom( - cw721Event.attributes, - EventAttribute.ATTRIBUTE_KEY.SENDER - ), - to, - owner: owner || to, - }) - ); + const cw721Activity = CW721Activity.fromJson({ + action: cw721Event.action, + sender: cw721Event.sender, + tx_hash: cw721Event.hash, + cw721_contract_id: cw721Contract.id, + cw721_token_id: cw721TokenId, + height: cw721Event.height, + smart_contract_event_id: cw721Event.smart_contract_event_id, + from: getAttributeFrom( + cw721Event.attributes, + EventAttribute.ATTRIBUTE_KEY.SENDER + ), + to, + owner: owner || to, + }); + // push to update records + cw721Activities.push(cw721Activity); + // update latestActivity for this token + if ( + cw721Activity.action === CW721_ACTION.MINT || + cw721Activity.action === CW721_ACTION.TRANSFER || + cw721Activity.action === CW721_ACTION.SEND_NFT + ) { + latestOwners[ + `${cw721Activity.cw721_contract_id}_${cw721Activity.cw721_token_id}` + ] = cw721Activity.to; + } } } if (cw721Activities.length > 0) { @@ -370,19 +381,11 @@ export default class Cw721HandlerService extends BullableService { async getLatestOwnerForToken( cw721ContractId: number, cw721TokenId: number | null, - others: CW721Activity[] + latestOwners: Dictionary ) { - const latestActivity = _.findLast(others, (e) => ( - [ - CW721_ACTION.MINT, - CW721_ACTION.TRANSFER, - CW721_ACTION.SEND_NFT, - ].includes(e.action) && - e.cw721_contract_id === cw721ContractId && - e.cw721_token_id === cw721TokenId - )); - if (latestActivity) { - return latestActivity.to; + const latestOwner = latestOwners[`${cw721ContractId}_${cw721TokenId}`]; + if (latestOwner) { + return latestOwner; } const latestActivityDB = await CW721Activity.query() .joinRelated('event') From cc3467bdbdeb558905577ca36393719ca2a76d9a Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Fri, 8 Sep 2023 09:50:06 +0700 Subject: [PATCH 30/33] refactor: code --- src/models/cw721_tx.ts | 2 -- src/services/cw721/cw721.service.ts | 8 ++---- test/unit/services/cw721/cw721.spec.ts | 34 +++++++++----------------- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/src/models/cw721_tx.ts b/src/models/cw721_tx.ts index da20a5325..024acc28e 100644 --- a/src/models/cw721_tx.ts +++ b/src/models/cw721_tx.ts @@ -33,8 +33,6 @@ export default class CW721Activity extends BaseModel { height!: number; - owner!: string; - smart_contract_event_id!: number; static get tableName() { diff --git a/src/services/cw721/cw721.service.ts b/src/services/cw721/cw721.service.ts index 43dc6eb83..ff60a38e7 100644 --- a/src/services/cw721/cw721.service.ts +++ b/src/services/cw721/cw721.service.ts @@ -325,7 +325,7 @@ export default class Cw721HandlerService extends BullableService { } } // eslint-disable-next-line no-await-in-loop - const owner = await this.getLatestOwnerForToken( + const from = await this.getLatestOwnerForToken( cw721Contract.id, cw721TokenId, latestOwners @@ -347,12 +347,8 @@ export default class Cw721HandlerService extends BullableService { cw721_token_id: cw721TokenId, height: cw721Event.height, smart_contract_event_id: cw721Event.smart_contract_event_id, - from: getAttributeFrom( - cw721Event.attributes, - EventAttribute.ATTRIBUTE_KEY.SENDER - ), to, - owner: owner || to, + from, }); // push to update records cw721Activities.push(cw721Activity); diff --git a/test/unit/services/cw721/cw721.spec.ts b/test/unit/services/cw721/cw721.spec.ts index baf45cfd7..2ca217f39 100644 --- a/test/unit/services/cw721/cw721.spec.ts +++ b/test/unit/services/cw721/cw721.spec.ts @@ -1190,7 +1190,7 @@ export default class AssetIndexerTest { const mockActivityMsgs = [ { contractAddress: this.mockInitContract.smart_contract.address, - sender: '', + sender: 'xv xcgfsgsfgsd', action: 'mint', content: '{"mint": {"extension": {"image": "https://twilight.s3.ap-southeast-1.amazonaws.com/dev/p69ceVxdSNaslECBLbwN5gjHNYZSjQtb.png","name": "FEB24_1003","attributes": []},"owner": "aura1afuqcya9g59v0slx4e930gzytxvpx2c43xhvtx","token_id": "1677207819871"}}', @@ -1227,7 +1227,7 @@ export default class AssetIndexerTest { }, { contractAddress: this.mockInitContract.smart_contract.address, - sender: '', + sender: 'xbvxcbfsdgfsgf', action: 'transfer_nft', content: '', attributes: [ @@ -1263,7 +1263,7 @@ export default class AssetIndexerTest { }, { contractAddress: this.mockInitContract.smart_contract.address, - sender: '', + sender: 'fsgvccxvsdfgfsdg', action: 'burn', content: '', attributes: [ @@ -1294,7 +1294,7 @@ export default class AssetIndexerTest { }, { contractAddress: this.mockInitContract.smart_contract.address, - sender: '', + sender: 'sdgsgfvfbvxcbx', action: 'mint', content: '{"mint": {"extension": {"image": "https://twilight.s3.ap-southeast-1.amazonaws.com/dev/p69ceVxdSNaslECBLbwN5gjHNYZSjQtb.png","name": "FEB24_1003","attributes": []},"owner": "aura1afuqcya9g59v0slx4e930gzytxvpx2c43xhvtx","token_id": "1677207819871"}}', @@ -1331,7 +1331,7 @@ export default class AssetIndexerTest { }, { contractAddress: this.mockInitContract.smart_contract.address, - sender: '', + sender: 'gdfgdfgdhgfh', action: 'transfer_nft', content: '', attributes: [ @@ -1378,40 +1378,28 @@ export default class AssetIndexerTest { this.mockInitContract.tokens[0].cw721_contract_id ); expect(cw721Activity.tx_hash).toEqual(mockActivityMsgs[index].hash); + expect(cw721Activity.sender).toEqual(mockActivityMsgs[index].sender); }); expect(cw721Activities[0].cw721_token_id).toEqual(1); expect(cw721Activities[1].cw721_token_id).toEqual(1); expect(cw721Activities[2].cw721_token_id).toEqual(1); - expect(cw721Activities[0].from).toEqual(null); expect(cw721Activities[0].to).toEqual( getAttributeFrom( mockActivityMsgs[0].attributes, EventAttribute.ATTRIBUTE_KEY.OWNER ) ); - expect(cw721Activities[1].from).toEqual( - getAttributeFrom( - mockActivityMsgs[1].attributes, - EventAttribute.ATTRIBUTE_KEY.SENDER - ) - ); expect(cw721Activities[1].to).toEqual( getAttributeFrom( mockActivityMsgs[1].attributes, EventAttribute.ATTRIBUTE_KEY.RECIPIENT ) ); - expect(cw721Activities[2].from).toEqual( - getAttributeFrom( - mockActivityMsgs[2].attributes, - EventAttribute.ATTRIBUTE_KEY.SENDER - ) - ); - expect(cw721Activities[0].owner).toEqual(cw721Activities[0].to); - expect(cw721Activities[1].owner).toEqual(cw721Activities[0].to); - expect(cw721Activities[2].owner).toEqual(cw721Activities[1].to); - expect(cw721Activities[3].owner).toEqual(cw721Activities[1].to); - expect(cw721Activities[4].owner).toEqual(cw721Activities[3].to); + expect(cw721Activities[0].from).toBeNull(); + expect(cw721Activities[1].from).toEqual(cw721Activities[0].to); + expect(cw721Activities[2].from).toEqual(cw721Activities[1].to); + expect(cw721Activities[3].from).toEqual(cw721Activities[1].to); + expect(cw721Activities[4].from).toEqual(cw721Activities[3].to); } @Test('test handle multi contract events') From 01e0c7037087592e547ecb891c3419f71e51240b Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Fri, 8 Sep 2023 10:34:38 +0700 Subject: [PATCH 31/33] refactor: migration --- ...0024631_cw721_activity_add_current_owner.ts | 18 ++++++++++-------- src/models/cw721_tx.ts | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/migrations/20230830024631_cw721_activity_add_current_owner.ts b/migrations/20230830024631_cw721_activity_add_current_owner.ts index 399556ae8..7da99fe07 100644 --- a/migrations/20230830024631_cw721_activity_add_current_owner.ts +++ b/migrations/20230830024631_cw721_activity_add_current_owner.ts @@ -5,7 +5,13 @@ import { CW721_ACTION } from '../src/services/cw721/cw721.service'; export async function up(knex: Knex): Promise { await knex.schema.alterTable('cw721_activity', (table) => { - table.string('owner'); + table.dropColumn('sender'); + }); + await knex.schema.alterTable('cw721_activity', (table) => { + table.renameColumn('from', 'sender'); + }); + await knex.schema.alterTable('cw721_activity', (table) => { + table.string('from'); }); await knex.transaction(async (trx) => { const activities = await CW721Activity.query() @@ -19,9 +25,9 @@ export async function up(knex: Knex): Promise { activity.cw721_contract_id + '_' + activity.cw721_token_id ]; if (latestOwner) { - activity.owner = latestOwner; + activity.from = latestOwner; } else { - activity.owner = activity.to; + activity.from = null; } if ( activity.action === CW721_ACTION.MINT || @@ -43,8 +49,4 @@ export async function up(knex: Knex): Promise { }); } -export async function down(knex: Knex): Promise { - await knex.schema.alterTable('cw721_activity', (table) => { - table.dropColumn('owner'); - }); -} +export async function down(knex: Knex): Promise {} diff --git a/src/models/cw721_tx.ts b/src/models/cw721_tx.ts index 024acc28e..9738ca249 100644 --- a/src/models/cw721_tx.ts +++ b/src/models/cw721_tx.ts @@ -27,7 +27,7 @@ export default class CW721Activity extends BaseModel { updated_at?: Date; - from!: string; + from!: string | null; to!: string; From dcbd97dced6460af073c1f25bab446b58e195006 Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Fri, 8 Sep 2023 10:57:44 +0700 Subject: [PATCH 32/33] refactor: sender --- src/services/cw721/cw721.service.ts | 5 ++++- test/unit/services/cw721/cw721.spec.ts | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/services/cw721/cw721.service.ts b/src/services/cw721/cw721.service.ts index ff60a38e7..6f2c47115 100644 --- a/src/services/cw721/cw721.service.ts +++ b/src/services/cw721/cw721.service.ts @@ -341,7 +341,10 @@ export default class Cw721HandlerService extends BullableService { ); const cw721Activity = CW721Activity.fromJson({ action: cw721Event.action, - sender: cw721Event.sender, + sender: getAttributeFrom( + cw721Event.attributes, + EventAttribute.ATTRIBUTE_KEY.SENDER + ), tx_hash: cw721Event.hash, cw721_contract_id: cw721Contract.id, cw721_token_id: cw721TokenId, diff --git a/test/unit/services/cw721/cw721.spec.ts b/test/unit/services/cw721/cw721.spec.ts index 2ca217f39..79c051d29 100644 --- a/test/unit/services/cw721/cw721.spec.ts +++ b/test/unit/services/cw721/cw721.spec.ts @@ -1378,7 +1378,12 @@ export default class AssetIndexerTest { this.mockInitContract.tokens[0].cw721_contract_id ); expect(cw721Activity.tx_hash).toEqual(mockActivityMsgs[index].hash); - expect(cw721Activity.sender).toEqual(mockActivityMsgs[index].sender); + expect(cw721Activity.sender).toEqual( + getAttributeFrom( + mockActivityMsgs[index].attributes, + EventAttribute.ATTRIBUTE_KEY.SENDER + ) || null + ); }); expect(cw721Activities[0].cw721_token_id).toEqual(1); expect(cw721Activities[1].cw721_token_id).toEqual(1); From f95072a9eed7d09e2f6ff330e3e8c2b448c11eeb Mon Sep 17 00:00:00 2001 From: phamphong9981 Date: Fri, 8 Sep 2023 14:55:40 +0700 Subject: [PATCH 33/33] refactor: review --- .../20230830024631_cw721_activity_add_current_owner.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/migrations/20230830024631_cw721_activity_add_current_owner.ts b/migrations/20230830024631_cw721_activity_add_current_owner.ts index 7da99fe07..103a0f896 100644 --- a/migrations/20230830024631_cw721_activity_add_current_owner.ts +++ b/migrations/20230830024631_cw721_activity_add_current_owner.ts @@ -4,14 +4,8 @@ import _, { Dictionary } from 'lodash'; import { CW721_ACTION } from '../src/services/cw721/cw721.service'; export async function up(knex: Knex): Promise { - await knex.schema.alterTable('cw721_activity', (table) => { - table.dropColumn('sender'); - }); - await knex.schema.alterTable('cw721_activity', (table) => { - table.renameColumn('from', 'sender'); - }); - await knex.schema.alterTable('cw721_activity', (table) => { - table.string('from'); + await knex('cw721_activity').update({ + sender: knex.ref('from'), }); await knex.transaction(async (trx) => { const activities = await CW721Activity.query()