From 2ba4f530982849561b06e03d789c8bf8bc3646f5 Mon Sep 17 00:00:00 2001 From: Vlad Bucur <83655219+vladbucur1@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:06:33 +0300 Subject: [PATCH] Improvements for scheduled miniblocks and getShards call (#25) * Improvements for scheduled miniblocks and getShards call * code review fixes * shardIds as instance variable instead of static --------- Co-authored-by: tanghel --- .../crons/transaction.processor.service.ts | 2 +- package-lock.json | 4 +- package.json | 2 +- src/shards-maintainer.service.ts | 34 +++ src/transaction.processor.ts | 236 +++++------------- src/types/cross-shard-transaction.ts | 11 + src/types/gateway/block-response.ts | 5 + src/types/gateway/block.ts | 25 ++ .../gateway/miniblock-processing-type.enum.ts | 5 + src/types/gateway/miniblock.ts | 15 ++ src/types/gateway/transaction.ts | 66 +++++ src/types/log-topic.ts | 5 + src/types/options.ts | 19 ++ src/types/shard-transaction.ts | 72 ++++++ src/types/transaction-processor-mode.enum.ts | 4 + src/types/transaction-statistics.ts | 7 + src/utils/constants.ts | 2 + src/utils/decoders.ts | 3 + src/utils/http.service.ts | 31 +++ src/utils/utils.ts | 9 + 20 files changed, 380 insertions(+), 177 deletions(-) create mode 100644 src/shards-maintainer.service.ts create mode 100644 src/types/cross-shard-transaction.ts create mode 100644 src/types/gateway/block-response.ts create mode 100644 src/types/gateway/block.ts create mode 100644 src/types/gateway/miniblock-processing-type.enum.ts create mode 100644 src/types/gateway/miniblock.ts create mode 100644 src/types/gateway/transaction.ts create mode 100644 src/types/log-topic.ts create mode 100644 src/types/options.ts create mode 100644 src/types/shard-transaction.ts create mode 100644 src/types/transaction-processor-mode.enum.ts create mode 100644 src/types/transaction-statistics.ts create mode 100644 src/utils/constants.ts create mode 100644 src/utils/decoders.ts create mode 100644 src/utils/http.service.ts create mode 100644 src/utils/utils.ts diff --git a/example/crons/transaction.processor.service.ts b/example/crons/transaction.processor.service.ts index 92a249e..1806876 100644 --- a/example/crons/transaction.processor.service.ts +++ b/example/crons/transaction.processor.service.ts @@ -18,7 +18,7 @@ export class TransactionProcessorService { async handleNewMultiversxTransactions() { Locker.lock('newMultiversxTransactions', async () => { await this.transactionProcessor.start({ - mode: TransactionProcessorMode.Hyperblock, + mode: TransactionProcessorMode.Shardblock, gatewayUrl: 'https://gateway.multiversx.com', // mainnet getLastProcessedNonce: async (_shardId: number, _currentNonce: number) => { // In ProcessByHyperblockTransactions shardId will always be METACHAIN diff --git a/package-lock.json b/package-lock.json index f06e69a..5ab36fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-transaction-processor", - "version": "0.1.34", + "version": "0.1.35", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@multiversx/sdk-transaction-processor", - "version": "0.1.34", + "version": "0.1.35", "license": "GPL-3.0-or-later", "dependencies": { "axios": "^1.7.4" diff --git a/package.json b/package.json index b6756d2..a1ee899 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-transaction-processor", - "version": "0.1.34", + "version": "0.1.35", "description": "Real-time transaction processor", "main": "lib/transaction.processor.js", "types": "lib/transaction.processor.d.ts", diff --git a/src/shards-maintainer.service.ts b/src/shards-maintainer.service.ts new file mode 100644 index 0000000..7234d2d --- /dev/null +++ b/src/shards-maintainer.service.ts @@ -0,0 +1,34 @@ +import { METACHAIN } from "./utils/constants"; +import { HttpService } from "./utils/http.service"; + +export class ShardsMaintainerService { + private shardIds: number[] | undefined = undefined; + + async get( + baseUrl: string | undefined, + timeout: number | undefined, + ) { + if (this.shardIds != null) { + return this.shardIds; + } + + return this.shardIds = await this.getShards(baseUrl, timeout); + } + + private async getShards( + baseUrl: string | undefined, + timeout: number | undefined, + ): Promise { + const httpService = new HttpService(baseUrl, timeout); + const networkConfig = await httpService.get('network/config'); + const shardCount = networkConfig.config.erd_num_shards_without_meta; + + const result = []; + for (let i = 0; i < shardCount; i++) { + result.push(i); + } + result.push(METACHAIN); + + return result; + } +} diff --git a/src/transaction.processor.ts b/src/transaction.processor.ts index 4cd110b..91739b1 100644 --- a/src/transaction.processor.ts +++ b/src/transaction.processor.ts @@ -1,19 +1,32 @@ -import axios from 'axios'; +import { GatewayBlockResponse } from './types/gateway/block-response'; +import { GatewayMiniblockProcessingType } from './types/gateway/miniblock-processing-type.enum'; +import { METACHAIN, NETWORK_RESET_NONCE_THRESHOLD } from './utils/constants'; +import { TransactionProcessorMode } from './types/transaction-processor-mode.enum'; +import { LogTopic } from './types/log-topic'; +import { TransactionStatistics } from './types/transaction-statistics'; +import { CrossShardTransaction } from './types/cross-shard-transaction'; +import { TransactionProcessorOptions } from './types/options'; +import { base64Decode } from './utils/decoders'; +import { selectMany } from './utils/utils'; +import { ShardTransaction } from './types/shard-transaction'; +import { GatewayMiniblock } from './types/gateway/miniblock'; +import { GatewayTransaction } from './types/gateway/transaction'; +import { ShardsMaintainerService } from './shards-maintainer.service'; +import { HttpService } from './utils/http.service'; export class TransactionProcessor { - private readonly METACHAIN = 4294967295; private startDate: Date = new Date(); private shardIds: number[] = []; private options: TransactionProcessorOptions = new TransactionProcessorOptions(); private readonly lastProcessedNoncesInternal: { [key: number]: number } = {}; private isRunning: boolean = false; - - private NETWORK_RESET_NONCE_THRESHOLD = 10000; - private crossShardDictionary: { [key: string]: CrossShardTransaction } = {}; + private httpService: HttpService | undefined; + private readonly shardsMaintainerService: ShardsMaintainerService = new ShardsMaintainerService(); - async start(options: TransactionProcessorOptions) { + async start(options: TransactionProcessorOptions): Promise { this.options = options; + this.httpService = new HttpService(this.options.gatewayUrl, this.options.timeout); switch (options.mode) { case TransactionProcessorMode.Hyperblock: @@ -70,7 +83,7 @@ export class TransactionProcessor { // this is to handle the situation where the current nonce is reset // (e.g. devnet/testnet reset where the nonces start again from zero) - if (lastProcessedNonce > currentNonce + this.NETWORK_RESET_NONCE_THRESHOLD) { + if (lastProcessedNonce > currentNonce + NETWORK_RESET_NONCE_THRESHOLD) { this.logMessage(LogTopic.Debug, `Detected network reset. Setting last processed nonce to ${currentNonce} for shard ${shardId}`); lastProcessedNonce = currentNonce; } @@ -167,7 +180,7 @@ export class TransactionProcessor { try { - this.shardIds = [this.METACHAIN]; + this.shardIds = [METACHAIN]; this.startDate = new Date(); let startLastProcessedNonce = 0; @@ -175,12 +188,12 @@ export class TransactionProcessor { let reachedTip: boolean; const currentNonces = await this.getCurrentNonces(); - const currentNonce = currentNonces[this.METACHAIN]; + const currentNonce = currentNonces[METACHAIN]; do { reachedTip = true; - let lastProcessedNonce = await this.getLastProcessedNonceOrCurrent(this.METACHAIN, currentNonce); + let lastProcessedNonce = await this.getLastProcessedNonceOrCurrent(METACHAIN, currentNonce); this.logMessage(LogTopic.Debug, `currentNonce: ${currentNonce}, lastProcessedNonce: ${lastProcessedNonce}`); @@ -190,7 +203,7 @@ export class TransactionProcessor { // this is to handle the situation where the current nonce is reset // (e.g. devnet/testnet reset where the nonces start again from zero) - if (lastProcessedNonce > currentNonce + this.NETWORK_RESET_NONCE_THRESHOLD) { + if (lastProcessedNonce > currentNonce + NETWORK_RESET_NONCE_THRESHOLD) { this.logMessage(LogTopic.Debug, `Detected network reset. Setting last processed nonce to ${currentNonce}`); lastProcessedNonce = currentNonce; } @@ -245,7 +258,7 @@ export class TransactionProcessor { } this.logMessage(LogTopic.Debug, `Setting last processed nonce to ${nonce}`); - await this.setLastProcessedNonce(this.METACHAIN, nonce); + await this.setLastProcessedNonce(METACHAIN, nonce); } while (!reachedTip); } finally { this.isRunning = false; @@ -273,7 +286,7 @@ export class TransactionProcessor { // if '@ok', ignore if (transaction.data) { - const data = TransactionProcessor.base64Decode(transaction.data); + const data = base64Decode(transaction.data); if (data === '@6f6b') { this.logMessage(LogTopic.CrossShardSmartContractResult, `Not incrementing counter for cross-shard SCR, original tx hash ${transaction.originalTransactionHash}, tx hash ${transaction.hash} since the data is @ok (${data})`); continue; @@ -298,7 +311,7 @@ export class TransactionProcessor { // if '@ok', ignore if (transaction.data) { - const data = TransactionProcessor.base64Decode(transaction.data); + const data = base64Decode(transaction.data); if (data === '@6f6b') { this.logMessage(LogTopic.CrossShardSmartContractResult, `Not decrementing counter for cross-shard SCR, original tx hash ${transaction.originalTransactionHash}, tx hash ${transaction.hash} since the data is @ok (${data})`); continue; @@ -330,22 +343,8 @@ export class TransactionProcessor { return crossShardTransactions; } - static base64Decode(str: string): string { - return Buffer.from(str, 'base64').toString('binary'); - } - - private selectMany(array: TIN[], predicate: Function): TOUT[] { - const result = []; - - for (const item of array) { - result.push(...predicate(item)); - } - - return result; - } - private async getShardTransactions(shardId: number, nonce: number): Promise<{ blockHash: string, transactions: ShardTransaction[] } | undefined> { - const result = await this.gatewayGet(`block/${shardId}/by-nonce/${nonce}?withTxs=true`); + const result = await this.gatewayGet(`block/${shardId}/by-nonce/${nonce}?withTxs=true`); if (!result || !result.block) { this.logMessage(LogTopic.Debug, `Block for shardId ${shardId} and nonce ${nonce} is undefined or block not available`); @@ -357,12 +356,19 @@ export class TransactionProcessor { return { blockHash: result.block.hash, transactions: [] }; } - const transactions: ShardTransaction[] = this.selectMany(result.block.miniBlocks, (item: any) => item.transactions ?? []) - .map((item: any) => TransactionProcessor.itemToShardTransaction(item)); + const filteredMiniBlocks = result.block.miniBlocks.filter(q => q.processingType !== GatewayMiniblockProcessingType.Scheduled); + + const transactions: ShardTransaction[] = this.computeShardTransactionsFromMiniblocks(filteredMiniBlocks); return { blockHash: result.block.hash, transactions }; } + private computeShardTransactionsFromMiniblocks(miniblocks: GatewayMiniblock[]): ShardTransaction[] { + const predicate = (item: GatewayMiniblock): GatewayTransaction[] => { return item.transactions ?? []; }; + return selectMany(miniblocks, predicate) + .map(ShardTransaction.build); + } + private async getHyperblockTransactions(nonce: number): Promise<{ blockHash: string, transactions: ShardTransaction[] } | undefined> { const result = await this.gatewayGet(`hyperblock/by-nonce/${nonce}`); if (!result) { @@ -373,41 +379,14 @@ export class TransactionProcessor { return { blockHash: hash, transactions: [] }; } - const shardTransactions: ShardTransaction[] = transactions - .map((item: any) => TransactionProcessor.itemToShardTransaction(item)); - - return { blockHash: hash, transactions: shardTransactions }; - } - - static itemToShardTransaction(item: any): ShardTransaction { - const transaction = new ShardTransaction(); - transaction.data = item.data; - transaction.sender = item.sender; - transaction.receiver = item.receiver; - transaction.sourceShard = item.sourceShard; - transaction.destinationShard = item.destinationShard; - transaction.hash = item.hash; - transaction.nonce = item.nonce; - transaction.status = item.status; - transaction.value = item.value; - transaction.originalTransactionHash = item.originalTransactionHash; - transaction.gasPrice = item.gasPrice; - transaction.gasLimit = item.gasLimit; - transaction.epoch = item.epoch; - return transaction; + return { + blockHash: hash, + transactions: transactions.map(ShardTransaction.build), + }; } private async getShards(): Promise { - const networkConfig = await this.gatewayGet('network/config'); - const shardCount = networkConfig.config.erd_num_shards_without_meta; - - const result = []; - for (let i = 0; i < shardCount; i++) { - result.push(i); - } - - result.push(this.METACHAIN); - return result; + return this.shardsMaintainerService.get(this.options.gatewayUrl, this.options.timeout); } private async getCurrentNonce(shardId: number): Promise { @@ -415,18 +394,12 @@ export class TransactionProcessor { return shardInfo.status.erd_nonce; } - private async gatewayGet(path: string): Promise { - const gatewayUrl = this.options.gatewayUrl ?? 'https://gateway.multiversx.com'; - const fullUrl = `${gatewayUrl}/${path}`; - - try { - const result = await axios.get(fullUrl, { - timeout: this.options.timeout ?? 5000, - }); - return result.data.data; - } catch (error) { - throw new Error(`Error when getting from gateway url ${fullUrl}: ${error}`); + private async gatewayGet(path: string): Promise { + if (this.httpService == null) { + throw new Error("Http Service not initialized."); } + + return this.httpService.get(path); } private async getCurrentNonces(): Promise<{ [key: number]: number }> { @@ -461,7 +434,7 @@ export class TransactionProcessor { return await getLastProcessedNonceFunc(shardId, currentNonce); } - private async setLastProcessedNonce(shardId: number, nonce: number) { + private async setLastProcessedNonce(shardId: number, nonce: number): Promise { const setLastProcessedNonceFunc = this.options.setLastProcessedNonce; if (!setLastProcessedNonceFunc) { this.lastProcessedNoncesInternal[shardId] = nonce; @@ -471,21 +444,31 @@ export class TransactionProcessor { await setLastProcessedNonceFunc(shardId, nonce); } - private async onTransactionsReceived(shardId: number, nonce: number, transactions: ShardTransaction[], statistics: TransactionStatistics, blockHash: string) { + private async onTransactionsReceived( + shardId: number, + nonce: number, + transactions: ShardTransaction[], + statistics: TransactionStatistics, + blockHash: string, + ): Promise { const onTransactionsReceivedFunc = this.options.onTransactionsReceived; if (onTransactionsReceivedFunc) { await onTransactionsReceivedFunc(shardId, nonce, transactions, statistics, blockHash); } } - private async onTransactionsPending(shardId: number, nonce: number, transactions: ShardTransaction[]) { + private async onTransactionsPending( + shardId: number, + nonce: number, + transactions: ShardTransaction[], + ): Promise { const onTransactionsPendingFunc = this.options.onTransactionsPending; if (onTransactionsPendingFunc) { await onTransactionsPendingFunc(shardId, nonce, transactions); } } - private logMessage(topic: LogTopic, message: string) { + private logMessage(topic: LogTopic, message: string): void { const onMessageLogged = this.options.onMessageLogged; if (onMessageLogged) { onMessageLogged(topic, message); @@ -493,98 +476,5 @@ export class TransactionProcessor { } } -export enum LogTopic { - CrossShardSmartContractResult = 'CrossShardSmartContractResult', - Debug = 'Debug', - Error = 'Error', -} - -export class ShardTransaction { - value: string = ''; - data?: string; - hash: string = ''; - sender: string = ''; - receiver: string = ''; - status: string = ''; - sourceShard: number = 0; - destinationShard: number = 0; - nonce: number = 0; - previousTransactionHash?: string; - originalTransactionHash?: string; - gasPrice?: number; - gasLimit?: number; - epoch: number = 0; - - private dataDecoded: string | undefined; - private getDataDecoded(): string | undefined { - if (!this.dataDecoded) { - if (this.data) { - this.dataDecoded = TransactionProcessor.base64Decode(this.data); - } - } - - return this.dataDecoded; - } - - private dataFunctionName: string | undefined; - public getDataFunctionName(): string | undefined { - if (!this.dataFunctionName) { - const decoded = this.getDataDecoded(); - if (decoded) { - this.dataFunctionName = decoded.split('@')[0]; - } - } - - return this.dataFunctionName; - } - - private dataArgs: string[] | undefined; - public getDataArgs(): string[] | undefined { - if (!this.dataArgs) { - const decoded = this.getDataDecoded(); - if (decoded) { - this.dataArgs = decoded.split('@').splice(1); - } - } - - return this.dataArgs; - } -} - -export enum TransactionProcessorMode { - Shardblock = 'Shardblock', - Hyperblock = 'Hyperblock', -} - -export class TransactionProcessorOptions { - gatewayUrl?: string; - maxLookBehind?: number; - waitForFinalizedCrossShardSmartContractResults?: boolean; - notifyEmptyBlocks?: boolean; - includeCrossShardStartedTransactions?: boolean; - mode?: TransactionProcessorMode; - onTransactionsReceived?: (shardId: number, nonce: number, transactions: ShardTransaction[], statistics: TransactionStatistics, blockHash: string) => Promise; - onTransactionsPending?: (shardId: number, nonce: number, transactions: ShardTransaction[]) => Promise; - getLastProcessedNonce?: (shardId: number, currentNonce: number) => Promise; - setLastProcessedNonce?: (shardId: number, nonce: number) => Promise; - onMessageLogged?: (topic: LogTopic, message: string) => void; - timeout?: number | undefined; -} - -export class TransactionStatistics { - secondsElapsed: number = 0; - processedNonces: number = 0; - noncesPerSecond: number = 0; - noncesLeft: number = 0; - secondsLeft: number = 0; -} - -export class CrossShardTransaction { - transaction: ShardTransaction; - counter: number = 0; - created: Date = new Date(); - - constructor(transaction: ShardTransaction) { - this.transaction = transaction; - } -} +export { ShardTransaction }; +export { TransactionProcessorMode }; diff --git a/src/types/cross-shard-transaction.ts b/src/types/cross-shard-transaction.ts new file mode 100644 index 0000000..f01d8fe --- /dev/null +++ b/src/types/cross-shard-transaction.ts @@ -0,0 +1,11 @@ +import { ShardTransaction } from "./shard-transaction"; + +export class CrossShardTransaction { + transaction: ShardTransaction; + counter: number = 0; + created: Date = new Date(); + + constructor(transaction: ShardTransaction) { + this.transaction = transaction; + } +} diff --git a/src/types/gateway/block-response.ts b/src/types/gateway/block-response.ts new file mode 100644 index 0000000..80dcc1b --- /dev/null +++ b/src/types/gateway/block-response.ts @@ -0,0 +1,5 @@ +import { GatewayBlock } from "./block"; + +export interface GatewayBlockResponse { + block: GatewayBlock; +} diff --git a/src/types/gateway/block.ts b/src/types/gateway/block.ts new file mode 100644 index 0000000..e3e604d --- /dev/null +++ b/src/types/gateway/block.ts @@ -0,0 +1,25 @@ +import { GatewayMiniblock } from "./miniblock"; + +export interface GatewayBlock { + nonce: number; + round: number; + epoch: number; + shard: number; + numTxs: number; + hash: string; + prevBlockHash: string; + stateRootHash: string; + accumulatedFees: string; + developerFees: string; + status: string; + randSeed: string; + prevRandSeed: string; + pubKeyBitmap: string; + signature: string; + leaderSignature: string; + chainID: string; + softwareVersion: string; + receiptsHash: string; + timestamp: number; + miniBlocks: GatewayMiniblock[]; +} diff --git a/src/types/gateway/miniblock-processing-type.enum.ts b/src/types/gateway/miniblock-processing-type.enum.ts new file mode 100644 index 0000000..19915b2 --- /dev/null +++ b/src/types/gateway/miniblock-processing-type.enum.ts @@ -0,0 +1,5 @@ +export enum GatewayMiniblockProcessingType { + Normal = 'Normal', + Scheduled = 'Scheduled', + Processed = 'Processed', +} diff --git a/src/types/gateway/miniblock.ts b/src/types/gateway/miniblock.ts new file mode 100644 index 0000000..3013023 --- /dev/null +++ b/src/types/gateway/miniblock.ts @@ -0,0 +1,15 @@ + +import { GatewayMiniblockProcessingType } from "./miniblock-processing-type.enum"; +import { GatewayTransaction } from "./transaction"; + +export interface GatewayMiniblock { + hash: string; + type: string; + processingType: GatewayMiniblockProcessingType; + constructionState: string; + sourceShard: number; + destinationShard: number; + transactions: GatewayTransaction[]; + indexOfFirstTxProcessed: number; + indexOfLastTxProcessed: number; +} diff --git a/src/types/gateway/transaction.ts b/src/types/gateway/transaction.ts new file mode 100644 index 0000000..015b1c3 --- /dev/null +++ b/src/types/gateway/transaction.ts @@ -0,0 +1,66 @@ +import { base64Decode } from "../../utils/decoders"; + +export class GatewayTransaction { + type: string = ''; + processingTypeOnSource: string = ''; + processingTypeOnDestination: string = ''; + round: number = 0; + value: string = ''; + data?: string; + hash: string = ''; + sender: string = ''; + receiver: string = ''; + status: string = ''; + sourceShard: number = 0; + destinationShard: number = 0; + nonce: number = 0; + previousTransactionHash?: string; + originalTransactionHash?: string; + gasPrice?: number; + gasLimit?: number; + epoch: number = 0; + signature: string = ''; + miniblockType: string = ''; + miniblockHash: string = ''; + operation: string = ''; + function: string = ''; + initiallyPaidFee: string = ''; + chainID: string = ''; + version: number = 0; + options: number = 0; + + private dataDecoded: string | undefined; + private getDataDecoded(): string | undefined { + if (!this.dataDecoded) { + if (this.data) { + this.dataDecoded = base64Decode(this.data); + } + } + + return this.dataDecoded; + } + + private dataFunctionName: string | undefined; + public getDataFunctionName(): string | undefined { + if (!this.dataFunctionName) { + const decoded = this.getDataDecoded(); + if (decoded) { + this.dataFunctionName = decoded.split('@')[0]; + } + } + + return this.dataFunctionName; + } + + private dataArgs: string[] | undefined; + public getDataArgs(): string[] | undefined { + if (!this.dataArgs) { + const decoded = this.getDataDecoded(); + if (decoded) { + this.dataArgs = decoded.split('@').splice(1); + } + } + + return this.dataArgs; + } +} diff --git a/src/types/log-topic.ts b/src/types/log-topic.ts new file mode 100644 index 0000000..c10b092 --- /dev/null +++ b/src/types/log-topic.ts @@ -0,0 +1,5 @@ +export enum LogTopic { + CrossShardSmartContractResult = 'CrossShardSmartContractResult', + Debug = 'Debug', + Error = 'Error', +} diff --git a/src/types/options.ts b/src/types/options.ts new file mode 100644 index 0000000..3af15f1 --- /dev/null +++ b/src/types/options.ts @@ -0,0 +1,19 @@ +import { ShardTransaction } from "./shard-transaction"; +import { LogTopic } from "./log-topic"; +import { TransactionProcessorMode } from "./transaction-processor-mode.enum"; +import { TransactionStatistics } from "./transaction-statistics"; + +export class TransactionProcessorOptions { + gatewayUrl?: string; + maxLookBehind?: number; + waitForFinalizedCrossShardSmartContractResults?: boolean; + notifyEmptyBlocks?: boolean; + includeCrossShardStartedTransactions?: boolean; + mode?: TransactionProcessorMode; + onTransactionsReceived?: (shardId: number, nonce: number, transactions: ShardTransaction[], statistics: TransactionStatistics, blockHash: string) => Promise; + onTransactionsPending?: (shardId: number, nonce: number, transactions: ShardTransaction[]) => Promise; + getLastProcessedNonce?: (shardId: number, currentNonce: number) => Promise; + setLastProcessedNonce?: (shardId: number, nonce: number) => Promise; + onMessageLogged?: (topic: LogTopic, message: string) => void; + timeout?: number | undefined; +} diff --git a/src/types/shard-transaction.ts b/src/types/shard-transaction.ts new file mode 100644 index 0000000..5116b77 --- /dev/null +++ b/src/types/shard-transaction.ts @@ -0,0 +1,72 @@ +import { base64Decode } from "../utils/decoders"; +import { GatewayTransaction } from "./gateway/transaction"; + +export class ShardTransaction { + value: string = ''; + data?: string; + hash: string = ''; + sender: string = ''; + receiver: string = ''; + status: string = ''; + sourceShard: number = 0; + destinationShard: number = 0; + nonce: number = 0; + previousTransactionHash?: string; + originalTransactionHash?: string; + gasPrice?: number; + gasLimit?: number; + epoch: number = 0; + + private dataDecoded: string | undefined; + private getDataDecoded(): string | undefined { + if (!this.dataDecoded) { + if (this.data) { + this.dataDecoded = base64Decode(this.data); + } + } + + return this.dataDecoded; + } + + private dataFunctionName: string | undefined; + public getDataFunctionName(): string | undefined { + if (!this.dataFunctionName) { + const decoded = this.getDataDecoded(); + if (decoded) { + this.dataFunctionName = decoded.split('@')[0]; + } + } + + return this.dataFunctionName; + } + + private dataArgs: string[] | undefined; + public getDataArgs(): string[] | undefined { + if (!this.dataArgs) { + const decoded = this.getDataDecoded(); + if (decoded) { + this.dataArgs = decoded.split('@').splice(1); + } + } + + return this.dataArgs; + } + + static build(item: GatewayTransaction): ShardTransaction { + const transaction = new ShardTransaction(); + transaction.data = item.data; + transaction.sender = item.sender; + transaction.receiver = item.receiver; + transaction.sourceShard = item.sourceShard; + transaction.destinationShard = item.destinationShard; + transaction.hash = item.hash; + transaction.nonce = item.nonce; + transaction.status = item.status; + transaction.value = item.value; + transaction.originalTransactionHash = item.originalTransactionHash; + transaction.gasPrice = item.gasPrice; + transaction.gasLimit = item.gasLimit; + transaction.epoch = item.epoch; + return transaction; + } +} diff --git a/src/types/transaction-processor-mode.enum.ts b/src/types/transaction-processor-mode.enum.ts new file mode 100644 index 0000000..e92582e --- /dev/null +++ b/src/types/transaction-processor-mode.enum.ts @@ -0,0 +1,4 @@ +export enum TransactionProcessorMode { + Shardblock = 'Shardblock', + Hyperblock = 'Hyperblock', +} diff --git a/src/types/transaction-statistics.ts b/src/types/transaction-statistics.ts new file mode 100644 index 0000000..9878636 --- /dev/null +++ b/src/types/transaction-statistics.ts @@ -0,0 +1,7 @@ +export class TransactionStatistics { + secondsElapsed: number = 0; + processedNonces: number = 0; + noncesPerSecond: number = 0; + noncesLeft: number = 0; + secondsLeft: number = 0; +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..e1b4b5c --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,2 @@ +export const NETWORK_RESET_NONCE_THRESHOLD = 10000; +export const METACHAIN = 4294967295; diff --git a/src/utils/decoders.ts b/src/utils/decoders.ts new file mode 100644 index 0000000..4c0bdb9 --- /dev/null +++ b/src/utils/decoders.ts @@ -0,0 +1,3 @@ +export function base64Decode(input: string): string { + return Buffer.from(input, 'base64').toString('binary'); +} diff --git a/src/utils/http.service.ts b/src/utils/http.service.ts new file mode 100644 index 0000000..c482038 --- /dev/null +++ b/src/utils/http.service.ts @@ -0,0 +1,31 @@ +import axios from "axios"; + +export class HttpService { + private readonly DEFAULT_TIMEOUT = 5000; + private readonly baseUrl: string | undefined; + private readonly timeout: number; + + constructor( + baseUrl: string | undefined, + timeout: number | undefined = undefined, + ) { + this.baseUrl = baseUrl; + this.timeout = timeout ?? this.DEFAULT_TIMEOUT; + } + + async get( + path: string, + ): Promise { + const gatewayUrl = this.baseUrl ?? 'https://gateway.multiversx.com'; + const fullUrl = `${gatewayUrl}/${path}`; + + try { + const result = await axios.get(fullUrl, { + timeout: this.timeout, + }); + return result.data.data; + } catch (error) { + throw new Error(`Error when getting from url ${fullUrl}: ${error}`); + } + } +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..22cc1f1 --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,9 @@ +export function selectMany(array: TIn[], predicate: (input: TIn) => TOut[]): TOut[] { + const result = []; + + for (const item of array) { + result.push(...predicate(item)); + } + + return result; +}