From ec9bf2898de27e1c2d78b971b1ad2c83b9fe0106 Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Wed, 15 Nov 2023 10:47:38 +0700 Subject: [PATCH 01/10] feat: crawl coin transfer --- src/common/constant.ts | 1 + .../crawl-tx/coin_transfer.service.ts | 239 ++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 src/services/crawl-tx/coin_transfer.service.ts diff --git a/src/common/constant.ts b/src/common/constant.ts index 7f4361c4b..eb8f284a4 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -32,6 +32,7 @@ export const BULL_JOB_NAME = { HANDLE_STAKE_EVENT: 'handle:stake-event', CRAWL_BLOCK: 'crawl:block', HANDLE_TRANSACTION: 'handle:transaction', + HANDLE_COIN_TRANSFER: 'handle:coin_transfer', HANDLE_CW721_TRANSACTION: 'handle:cw721-tx', REFRESH_CW721_STATS: 'refresh:cw721-stats', CRAWL_PROPOSAL: 'crawl:proposal', diff --git a/src/services/crawl-tx/coin_transfer.service.ts b/src/services/crawl-tx/coin_transfer.service.ts new file mode 100644 index 000000000..e7cbf3c0f --- /dev/null +++ b/src/services/crawl-tx/coin_transfer.service.ts @@ -0,0 +1,239 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { ServiceBroker } from 'moleculer'; +import { Service } from '@ourparentcenter/moleculer-decorators-extended'; +import { BULL_JOB_NAME, SERVICE } from '../../common'; +import { + BlockCheckpoint, + CoinTransfer, + Event, + Transaction, +} from '../../models'; +import BullableService, { QueueHandler } from '../../base/bullable.service'; +import config from '../../../config.json' assert { type: 'json' }; +import knex from '../../common/utils/db_connection'; + +@Service({ + name: SERVICE.V1.CrawlTransaction.key, + version: 1, +}) +export default class CrawlTxService extends BullableService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + /** + * @description Get latest coin transfer to get latest height, otherwise get height from the oldest transaction crawled + * @private + */ + private async getLatestCoinTransferHeight(): Promise { + const blockCheckpointCT = await BlockCheckpoint.query() + .where('job_name', BULL_JOB_NAME.HANDLE_COIN_TRANSFER) + .first(); + + if (!blockCheckpointCT) { + const oldestTransaction = await Transaction.query() + .orderBy('height', 'ASC') + .first(); + const latestBlockHeight = oldestTransaction + ? oldestTransaction.height + : 0; + await BlockCheckpoint.query() + .insert({ + height: latestBlockHeight, + job_name: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, + }) + .onConflict('job_name') + .merge(); + return latestBlockHeight; + } + return blockCheckpointCT.height; + } + + /** + * @description Get transaction data for insert coin transfer + * @param fromHeight + * @param toHeight + * @private + */ + private async fetchTransactionCTByHeight( + fromHeight: number, + toHeight: number + ): Promise { + return Transaction.query() + .withGraphFetched('events.[attributes]') + .modifyGraph('events', (builder) => { + builder.andWhere('type', '=', 'transfer').whereNotNull('tx_msg_index'); + }) + .withGraphFetched('messages') + .where('transaction.height', '>', fromHeight) + .andWhere('transaction.height', '<=', toHeight); + } + + /** + * split amount to amount and denom using regex + * example: 10000uaura + * amount = 10000 + * denom = uaura + * return [0, ''] if invalid + */ + private extractAmount(rawAmount: string | undefined): [number, string] { + const amount = rawAmount?.match(/(\d+)/)?.[0] ?? '0'; + const denom = rawAmount?.replace(amount, '') ?? ''; + return [Number.parseInt(amount, 10), denom]; + } + + @QueueHandler({ + queueName: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, + jobName: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, + }) + public async jobHandlerCrawlTx() { + const transactionCheckPoint = await BlockCheckpoint.query() + .where('job_name', BULL_JOB_NAME.HANDLE_TRANSACTION) + .first(); + let latestCoinTransferHeight = await this.getLatestCoinTransferHeight(); + + if (!transactionCheckPoint) { + this.logger.info('Waiting for new transaction crawled'); + return; + } + + while (latestCoinTransferHeight < transactionCheckPoint.height) { + const fromBlock = latestCoinTransferHeight; + const toBlock = fromBlock + 100; + this.logger.info( + `QUERY FROM ${fromBlock} - TO ${toBlock}................` + ); + + const coinTransfers: CoinTransfer[] = []; + // eslint-disable-next-line no-await-in-loop + const transactions = await this.fetchTransactionCTByHeight( + fromBlock, + toBlock + ); + + transactions.forEach((tx: Transaction) => { + tx.events.forEach((event: Event) => { + if (!event.tx_msg_index) return; + // skip if message is not 'MsgMultiSend' + if ( + event.attributes.length !== 3 && + tx.messages[event.tx_msg_index].type !== + '/cosmos.bank.v1beta1.MsgMultiSend' + ) { + this.logger.error( + 'Coin transfer detected in unsupported message type', + tx.hash, + tx.messages[event.tx_msg_index].content + ); + return; + } + + const ctTemplate = { + block_height: tx.height, + tx_id: tx.id, + tx_msg_id: tx.messages[event.tx_msg_index].id, + from: event.attributes.find((attr) => attr.key === 'sender')?.value, + to: '', + amount: 0, + denom: '', + timestamp: new Date(tx.timestamp).toISOString(), + }; + /** + * we expect 2 cases: + * 1. transfer event has only 1 sender and 1 recipient + * then the event will have 3 attributes: sender, recipient, amount + * 2. transfer event has 1 sender and multiple recipients, message must be 'MsgMultiSend' + * then the event will be an array of attributes: recipient1, amount1, recipient2, amount2, ... + * sender is the coin_spent.spender + */ + if (event.attributes.length === 3) { + const rawAmount = event.attributes.find( + (attr) => attr.key === 'amount' + )?.value; + const [amount, denom] = this.extractAmount(rawAmount); + coinTransfers.push( + CoinTransfer.fromJson({ + ...ctTemplate, + from: event.attributes.find((attr) => attr.key === 'sender') + ?.value, + to: event.attributes.find((attr) => attr.key === 'recipient') + ?.value, + amount, + denom, + }) + ); + return; + } + + const coinSpentEvent = tx.events.find( + (e: Event) => + e.type === 'coin_spent' && e.tx_msg_index === event.tx_msg_index + ); + ctTemplate.from = coinSpentEvent?.attributes.find( + (attr: { key: string; value: string }) => attr.key === 'spender' + )?.value; + for (let i = 0; i < event.attributes.length; i += 2) { + if ( + event.attributes[i].key !== 'recipient' && + event.attributes[i + 1].key !== 'amount' + ) { + this.logger.error( + 'Coin transfer in MsgMultiSend detected with invalid attributes', + tx.hash, + event.attributes + ); + return; + } + + const rawAmount = event.attributes[i + 1].value; + const [amount, denom] = this.extractAmount(rawAmount); + coinTransfers.push( + CoinTransfer.fromJson({ + ...ctTemplate, + to: event.attributes[i].value, + amount, + denom, + }) + ); + } + }); + }); + + latestCoinTransferHeight = toBlock; + // eslint-disable-next-line no-await-in-loop,@typescript-eslint/no-loop-func + await knex.transaction(async (trx) => { + await BlockCheckpoint.query() + .transacting(trx) + .insert({ + height: latestCoinTransferHeight, + job_name: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, + }) + .onConflict('job_name') + .merge(); + + if (coinTransfers.length > 0) { + this.logger.info(`INSERTING ${coinTransfers.length} COIN TRANSFER`); + await CoinTransfer.query().transacting(trx).insert(coinTransfers); + } + }); + } + } + + public async _start() { + this.createJob( + BULL_JOB_NAME.HANDLE_COIN_TRANSFER, + BULL_JOB_NAME.HANDLE_COIN_TRANSFER, + {}, + { + removeOnComplete: true, + removeOnFail: { + count: 3, + }, + repeat: { + every: config.handleTransaction.millisecondCrawl, + }, + } + ); + return super._start(); + } +} From 8946b460f7217136f38818b2c60df31fed2b8c21 Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Wed, 15 Nov 2023 11:01:41 +0700 Subject: [PATCH 02/10] feat: crawl coin transfer --- .../crawl-tx/coin_transfer.service.ts | 222 +++++++++--------- 1 file changed, 111 insertions(+), 111 deletions(-) diff --git a/src/services/crawl-tx/coin_transfer.service.ts b/src/services/crawl-tx/coin_transfer.service.ts index e7cbf3c0f..5efb090ba 100644 --- a/src/services/crawl-tx/coin_transfer.service.ts +++ b/src/services/crawl-tx/coin_transfer.service.ts @@ -92,131 +92,131 @@ export default class CrawlTxService extends BullableService { .first(); let latestCoinTransferHeight = await this.getLatestCoinTransferHeight(); - if (!transactionCheckPoint) { + if ( + !transactionCheckPoint || + latestCoinTransferHeight >= transactionCheckPoint.height + ) { this.logger.info('Waiting for new transaction crawled'); return; } - while (latestCoinTransferHeight < transactionCheckPoint.height) { - const fromBlock = latestCoinTransferHeight; - const toBlock = fromBlock + 100; - this.logger.info( - `QUERY FROM ${fromBlock} - TO ${toBlock}................` - ); - - const coinTransfers: CoinTransfer[] = []; - // eslint-disable-next-line no-await-in-loop - const transactions = await this.fetchTransactionCTByHeight( - fromBlock, - toBlock - ); - - transactions.forEach((tx: Transaction) => { - tx.events.forEach((event: Event) => { - if (!event.tx_msg_index) return; - // skip if message is not 'MsgMultiSend' + const fromBlock = latestCoinTransferHeight; + const toBlock = Math.min( + fromBlock + config.handleCoinTransfer.blocksPerCall, + transactionCheckPoint.height + ); + this.logger.info(`QUERY FROM ${fromBlock} - TO ${toBlock}................`); + + const coinTransfers: CoinTransfer[] = []; + const transactions = await this.fetchTransactionCTByHeight( + fromBlock, + toBlock + ); + + transactions.forEach((tx: Transaction) => { + tx.events.forEach((event: Event) => { + if (!event.tx_msg_index) return; + // skip if message is not 'MsgMultiSend' + if ( + event.attributes.length !== 3 && + tx.messages[event.tx_msg_index].type !== + '/cosmos.bank.v1beta1.MsgMultiSend' + ) { + this.logger.error( + 'Coin transfer detected in unsupported message type', + tx.hash, + tx.messages[event.tx_msg_index].content + ); + return; + } + + const ctTemplate = { + block_height: tx.height, + tx_id: tx.id, + tx_msg_id: tx.messages[event.tx_msg_index].id, + from: event.attributes.find((attr) => attr.key === 'sender')?.value, + to: '', + amount: 0, + denom: '', + timestamp: new Date(tx.timestamp).toISOString(), + }; + /** + * we expect 2 cases: + * 1. transfer event has only 1 sender and 1 recipient + * then the event will have 3 attributes: sender, recipient, amount + * 2. transfer event has 1 sender and multiple recipients, message must be 'MsgMultiSend' + * then the event will be an array of attributes: recipient1, amount1, recipient2, amount2, ... + * sender is the coin_spent.spender + */ + if (event.attributes.length === 3) { + const rawAmount = event.attributes.find( + (attr) => attr.key === 'amount' + )?.value; + const [amount, denom] = this.extractAmount(rawAmount); + coinTransfers.push( + CoinTransfer.fromJson({ + ...ctTemplate, + from: event.attributes.find((attr) => attr.key === 'sender') + ?.value, + to: event.attributes.find((attr) => attr.key === 'recipient') + ?.value, + amount, + denom, + }) + ); + return; + } + + const coinSpentEvent = tx.events.find( + (e: Event) => + e.type === 'coin_spent' && e.tx_msg_index === event.tx_msg_index + ); + ctTemplate.from = coinSpentEvent?.attributes.find( + (attr: { key: string; value: string }) => attr.key === 'spender' + )?.value; + for (let i = 0; i < event.attributes.length; i += 2) { if ( - event.attributes.length !== 3 && - tx.messages[event.tx_msg_index].type !== - '/cosmos.bank.v1beta1.MsgMultiSend' + event.attributes[i].key !== 'recipient' && + event.attributes[i + 1].key !== 'amount' ) { this.logger.error( - 'Coin transfer detected in unsupported message type', + 'Coin transfer in MsgMultiSend detected with invalid attributes', tx.hash, - tx.messages[event.tx_msg_index].content - ); - return; - } - - const ctTemplate = { - block_height: tx.height, - tx_id: tx.id, - tx_msg_id: tx.messages[event.tx_msg_index].id, - from: event.attributes.find((attr) => attr.key === 'sender')?.value, - to: '', - amount: 0, - denom: '', - timestamp: new Date(tx.timestamp).toISOString(), - }; - /** - * we expect 2 cases: - * 1. transfer event has only 1 sender and 1 recipient - * then the event will have 3 attributes: sender, recipient, amount - * 2. transfer event has 1 sender and multiple recipients, message must be 'MsgMultiSend' - * then the event will be an array of attributes: recipient1, amount1, recipient2, amount2, ... - * sender is the coin_spent.spender - */ - if (event.attributes.length === 3) { - const rawAmount = event.attributes.find( - (attr) => attr.key === 'amount' - )?.value; - const [amount, denom] = this.extractAmount(rawAmount); - coinTransfers.push( - CoinTransfer.fromJson({ - ...ctTemplate, - from: event.attributes.find((attr) => attr.key === 'sender') - ?.value, - to: event.attributes.find((attr) => attr.key === 'recipient') - ?.value, - amount, - denom, - }) + event.attributes ); return; } - const coinSpentEvent = tx.events.find( - (e: Event) => - e.type === 'coin_spent' && e.tx_msg_index === event.tx_msg_index + const rawAmount = event.attributes[i + 1].value; + const [amount, denom] = this.extractAmount(rawAmount); + coinTransfers.push( + CoinTransfer.fromJson({ + ...ctTemplate, + to: event.attributes[i].value, + amount, + denom, + }) ); - ctTemplate.from = coinSpentEvent?.attributes.find( - (attr: { key: string; value: string }) => attr.key === 'spender' - )?.value; - for (let i = 0; i < event.attributes.length; i += 2) { - if ( - event.attributes[i].key !== 'recipient' && - event.attributes[i + 1].key !== 'amount' - ) { - this.logger.error( - 'Coin transfer in MsgMultiSend detected with invalid attributes', - tx.hash, - event.attributes - ); - return; - } - - const rawAmount = event.attributes[i + 1].value; - const [amount, denom] = this.extractAmount(rawAmount); - coinTransfers.push( - CoinTransfer.fromJson({ - ...ctTemplate, - to: event.attributes[i].value, - amount, - denom, - }) - ); - } - }); - }); - - latestCoinTransferHeight = toBlock; - // eslint-disable-next-line no-await-in-loop,@typescript-eslint/no-loop-func - await knex.transaction(async (trx) => { - await BlockCheckpoint.query() - .transacting(trx) - .insert({ - height: latestCoinTransferHeight, - job_name: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, - }) - .onConflict('job_name') - .merge(); - - if (coinTransfers.length > 0) { - this.logger.info(`INSERTING ${coinTransfers.length} COIN TRANSFER`); - await CoinTransfer.query().transacting(trx).insert(coinTransfers); } }); - } + }); + + latestCoinTransferHeight = toBlock; + await knex.transaction(async (trx) => { + await BlockCheckpoint.query() + .transacting(trx) + .insert({ + height: latestCoinTransferHeight, + job_name: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, + }) + .onConflict('job_name') + .merge(); + + if (coinTransfers.length > 0) { + this.logger.info(`INSERTING ${coinTransfers.length} COIN TRANSFER`); + await CoinTransfer.query().transacting(trx).insert(coinTransfers); + } + }); } public async _start() { @@ -230,7 +230,7 @@ export default class CrawlTxService extends BullableService { count: 3, }, repeat: { - every: config.handleTransaction.millisecondCrawl, + every: config.handleCoinTransfer.millisecondCrawl, }, } ); From e11fc026d7c59dbb361cdb2d26f19b4f5c69f5d9 Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Wed, 15 Nov 2023 11:03:49 +0700 Subject: [PATCH 03/10] feat: add config for job coin transfer --- config.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.json b/config.json index 92dd251c0..679a0bbe5 100644 --- a/config.json +++ b/config.json @@ -28,6 +28,11 @@ "numberOfBlockPerCall": 100, "startBlock": 4860000 }, + "handleCoinTransfer": { + "key": "handleCoinTransfer", + "blocksPerCall": 100, + "millisecondCrawl": 3000 + }, "handleTransaction": { "key": "handleTransaction", "blocksPerCall": 100, From d07bfba87beb623cbcf48d1eb41ad7638ae5572d Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Wed, 15 Nov 2023 11:08:32 +0700 Subject: [PATCH 04/10] fix: revert crawl tx to old logic --- src/services/crawl-tx/crawl_tx.service.ts | 117 +--------------------- 1 file changed, 4 insertions(+), 113 deletions(-) diff --git a/src/services/crawl-tx/crawl_tx.service.ts b/src/services/crawl-tx/crawl_tx.service.ts index 54925ed19..22a91261f 100644 --- a/src/services/crawl-tx/crawl_tx.service.ts +++ b/src/services/crawl-tx/crawl_tx.service.ts @@ -20,13 +20,7 @@ import { getLcdClient, SERVICE, } from '../../common'; -import { - Block, - BlockCheckpoint, - CoinTransfer, - Event, - Transaction, -} from '../../models'; +import { Block, BlockCheckpoint, Event, Transaction } from '../../models'; import BullableService, { QueueHandler } from '../../base/bullable.service'; import config from '../../../config.json' assert { type: 'json' }; import knex from '../../common/utils/db_connection'; @@ -358,113 +352,10 @@ export default class CrawlTxService extends BullableService { }); }); - const transactions = await Transaction.query() + const resultInsertGraph = await Transaction.query() .insertGraph(listTxModel, { allowRefs: true }) - .transacting(transactionDB) - .returning('*'); - this.logger.debug('result insert tx', transactions); - - // TODO: make this a separate job - const coinTransfers: CoinTransfer[] = []; - transactions.forEach((tx: Transaction) => { - tx.events.forEach((event: Event) => { - if (event.type === 'transfer' && event.tx_msg_index) { - const ctTemplate = { - block_height: tx.height, - tx_id: tx.id, - tx_msg_id: tx.messages[event.tx_msg_index].id, - from: event.attributes.find((attr) => attr.key === 'sender')?.value, - to: '', - amount: 0, - denom: '', - timestamp: tx.timestamp, - }; - - // split amount to amount and denom using regex - // example: 10000uaura - // amount = 10000 - // denom = uaura - // return [0, ''] if invalid - const extractAmount = ( - rawAmount: string | undefined - ): [number, string] => { - const amount = rawAmount?.match(/(\d+)/)?.[0] ?? '0'; - const denom = rawAmount?.replace(amount, '') ?? ''; - return [Number.parseInt(amount, 10), denom]; - }; - - // we expect 2 cases: - // 1. transfer event has only 1 sender and 1 recipient - // then the event will has 3 attributes: sender, recipient, amount - // 2. transfer event has 1 sender and multiple recipients, message must be 'MsgMultiSend' - // then the event will be an array of attributes: recipient1, amount1, recipient2, amount2, ... - // sender is the coin_spent.spender - - if (event.attributes.length === 3) { - const rawAmount = event.attributes.find( - (attr) => attr.key === 'amount' - )?.value; - const [amount, denom] = extractAmount(rawAmount); - coinTransfers.push( - CoinTransfer.fromJson({ - ...ctTemplate, - from: event.attributes.find((attr) => attr.key === 'sender') - ?.value, - to: event.attributes.find((attr) => attr.key === 'recipient') - ?.value, - amount, - denom, - }) - ); - } else { - // skip if message is not 'MsgMultiSend' - if ( - tx.messages[event.tx_msg_index].type !== - '/cosmos.bank.v1beta1.MsgMultiSend' - ) { - this.logger.error( - 'Coin transfer detected in unsupported message type', - tx.hash, - tx.messages[event.tx_msg_index].content - ); - return; - } - const coinSpentEvent = tx.events.find( - (e: Event) => - e.type === 'coin_spent' && e.tx_msg_index === event.tx_msg_index - ); - ctTemplate.from = coinSpentEvent?.attributes.find( - (attr: { key: string; value: string }) => attr.key === 'spender' - )?.value; - for (let i = 0; i < event.attributes.length; i += 2) { - if ( - event.attributes[i].key !== 'recipient' && - event.attributes[i + 1].key !== 'amount' - ) { - this.logger.error( - 'Coin transfer in MsgMultiSend detected with invalid attributes', - tx.hash, - event.attributes - ); - return; - } - - const rawAmount = event.attributes[i + 1].value; - const [amount, denom] = extractAmount(rawAmount); - coinTransfers.push( - CoinTransfer.fromJson({ - ...ctTemplate, - to: event.attributes[i].value, - amount, - denom, - }) - ); - } - } - } - }); - }); - await CoinTransfer.query().insert(coinTransfers).transacting(transactionDB); + .transacting(transactionDB); + this.logger.debug('result insert tx', resultInsertGraph); } public mappingFlatEventToLog( From 7591a87d6b8a0b41e52441d5f2e1a8d0a6494989 Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Thu, 16 Nov 2023 16:48:26 +0700 Subject: [PATCH 05/10] feat: test unit and fix bug for coin transfer --- src/common/constant.ts | 4 ++ src/models/block_checkpoint.ts | 8 +++ .../crawl-tx/coin_transfer.service.ts | 66 ++++--------------- 3 files changed, 26 insertions(+), 52 deletions(-) diff --git a/src/common/constant.ts b/src/common/constant.ts index eb8f284a4..c167b2937 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -157,6 +157,10 @@ export const SERVICE = { path: 'v1.CrawlTransactionService.TriggerHandleTxJob', }, }, + CoinTransfer: { + key: 'CoinTransferService', + name: 'v1.CoinTransferService', + }, CrawlGenesisService: { key: 'CrawlGenesisService', name: 'v1.CrawlGenesisService', diff --git a/src/models/block_checkpoint.ts b/src/models/block_checkpoint.ts index 852198c38..95027fc41 100644 --- a/src/models/block_checkpoint.ts +++ b/src/models/block_checkpoint.ts @@ -21,6 +21,14 @@ export class BlockCheckpoint extends BaseModel { }; } + /** + * @description Get or create a check point for job and step run (from, to) + * @param jobName Your job name want to run + * @param lastHeightJobNames Another one or more job that your job depending on. So if your job want to process + * block A, it needs to wait util those jobs process success block A before your job + * @param configName property of config (import config from '../../../config.json' assert { type: 'json' };). + * it used to set step call via blocksPerCall in config + */ static async getCheckpoint( jobName: string, lastHeightJobNames: string[], diff --git a/src/services/crawl-tx/coin_transfer.service.ts b/src/services/crawl-tx/coin_transfer.service.ts index 5efb090ba..1bf1ab7ec 100644 --- a/src/services/crawl-tx/coin_transfer.service.ts +++ b/src/services/crawl-tx/coin_transfer.service.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-extraneous-dependencies */ import { ServiceBroker } from 'moleculer'; import { Service } from '@ourparentcenter/moleculer-decorators-extended'; import { BULL_JOB_NAME, SERVICE } from '../../common'; @@ -13,42 +12,14 @@ import config from '../../../config.json' assert { type: 'json' }; import knex from '../../common/utils/db_connection'; @Service({ - name: SERVICE.V1.CrawlTransaction.key, + name: SERVICE.V1.CoinTransfer.key, version: 1, }) -export default class CrawlTxService extends BullableService { +export default class CoinTransferService extends BullableService { public constructor(public broker: ServiceBroker) { super(broker); } - /** - * @description Get latest coin transfer to get latest height, otherwise get height from the oldest transaction crawled - * @private - */ - private async getLatestCoinTransferHeight(): Promise { - const blockCheckpointCT = await BlockCheckpoint.query() - .where('job_name', BULL_JOB_NAME.HANDLE_COIN_TRANSFER) - .first(); - - if (!blockCheckpointCT) { - const oldestTransaction = await Transaction.query() - .orderBy('height', 'ASC') - .first(); - const latestBlockHeight = oldestTransaction - ? oldestTransaction.height - : 0; - await BlockCheckpoint.query() - .insert({ - height: latestBlockHeight, - job_name: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, - }) - .onConflict('job_name') - .merge(); - return latestBlockHeight; - } - return blockCheckpointCT.height; - } - /** * @description Get transaction data for insert coin transfer * @param fromHeight @@ -62,7 +33,7 @@ export default class CrawlTxService extends BullableService { return Transaction.query() .withGraphFetched('events.[attributes]') .modifyGraph('events', (builder) => { - builder.andWhere('type', '=', 'transfer').whereNotNull('tx_msg_index'); + builder.whereNotNull('tx_msg_index'); }) .withGraphFetched('messages') .where('transaction.height', '>', fromHeight) @@ -86,25 +57,19 @@ export default class CrawlTxService extends BullableService { queueName: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, jobName: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, }) - public async jobHandlerCrawlTx() { - const transactionCheckPoint = await BlockCheckpoint.query() - .where('job_name', BULL_JOB_NAME.HANDLE_TRANSACTION) - .first(); - let latestCoinTransferHeight = await this.getLatestCoinTransferHeight(); - - if ( - !transactionCheckPoint || - latestCoinTransferHeight >= transactionCheckPoint.height - ) { + public async jobHandleTxCoinTransfer() { + const [fromBlock, toBlock, updateBlockCheckpoint] = + await BlockCheckpoint.getCheckpoint( + BULL_JOB_NAME.HANDLE_COIN_TRANSFER, + [BULL_JOB_NAME.HANDLE_TRANSACTION], + 'handleCoinTransfer' + ); + + if (fromBlock >= toBlock) { this.logger.info('Waiting for new transaction crawled'); return; } - const fromBlock = latestCoinTransferHeight; - const toBlock = Math.min( - fromBlock + config.handleCoinTransfer.blocksPerCall, - transactionCheckPoint.height - ); this.logger.info(`QUERY FROM ${fromBlock} - TO ${toBlock}................`); const coinTransfers: CoinTransfer[] = []; @@ -201,14 +166,11 @@ export default class CrawlTxService extends BullableService { }); }); - latestCoinTransferHeight = toBlock; + updateBlockCheckpoint.height = toBlock; await knex.transaction(async (trx) => { await BlockCheckpoint.query() .transacting(trx) - .insert({ - height: latestCoinTransferHeight, - job_name: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, - }) + .insert(updateBlockCheckpoint) .onConflict('job_name') .merge(); From dda4e10f983dbc1507b501e758fb403faaf10128 Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Thu, 16 Nov 2023 16:49:20 +0700 Subject: [PATCH 06/10] feat: test unit and fix bug for coin transfer --- .../crawl-transaction/coin_transfer.spec.ts | 188 +++ .../multiple_tx_coin_transfer.json | 1172 +++++++++++++++++ .../single_tx_coin_transfer.json | 312 +++++ 3 files changed, 1672 insertions(+) create mode 100644 test/unit/services/crawl-transaction/coin_transfer.spec.ts create mode 100644 test/unit/services/crawl-transaction/multiple_tx_coin_transfer.json create mode 100644 test/unit/services/crawl-transaction/single_tx_coin_transfer.json diff --git a/test/unit/services/crawl-transaction/coin_transfer.spec.ts b/test/unit/services/crawl-transaction/coin_transfer.spec.ts new file mode 100644 index 000000000..ea21faace --- /dev/null +++ b/test/unit/services/crawl-transaction/coin_transfer.spec.ts @@ -0,0 +1,188 @@ +import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; +import { ServiceBroker } from 'moleculer'; +import { Block, BlockCheckpoint, CoinTransfer } from '../../../../src/models'; +import { BULL_JOB_NAME } from '../../../../src/common'; +import knex from '../../../../src/common/utils/db_connection'; +import CoinTransferService from '../../../../src/services/crawl-tx/coin_transfer.service'; +import CrawlTxService from '../../../../src/services/crawl-tx/crawl_tx.service'; +import single_tx_coin_transfer from './single_tx_coin_transfer.json' assert { type: 'json' }; +import multiple_tx_coin_transfer from './multiple_tx_coin_transfer.json' assert { type: 'json' }; + +@Describe('Test coin transfer') +export default class CoinTransferSpec { + broker = new ServiceBroker({ logger: false }); + + coinTransferService?: CoinTransferService; + + crawlTxService?: CrawlTxService; + + private status = { + test: 1, + stop: 2, + }; + + private async clearData(): Promise { + await Promise.all([ + knex.raw('TRUNCATE TABLE coin_transfer RESTART IDENTITY CASCADE'), + knex.raw('TRUNCATE TABLE block RESTART IDENTITY CASCADE'), + knex.raw('TRUNCATE TABLE block_checkpoint RESTART IDENTITY CASCADE'), + knex.raw('TRUNCATE TABLE transaction RESTART IDENTITY CASCADE'), + ]); + } + + private async prepareService(status: number): Promise { + if (status === this.status.stop) { + await Promise.all([ + this.crawlTxService?.getQueueManager().stopAll(), + this.coinTransferService?.getQueueManager().stopAll(), + this.coinTransferService?._stop(), + this.crawlTxService?._stop(), + this.broker.stop(), + ]); + } + + if (status === this.status.test) { + await this.broker.start(); + this.coinTransferService = this.broker.createService( + CoinTransferService + ) as CoinTransferService; + this.crawlTxService = this.broker.createService( + CrawlTxService + ) as CrawlTxService; + await this.crawlTxService._start(); + } + } + + private async insertDataForTest(txHeight: number, tx: any): Promise { + // Insert job checkpoint + await BlockCheckpoint.query().insert([ + BlockCheckpoint.fromJson({ + job_name: BULL_JOB_NAME.HANDLE_COIN_TRANSFER, + height: txHeight - 50, + }), + BlockCheckpoint.fromJson({ + job_name: BULL_JOB_NAME.HANDLE_TRANSACTION, + height: txHeight, + }), + ]); + + // Insert block for insert transaction + await Block.query().insert( + Block.fromJson({ + height: txHeight, + hash: 'data for test', + time: '2023-04-17T03:44:41.000Z', + proposer_address: 'proposer address', + data: {}, + }) + ); + + // Insert single coin transfer transaction and related + const listDecodedTx = await this.crawlTxService?.decodeListRawTx([ + { + listTx: { ...tx }, + height: txHeight, + timestamp: '2023-04-17T03:44:41.000Z', + }, + ]); + if (listDecodedTx) + await knex.transaction(async (trx) => { + await this.crawlTxService?.insertDecodedTxAndRelated( + listDecodedTx, + trx + ); + }); + } + + @BeforeAll() + async initSuite() { + await this.prepareService(this.status.stop); + await this.clearData(); + await this.prepareService(this.status.test); + } + + @Test('Test single coin transfer') + public async testSingleCoinTransfer() { + // Expectation data + const txHeight = 3652723; + const amount = '1050011'; + const receiver = 'aura15x4v36r6rl73nhn9h0954mwp42sawrc25f0rnx'; + const sender = 'aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx'; + + // Prepare data test and job to handle testing data + await this.insertDataForTest(txHeight, single_tx_coin_transfer); + await this.coinTransferService?.jobHandleTxCoinTransfer(); + + // Validate + const coinTransfer = await CoinTransfer.query().where( + 'block_height', + txHeight + ); + // Test determine single coin transfer + expect(coinTransfer.length).toEqual(1); + // Test value + expect(coinTransfer[0].from).toEqual(sender); + expect(coinTransfer[0].to).toEqual(receiver); + expect(coinTransfer[0].amount).toEqual(amount); + } + + @Test('Test multi coin transfer') + public async testMultiCoinTransfer() { + // Expectation data + const txHeight = 3657660; + const amounts = [ + '6279579', + '9419368', + '3601633', + '2330468', + '1412405', + '1412405', + '2683570', + '706202', + '706202', + '706202', + '282481', + '282481', + ]; + const sender = 'aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr'; + const receivers = [ + 'aura1ewn73qp0aqrtya38p0nv5c2xsshdea7a7u3jzn', + 'aura1c03jkal0xplar2p7ndshxeqrh9kw4m6unncwsh', + 'aura1ytem9skzmq7n7tlfcqjw6wqfgd587cn3q80hxq', + 'aura10f4hqk4svs0ry0twd0fc2swf2yqcpqn0x72zs4', + 'aura12tdulmvjmsmpaqrpshz0emu0h9sqz5x5lj0qx8', + 'aura1ate5nqvum46uk2k3ta27e7yr3fgskqllnjwnm0', + 'aura1f6s4550dzyu0yzp7q2acn47mp5u25k0xzvypc6', + 'aura16n5we9kd3ewdp8ll0fgk8h2qnjzwtwz9e56vxs', + 'aura1c03jkal0xplar2p7ndshxeqrh9kw4m6unncwsh', + 'aura1mxpyg8u68k6a8wdu3hs5whcpw9q285pcnlez66', + 'aura1jjvfnekyy78n7xcvhfdqymtmsyg0yzt4ejtmkq', + 'aura1r5h46t8crr7ur99tg9x483n3t8es5gwp0m733h', + ]; + + // Prepare data and run job + await this.clearData(); + await this.insertDataForTest(txHeight, multiple_tx_coin_transfer); + await this.coinTransferService?.jobHandleTxCoinTransfer(); + + // Validate + const coinTransfers = await CoinTransfer.query().where( + 'block_height', + txHeight + ); + + expect(coinTransfers.length).toEqual(12); + + for (let i = 0; i < coinTransfers.length; i += 1) { + expect(coinTransfers[i].from).toEqual(sender); + expect(coinTransfers[i].to).toEqual(receivers[i]); + expect(coinTransfers[i].amount).toEqual(amounts[i]); + } + } + + @AfterAll() + async tearDown() { + await this.clearData(); + await this.prepareService(this.status.stop); + } +} diff --git a/test/unit/services/crawl-transaction/multiple_tx_coin_transfer.json b/test/unit/services/crawl-transaction/multiple_tx_coin_transfer.json new file mode 100644 index 000000000..ae2054951 --- /dev/null +++ b/test/unit/services/crawl-transaction/multiple_tx_coin_transfer.json @@ -0,0 +1,1172 @@ +{ + "txs": [ + { + "hash": "BF507B5BA7630DA69E73B8CBF9153778EC247BEF40D3FEBE3F98A7CA353D7B06", + "height": "3657660", + "index": 0, + "tx_result": { + "code": 0, + "data": "Cj0KOy9jb3Ntb3MuZGlzdHJpYnV0aW9uLnYxYmV0YTEuTXNnV2l0aGRyYXdWYWxpZGF0b3JDb21taXNzaW9uCh4KHC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQKHgocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZAoeChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kCh4KHC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQKHgocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZAoeChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kCh4KHC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQKHgocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZAoeChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kCh4KHC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQKHgocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZAoeChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5k", + "log": "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"31401099uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx\"},{\"key\":\"amount\",\"value\":\"31401099uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission\"},{\"key\":\"sender\",\"value\":\"aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx\"},{\"key\":\"module\",\"value\":\"distribution\"},{\"key\":\"sender\",\"value\":\"auravaloper15pzl0s6ym85qx4yeq29rflp702wtx3dntle05a\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"sender\",\"value\":\"aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx\"},{\"key\":\"amount\",\"value\":\"31401099uaura\"}]},{\"type\":\"withdraw_commission\",\"attributes\":[{\"key\":\"amount\",\"value\":\"31401099uaura\"}]}]},{\"msg_index\":1,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura1ewn73qp0aqrtya38p0nv5c2xsshdea7a7u3jzn\"},{\"key\":\"amount\",\"value\":\"6279579uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"6279579uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura1ewn73qp0aqrtya38p0nv5c2xsshdea7a7u3jzn\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"6279579uaura\"}]}]},{\"msg_index\":2,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura1c03jkal0xplar2p7ndshxeqrh9kw4m6unncwsh\"},{\"key\":\"amount\",\"value\":\"9419368uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"9419368uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura1c03jkal0xplar2p7ndshxeqrh9kw4m6unncwsh\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"9419368uaura\"}]}]},{\"msg_index\":3,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura1ytem9skzmq7n7tlfcqjw6wqfgd587cn3q80hxq\"},{\"key\":\"amount\",\"value\":\"3601633uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"3601633uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura1ytem9skzmq7n7tlfcqjw6wqfgd587cn3q80hxq\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"3601633uaura\"}]}]},{\"msg_index\":4,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura10f4hqk4svs0ry0twd0fc2swf2yqcpqn0x72zs4\"},{\"key\":\"amount\",\"value\":\"2330468uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"2330468uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura10f4hqk4svs0ry0twd0fc2swf2yqcpqn0x72zs4\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"2330468uaura\"}]}]},{\"msg_index\":5,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura12tdulmvjmsmpaqrpshz0emu0h9sqz5x5lj0qx8\"},{\"key\":\"amount\",\"value\":\"1412405uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"1412405uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura12tdulmvjmsmpaqrpshz0emu0h9sqz5x5lj0qx8\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"1412405uaura\"}]}]},{\"msg_index\":6,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura1ate5nqvum46uk2k3ta27e7yr3fgskqllnjwnm0\"},{\"key\":\"amount\",\"value\":\"1412405uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"1412405uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura1ate5nqvum46uk2k3ta27e7yr3fgskqllnjwnm0\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"1412405uaura\"}]}]},{\"msg_index\":7,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura1f6s4550dzyu0yzp7q2acn47mp5u25k0xzvypc6\"},{\"key\":\"amount\",\"value\":\"2683570uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"2683570uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura1f6s4550dzyu0yzp7q2acn47mp5u25k0xzvypc6\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"2683570uaura\"}]}]},{\"msg_index\":8,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura16n5we9kd3ewdp8ll0fgk8h2qnjzwtwz9e56vxs\"},{\"key\":\"amount\",\"value\":\"706202uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"706202uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura16n5we9kd3ewdp8ll0fgk8h2qnjzwtwz9e56vxs\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"706202uaura\"}]}]},{\"msg_index\":9,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura1c03jkal0xplar2p7ndshxeqrh9kw4m6unncwsh\"},{\"key\":\"amount\",\"value\":\"706202uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"706202uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura1c03jkal0xplar2p7ndshxeqrh9kw4m6unncwsh\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"706202uaura\"}]}]},{\"msg_index\":10,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura1mxpyg8u68k6a8wdu3hs5whcpw9q285pcnlez66\"},{\"key\":\"amount\",\"value\":\"706202uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"706202uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura1mxpyg8u68k6a8wdu3hs5whcpw9q285pcnlez66\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"706202uaura\"}]}]},{\"msg_index\":11,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura1jjvfnekyy78n7xcvhfdqymtmsyg0yzt4ejtmkq\"},{\"key\":\"amount\",\"value\":\"282481uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"282481uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura1jjvfnekyy78n7xcvhfdqymtmsyg0yzt4ejtmkq\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"282481uaura\"}]}]},{\"msg_index\":12,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura1r5h46t8crr7ur99tg9x483n3t8es5gwp0m733h\"},{\"key\":\"amount\",\"value\":\"282481uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"282481uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura1r5h46t8crr7ur99tg9x483n3t8es5gwp0m733h\"},{\"key\":\"sender\",\"value\":\"aura15pzl0s6ym85qx4yeq29rflp702wtx3dnsdg8vr\"},{\"key\":\"amount\",\"value\":\"282481uaura\"}]}]}]", + "info": "", + "gas_wanted": "500000", + "gas_used": "259290", + "events": [ + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NTAwMHVhdXJh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTE3eHBmdmFrbTJhbWc5NjJ5bHM2Zjg0ejNrZWxsOGM1bHQwNXpmeQ==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NTAwMHVhdXJh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTE3eHBmdmFrbTJhbWc5NjJ5bHM2Zjg0ejNrZWxsOGM1bHQwNXpmeQ==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NTAwMHVhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": "NTAwMHVhdXJh", + "index": true + }, + { + "key": "ZmVlX3BheWVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2ci81Njc=", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "MlRNUWwzWTJ4ZDhQMFg2UzhJQTZESlFBcFZsZUtoU1BYNVduS3Z1N0lHWmw5RzVJcmlFVWhiT1hVK0F4Q0ZCQ1JrNEhGK0lDanY5cUVJOGJIeUZUZ1E9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd1ZhbGlkYXRvckNvbW1pc3Npb24=", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTFqdjY1czNncnFmNnY2amwzZHA0dDZjOXQ5cms5OWNkOHVmbjd0eA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzE0MDEwOTl1YXVyYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzE0MDEwOTl1YXVyYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTFqdjY1czNncnFmNnY2amwzZHA0dDZjOXQ5cms5OWNkOHVmbjd0eA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzE0MDEwOTl1YXVyYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTFqdjY1czNncnFmNnY2amwzZHA0dDZjOXQ5cms5OWNkOHVmbjd0eA==", + "index": true + } + ] + }, + { + "type": "withdraw_commission", + "attributes": [ + { + "key": "YW1vdW50", + "value": "MzE0MDEwOTl1YXVyYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "ZGlzdHJpYnV0aW9u", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYXZhbG9wZXIxNXB6bDBzNnltODVxeDR5ZXEyOXJmbHA3MDJ3dHgzZG50bGUwNWE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjI3OTU3OXVhdXJh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTFld243M3FwMGFxcnR5YTM4cDBudjVjMnhzc2hkZWE3YTd1M2p6bg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjI3OTU3OXVhdXJh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTFld243M3FwMGFxcnR5YTM4cDBudjVjMnhzc2hkZWE3YTd1M2p6bg==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjI3OTU3OXVhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "OTQxOTM2OHVhdXJh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTFjMDNqa2FsMHhwbGFyMnA3bmRzaHhlcXJoOWt3NG02dW5uY3dzaA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "OTQxOTM2OHVhdXJh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTFjMDNqa2FsMHhwbGFyMnA3bmRzaHhlcXJoOWt3NG02dW5uY3dzaA==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "OTQxOTM2OHVhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzYwMTYzM3VhdXJh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTF5dGVtOXNrem1xN243dGxmY3FqdzZ3cWZnZDU4N2NuM3E4MGh4cQ==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzYwMTYzM3VhdXJh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTF5dGVtOXNrem1xN243dGxmY3FqdzZ3cWZnZDU4N2NuM3E4MGh4cQ==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzYwMTYzM3VhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjMzMDQ2OHVhdXJh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTEwZjRocWs0c3ZzMHJ5MHR3ZDBmYzJzd2YyeXFjcHFuMHg3MnpzNA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjMzMDQ2OHVhdXJh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTEwZjRocWs0c3ZzMHJ5MHR3ZDBmYzJzd2YyeXFjcHFuMHg3MnpzNA==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjMzMDQ2OHVhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTQxMjQwNXVhdXJh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTEydGR1bG12am1zbXBhcXJwc2h6MGVtdTBoOXNxejV4NWxqMHF4OA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTQxMjQwNXVhdXJh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTEydGR1bG12am1zbXBhcXJwc2h6MGVtdTBoOXNxejV4NWxqMHF4OA==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTQxMjQwNXVhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTQxMjQwNXVhdXJh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTFhdGU1bnF2dW00NnVrMmszdGEyN2U3eXIzZmdza3FsbG5qd25tMA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTQxMjQwNXVhdXJh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTFhdGU1bnF2dW00NnVrMmszdGEyN2U3eXIzZmdza3FsbG5qd25tMA==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTQxMjQwNXVhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjY4MzU3MHVhdXJh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTFmNnM0NTUwZHp5dTB5enA3cTJhY240N21wNXUyNWsweHp2eXBjNg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjY4MzU3MHVhdXJh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTFmNnM0NTUwZHp5dTB5enA3cTJhY240N21wNXUyNWsweHp2eXBjNg==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjY4MzU3MHVhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NzA2MjAydWF1cmE=", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTE2bjV3ZTlrZDNld2RwOGxsMGZnazhoMnFuanp3dHd6OWU1NnZ4cw==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NzA2MjAydWF1cmE=", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTE2bjV3ZTlrZDNld2RwOGxsMGZnazhoMnFuanp3dHd6OWU1NnZ4cw==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NzA2MjAydWF1cmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NzA2MjAydWF1cmE=", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTFjMDNqa2FsMHhwbGFyMnA3bmRzaHhlcXJoOWt3NG02dW5uY3dzaA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NzA2MjAydWF1cmE=", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTFjMDNqa2FsMHhwbGFyMnA3bmRzaHhlcXJoOWt3NG02dW5uY3dzaA==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NzA2MjAydWF1cmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NzA2MjAydWF1cmE=", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTFteHB5Zzh1NjhrNmE4d2R1M2hzNXdoY3B3OXEyODVwY25sZXo2Ng==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NzA2MjAydWF1cmE=", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTFteHB5Zzh1NjhrNmE4d2R1M2hzNXdoY3B3OXEyODVwY25sZXo2Ng==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NzA2MjAydWF1cmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjgyNDgxdWF1cmE=", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTFqanZmbmVreXk3OG43eGN2aGZkcXltdG1zeWcweXp0NGVqdG1rcQ==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjgyNDgxdWF1cmE=", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTFqanZmbmVreXk3OG43eGN2aGZkcXltdG1zeWcweXp0NGVqdG1rcQ==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjgyNDgxdWF1cmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjgyNDgxdWF1cmE=", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTFyNWg0NnQ4Y3JyN3VyOTl0Zzl4NDgzbjN0OGVzNWd3cDBtNzMzaA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjgyNDgxdWF1cmE=", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTFyNWg0NnQ4Y3JyN3VyOTl0Zzl4NDgzbjN0OGVzNWd3cDBtNzMzaA==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MjgyNDgxdWF1cmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2cg==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + } + ], + "codespace": "" + }, + "tx": "CqQOCnMKOy9jb3Ntb3MuZGlzdHJpYnV0aW9uLnYxYmV0YTEuTXNnV2l0aGRyYXdWYWxpZGF0b3JDb21taXNzaW9uEjQKMmF1cmF2YWxvcGVyMTVwemwwczZ5bTg1cXg0eWVxMjlyZmxwNzAyd3R4M2RudGxlMDVhCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKK2F1cmExNXB6bDBzNnltODVxeDR5ZXEyOXJmbHA3MDJ3dHgzZG5zZGc4dnISK2F1cmExZXduNzNxcDBhcXJ0eWEzOHAwbnY1YzJ4c3NoZGVhN2E3dTNqem4aEAoFdWF1cmESBzYyNzk1NzkKjAEKHC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQSbAorYXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2chIrYXVyYTFjMDNqa2FsMHhwbGFyMnA3bmRzaHhlcXJoOWt3NG02dW5uY3dzaBoQCgV1YXVyYRIHOTQxOTM2OAqMAQocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBJsCithdXJhMTVwemwwczZ5bTg1cXg0eWVxMjlyZmxwNzAyd3R4M2Ruc2RnOHZyEithdXJhMXl0ZW05c2t6bXE3bjd0bGZjcWp3NndxZmdkNTg3Y24zcTgwaHhxGhAKBXVhdXJhEgczNjAxNjMzCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKK2F1cmExNXB6bDBzNnltODVxeDR5ZXEyOXJmbHA3MDJ3dHgzZG5zZGc4dnISK2F1cmExMGY0aHFrNHN2czByeTB0d2QwZmMyc3dmMnlxY3BxbjB4NzJ6czQaEAoFdWF1cmESBzIzMzA0NjgKjAEKHC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQSbAorYXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2chIrYXVyYTEydGR1bG12am1zbXBhcXJwc2h6MGVtdTBoOXNxejV4NWxqMHF4OBoQCgV1YXVyYRIHMTQxMjQwNQqMAQocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBJsCithdXJhMTVwemwwczZ5bTg1cXg0eWVxMjlyZmxwNzAyd3R4M2Ruc2RnOHZyEithdXJhMWF0ZTVucXZ1bTQ2dWsyazN0YTI3ZTd5cjNmZ3NrcWxsbmp3bm0wGhAKBXVhdXJhEgcxNDEyNDA1CowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKK2F1cmExNXB6bDBzNnltODVxeDR5ZXEyOXJmbHA3MDJ3dHgzZG5zZGc4dnISK2F1cmExZjZzNDU1MGR6eXUweXpwN3EyYWNuNDdtcDV1MjVrMHh6dnlwYzYaEAoFdWF1cmESBzI2ODM1NzAKiwEKHC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQSaworYXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2chIrYXVyYTE2bjV3ZTlrZDNld2RwOGxsMGZnazhoMnFuanp3dHd6OWU1NnZ4cxoPCgV1YXVyYRIGNzA2MjAyCosBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmsKK2F1cmExNXB6bDBzNnltODVxeDR5ZXEyOXJmbHA3MDJ3dHgzZG5zZGc4dnISK2F1cmExYzAzamthbDB4cGxhcjJwN25kc2h4ZXFyaDlrdzRtNnVubmN3c2gaDwoFdWF1cmESBjcwNjIwMgqLAQocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBJrCithdXJhMTVwemwwczZ5bTg1cXg0eWVxMjlyZmxwNzAyd3R4M2Ruc2RnOHZyEithdXJhMW14cHlnOHU2OGs2YTh3ZHUzaHM1d2hjcHc5cTI4NXBjbmxlejY2Gg8KBXVhdXJhEgY3MDYyMDIKiwEKHC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQSaworYXVyYTE1cHpsMHM2eW04NXF4NHllcTI5cmZscDcwMnd0eDNkbnNkZzh2chIrYXVyYTFqanZmbmVreXk3OG43eGN2aGZkcXltdG1zeWcweXp0NGVqdG1rcRoPCgV1YXVyYRIGMjgyNDgxCosBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmsKK2F1cmExNXB6bDBzNnltODVxeDR5ZXEyOXJmbHA3MDJ3dHgzZG5zZGc4dnISK2F1cmExcjVoNDZ0OGNycjd1cjk5dGc5eDQ4M24zdDhlczVnd3AwbTczM2gaDwoFdWF1cmESBjI4MjQ4MRJoClEKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECHiAzEritMJ4is826AVw8+Z+9DNPyUhMbeNTCrbwNzpkSBAoCCAEYtwQSEwoNCgV1YXVyYRIENTAwMBCgwh4aQNkzEJd2NsXfD9F+kvCAOgyUAKVZXioUj1+Vpyr7uyBmZfRuSK4hFIWzl1PgMQhQQkZOBxfiAo7/ahCPGx8hU4E=" + } + ], + "total_count": "1" +} diff --git a/test/unit/services/crawl-transaction/single_tx_coin_transfer.json b/test/unit/services/crawl-transaction/single_tx_coin_transfer.json new file mode 100644 index 000000000..47129959a --- /dev/null +++ b/test/unit/services/crawl-transaction/single_tx_coin_transfer.json @@ -0,0 +1,312 @@ +{ + "txs": [ + { + "hash": "891B88F4A6F300BDB4AD8A43CF81FAE40564BECBF5A33E7CF6D61EAF84331FD2", + "height": "3652723", + "index": 0, + "tx_result": { + "code": 0, + "data": "CjkKNy9jb3Ntb3MuZGlzdHJpYnV0aW9uLnYxYmV0YTEuTXNnV2l0aGRyYXdEZWxlZ2F0b3JSZXdhcmQKPQo7L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd1ZhbGlkYXRvckNvbW1pc3Npb24=", + "log": "[{\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura15x4v36r6rl73nhn9h0954mwp42sawrc25f0rnx\"},{\"key\":\"amount\",\"value\":\"61688uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx\"},{\"key\":\"amount\",\"value\":\"61688uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\"},{\"key\":\"sender\",\"value\":\"aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx\"},{\"key\":\"module\",\"value\":\"distribution\"},{\"key\":\"sender\",\"value\":\"aura15x4v36r6rl73nhn9h0954mwp42sawrc25f0rnx\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura15x4v36r6rl73nhn9h0954mwp42sawrc25f0rnx\"},{\"key\":\"sender\",\"value\":\"aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx\"},{\"key\":\"amount\",\"value\":\"61688uaura\"}]},{\"type\":\"withdraw_rewards\",\"attributes\":[{\"key\":\"amount\",\"value\":\"61688uaura\"},{\"key\":\"validator\",\"value\":\"auravaloper15x4v36r6rl73nhn9h0954mwp42sawrc20m7ttc\"}]}]},{\"msg_index\":1,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"aura15x4v36r6rl73nhn9h0954mwp42sawrc25f0rnx\"},{\"key\":\"amount\",\"value\":\"1050011uaura\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx\"},{\"key\":\"amount\",\"value\":\"1050011uaura\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission\"},{\"key\":\"sender\",\"value\":\"aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx\"},{\"key\":\"module\",\"value\":\"distribution\"},{\"key\":\"sender\",\"value\":\"auravaloper15x4v36r6rl73nhn9h0954mwp42sawrc20m7ttc\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"aura15x4v36r6rl73nhn9h0954mwp42sawrc25f0rnx\"},{\"key\":\"sender\",\"value\":\"aura1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8ufn7tx\"},{\"key\":\"amount\",\"value\":\"1050011uaura\"}]},{\"type\":\"withdraw_commission\",\"attributes\":[{\"key\":\"amount\",\"value\":\"1050011uaura\"}]}]}]", + "info": "", + "gas_wanted": "219582", + "gas_used": "147857", + "events": [ + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzAwdWF1cmE=", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTE3eHBmdmFrbTJhbWc5NjJ5bHM2Zjg0ejNrZWxsOGM1bHQwNXpmeQ==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzAwdWF1cmE=", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTE3eHBmdmFrbTJhbWc5NjJ5bHM2Zjg0ejNrZWxsOGM1bHQwNXpmeQ==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzAwdWF1cmE=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": "MzAwdWF1cmE=", + "index": true + }, + { + "key": "ZmVlX3BheWVy", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueC80MTAzMA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "bkI0MEVVNHpMa0ltUlExeDQwZnBuc3k3Yi9KYnl5dlp6RGszOHp0WEVSWlQzM29ybWFzdWVDaGNFajB6UTVYOVlGVUdCRWdVaHBHeDJFYzZLdzhTR2c9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd0RlbGVnYXRvclJld2FyZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTFqdjY1czNncnFmNnY2amwzZHA0dDZjOXQ5cms5OWNkOHVmbjd0eA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjE2ODh1YXVyYQ==", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjE2ODh1YXVyYQ==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueA==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTFqdjY1czNncnFmNnY2amwzZHA0dDZjOXQ5cms5OWNkOHVmbjd0eA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "NjE2ODh1YXVyYQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTFqdjY1czNncnFmNnY2amwzZHA0dDZjOXQ5cms5OWNkOHVmbjd0eA==", + "index": true + } + ] + }, + { + "type": "withdraw_rewards", + "attributes": [ + { + "key": "YW1vdW50", + "value": "NjE2ODh1YXVyYQ==", + "index": true + }, + { + "key": "dmFsaWRhdG9y", + "value": "YXVyYXZhbG9wZXIxNXg0djM2cjZybDczbmhuOWgwOTU0bXdwNDJzYXdyYzIwbTd0dGM=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "ZGlzdHJpYnV0aW9u", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueA==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5kaXN0cmlidXRpb24udjFiZXRhMS5Nc2dXaXRoZHJhd1ZhbGlkYXRvckNvbW1pc3Npb24=", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "YXVyYTFqdjY1czNncnFmNnY2amwzZHA0dDZjOXQ5cms5OWNkOHVmbjd0eA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTA1MDAxMXVhdXJh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTA1MDAxMXVhdXJh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "YXVyYTE1eDR2MzZyNnJsNzNuaG45aDA5NTRtd3A0MnNhd3JjMjVmMHJueA==", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYTFqdjY1czNncnFmNnY2amwzZHA0dDZjOXQ5cms5OWNkOHVmbjd0eA==", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MTA1MDAxMXVhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "YXVyYTFqdjY1czNncnFmNnY2amwzZHA0dDZjOXQ5cms5OWNkOHVmbjd0eA==", + "index": true + } + ] + }, + { + "type": "withdraw_commission", + "attributes": [ + { + "key": "YW1vdW50", + "value": "MTA1MDAxMXVhdXJh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "ZGlzdHJpYnV0aW9u", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "YXVyYXZhbG9wZXIxNXg0djM2cjZybDczbmhuOWgwOTU0bXdwNDJzYXdyYzIwbTd0dGM=", + "index": true + } + ] + } + ], + "codespace": "" + }, + "tx": "CpQCCpwBCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmEKK2F1cmExNXg0djM2cjZybDczbmhuOWgwOTU0bXdwNDJzYXdyYzI1ZjBybngSMmF1cmF2YWxvcGVyMTV4NHYzNnI2cmw3M25objloMDk1NG13cDQyc2F3cmMyMG03dHRjCnMKOy9jb3Ntb3MuZGlzdHJpYnV0aW9uLnYxYmV0YTEuTXNnV2l0aGRyYXdWYWxpZGF0b3JDb21taXNzaW9uEjQKMmF1cmF2YWxvcGVyMTV4NHYzNnI2cmw3M25objloMDk1NG13cDQyc2F3cmMyMG03dHRjEmgKUgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQKFQf/rZWzHiBM+NhWgcO36pzLYuHFTW9BrLsxT5d2HnBIECgIIARjGwAISEgoMCgV1YXVyYRIDMzAwEL6zDRpAnB40EU4zLkImRQ1x40fpnsy7b/JbyyvZzDk38ztXERZT33ormasueChcEj0zQ5X9YFUGBEgUhpGx2Ec6Kw8SGg==" + } + ], + "total_count": "1" +} From 6e8746edd7e7fc521314a4e6ab12f47e4a399825 Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Fri, 17 Nov 2023 10:55:58 +0700 Subject: [PATCH 07/10] feat: test unit and fix bug for coin transfer --- .../crawl-transaction/coin_transfer.spec.ts | 73 +++++++------------ 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/test/unit/services/crawl-transaction/coin_transfer.spec.ts b/test/unit/services/crawl-transaction/coin_transfer.spec.ts index ea21faace..d13d2a13f 100644 --- a/test/unit/services/crawl-transaction/coin_transfer.spec.ts +++ b/test/unit/services/crawl-transaction/coin_transfer.spec.ts @@ -1,4 +1,4 @@ -import { AfterAll, BeforeAll, Describe, Test } from '@jest-decorated/core'; +import { AfterAll, BeforeEach, Describe, Test } from '@jest-decorated/core'; import { ServiceBroker } from 'moleculer'; import { Block, BlockCheckpoint, CoinTransfer } from '../../../../src/models'; import { BULL_JOB_NAME } from '../../../../src/common'; @@ -7,6 +7,7 @@ import CoinTransferService from '../../../../src/services/crawl-tx/coin_transfer import CrawlTxService from '../../../../src/services/crawl-tx/crawl_tx.service'; import single_tx_coin_transfer from './single_tx_coin_transfer.json' assert { type: 'json' }; import multiple_tx_coin_transfer from './multiple_tx_coin_transfer.json' assert { type: 'json' }; +import AuraRegistry from '../../../../src/services/crawl-tx/aura.registry'; @Describe('Test coin transfer') export default class CoinTransferSpec { @@ -16,43 +17,6 @@ export default class CoinTransferSpec { crawlTxService?: CrawlTxService; - private status = { - test: 1, - stop: 2, - }; - - private async clearData(): Promise { - await Promise.all([ - knex.raw('TRUNCATE TABLE coin_transfer RESTART IDENTITY CASCADE'), - knex.raw('TRUNCATE TABLE block RESTART IDENTITY CASCADE'), - knex.raw('TRUNCATE TABLE block_checkpoint RESTART IDENTITY CASCADE'), - knex.raw('TRUNCATE TABLE transaction RESTART IDENTITY CASCADE'), - ]); - } - - private async prepareService(status: number): Promise { - if (status === this.status.stop) { - await Promise.all([ - this.crawlTxService?.getQueueManager().stopAll(), - this.coinTransferService?.getQueueManager().stopAll(), - this.coinTransferService?._stop(), - this.crawlTxService?._stop(), - this.broker.stop(), - ]); - } - - if (status === this.status.test) { - await this.broker.start(); - this.coinTransferService = this.broker.createService( - CoinTransferService - ) as CoinTransferService; - this.crawlTxService = this.broker.createService( - CrawlTxService - ) as CrawlTxService; - await this.crawlTxService._start(); - } - } - private async insertDataForTest(txHeight: number, tx: any): Promise { // Insert job checkpoint await BlockCheckpoint.query().insert([ @@ -94,11 +58,23 @@ export default class CoinTransferSpec { }); } - @BeforeAll() + @BeforeEach() async initSuite() { - await this.prepareService(this.status.stop); - await this.clearData(); - await this.prepareService(this.status.test); + this.coinTransferService = this.broker.createService( + CoinTransferService + ) as CoinTransferService; + this.crawlTxService = this.broker.createService( + CrawlTxService + ) as CrawlTxService; + this.crawlTxService?.getQueueManager().stopAll(); + await Promise.all([ + knex.raw('TRUNCATE TABLE coin_transfer RESTART IDENTITY CASCADE'), + knex.raw('TRUNCATE TABLE block RESTART IDENTITY CASCADE'), + knex.raw('TRUNCATE TABLE block_checkpoint RESTART IDENTITY CASCADE'), + ]); + const auraRegistry = new AuraRegistry(this.crawlTxService.logger); + auraRegistry.setCosmosSdkVersionByString('v0.45.7'); + this.crawlTxService.setRegistry(auraRegistry); } @Test('Test single coin transfer') @@ -161,7 +137,6 @@ export default class CoinTransferSpec { ]; // Prepare data and run job - await this.clearData(); await this.insertDataForTest(txHeight, multiple_tx_coin_transfer); await this.coinTransferService?.jobHandleTxCoinTransfer(); @@ -182,7 +157,15 @@ export default class CoinTransferSpec { @AfterAll() async tearDown() { - await this.clearData(); - await this.prepareService(this.status.stop); + this.crawlTxService?.getQueueManager().stopAll(); + this.coinTransferService?.getQueueManager().stopAll(); + await Promise.all([ + knex.raw('TRUNCATE TABLE coin_transfer RESTART IDENTITY CASCADE'), + knex.raw('TRUNCATE TABLE block RESTART IDENTITY CASCADE'), + knex.raw('TRUNCATE TABLE block_checkpoint RESTART IDENTITY CASCADE'), + this.crawlTxService?._stop(), + this.coinTransferService?._stop(), + this.broker.stop(), + ]); } } From 1d47c279efef3ae4c5a2b8fe49896b876e763de2 Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Fri, 17 Nov 2023 14:02:09 +0700 Subject: [PATCH 08/10] feat: test unit and fix bug for coin transfer --- ci/config.json.ci | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ci/config.json.ci b/ci/config.json.ci index a3939017a..053aaeffb 100644 --- a/ci/config.json.ci +++ b/ci/config.json.ci @@ -27,7 +27,12 @@ "millisecondCrawl": 5000, "numberOfBlockPerCall": 100, "startBlock": 4860000 - }, + }, + "handleCoinTransfer": { + "key": "handleCoinTransfer", + "blocksPerCall": 100, + "millisecondCrawl": 3000 + }, "handleTransaction": { "key": "handleTransaction", "blocksPerCall": 100, From 3bd20a142b9c3989f8b42250caf9a0037ce184ad Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Fri, 17 Nov 2023 15:14:30 +0700 Subject: [PATCH 09/10] fix: refactor query coin transfer --- .../crawl-tx/coin_transfer.service.ts | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/services/crawl-tx/coin_transfer.service.ts b/src/services/crawl-tx/coin_transfer.service.ts index 1bf1ab7ec..c7b7b7234 100644 --- a/src/services/crawl-tx/coin_transfer.service.ts +++ b/src/services/crawl-tx/coin_transfer.service.ts @@ -30,14 +30,32 @@ export default class CoinTransferService extends BullableService { fromHeight: number, toHeight: number ): Promise { - return Transaction.query() - .withGraphFetched('events.[attributes]') - .modifyGraph('events', (builder) => { - builder.whereNotNull('tx_msg_index'); - }) + const transactions = await Transaction.query() .withGraphFetched('messages') - .where('transaction.height', '>', fromHeight) - .andWhere('transaction.height', '<=', toHeight); + .where('height', '>', fromHeight) + .andWhere('height', '<=', toHeight); + if (transactions.length === 0) return []; + + const transactionsWithId: any = []; + transactions.forEach((transaction) => { + transactionsWithId[transaction.id] = { + ...transaction, + events: [], + }; + }); + + const minTransactionId = transactions[0].id; + const maxTransactionId = transactions[transactions.length - 1].id; + const events = await Event.query() + .withGraphFetched('attributes') + .where('tx_id', '>=', minTransactionId) + .andWhere('tx_id', '<=', maxTransactionId) + .whereNotNull('tx_msg_index'); + events.forEach((event) => { + transactionsWithId[event.tx_id].events.push(event); + }); + + return transactionsWithId; } /** From 3f65908a1b28afb47031082666632e5a0512c780 Mon Sep 17 00:00:00 2001 From: "matthew.nguyen.20.03.2023" Date: Fri, 17 Nov 2023 15:26:52 +0700 Subject: [PATCH 10/10] fix: refactor query coin transfer --- src/services/crawl-tx/coin_transfer.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/crawl-tx/coin_transfer.service.ts b/src/services/crawl-tx/coin_transfer.service.ts index c7b7b7234..3f5b275c6 100644 --- a/src/services/crawl-tx/coin_transfer.service.ts +++ b/src/services/crawl-tx/coin_transfer.service.ts @@ -33,7 +33,8 @@ export default class CoinTransferService extends BullableService { const transactions = await Transaction.query() .withGraphFetched('messages') .where('height', '>', fromHeight) - .andWhere('height', '<=', toHeight); + .andWhere('height', '<=', toHeight) + .orderBy('id', 'ASC'); if (transactions.length === 0) return []; const transactionsWithId: any = [];