UNPKG

@aptos-labs/ts-sdk

Version:
1 lines 17.6 kB
{"version":3,"sources":["../../src/transactions/management/transactionWorker.ts"],"sourcesContent":["/* eslint-disable no-await-in-loop */\n\nimport EventEmitter from \"eventemitter3\";\nimport { AptosConfig } from \"../../api/aptosConfig\";\nimport { Account } from \"../../account\";\nimport { waitForTransaction } from \"../../internal/transaction\";\nimport { generateTransaction, signAndSubmitTransaction } from \"../../internal/transactionSubmission\";\nimport { PendingTransactionResponse, TransactionResponse } from \"../../types\";\nimport { InputGenerateTransactionOptions, InputGenerateTransactionPayloadData } from \"../types\";\nimport { AccountSequenceNumber } from \"./accountSequenceNumber\";\nimport { AsyncQueue, AsyncQueueCancelledError } from \"./asyncQueue\";\nimport { SimpleTransaction } from \"../instances/simpleTransaction\";\n\nexport const promiseFulfilledStatus = \"fulfilled\";\n\n// Event types the worker fires during execution and\n// the dapp can listen to\nexport enum TransactionWorkerEventsEnum {\n // fired after a transaction gets sent to the chain\n TransactionSent = \"transactionSent\",\n // fired if there is an error sending the transaction to the chain\n TransactionSendFailed = \"transactionSendFailed\",\n // fired when a single transaction has executed successfully\n TransactionExecuted = \"transactionExecuted\",\n // fired if a single transaction fails in execution\n TransactionExecutionFailed = \"transactionExecutionFailed\",\n // fired when the worker has finished its job / when the queue has been emptied\n ExecutionFinish = \"executionFinish\",\n}\n\n// Typed interface of the worker events\nexport interface TransactionWorkerEvents {\n transactionSent: (data: SuccessEventData) => void;\n transactionSendFailed: (data: FailureEventData) => void;\n transactionExecuted: (data: SuccessEventData) => void;\n transactionExecutionFailed: (data: FailureEventData) => void;\n executionFinish: (data: ExecutionFinishEventData) => void;\n}\n\n// Type for when the worker has finished its job\nexport type ExecutionFinishEventData = {\n message: string;\n};\n\n// Type for a success event\nexport type SuccessEventData = {\n message: string;\n transactionHash: string;\n};\n\n// Type for a failure event\nexport type FailureEventData = {\n message: string;\n error: string;\n};\n\n/**\n * TransactionWorker provides a simple framework for receiving payloads to be processed.\n *\n * Once one `start()` the process and pushes a new transaction, the worker acquires\n * the current account's next sequence number (by using the AccountSequenceNumber class),\n * generates a signed transaction and pushes an async submission process into the `outstandingTransactions` queue.\n * At the same time, the worker processes transactions by reading the `outstandingTransactions` queue\n * and submits the next transaction to chain, it\n * 1) waits for resolution of the submission process or get pre-execution validation error\n * and 2) waits for the resolution of the execution process or get an execution error.\n * The worker fires events for any submission and/or execution success and/or failure.\n */\nexport class TransactionWorker extends EventEmitter<TransactionWorkerEvents> {\n readonly aptosConfig: AptosConfig;\n\n readonly account: Account;\n\n // current account sequence number\n readonly accountSequnceNumber: AccountSequenceNumber;\n\n readonly taskQueue: AsyncQueue<() => Promise<void>> = new AsyncQueue<() => Promise<void>>();\n\n // process has started\n started: boolean;\n\n /**\n * transactions payloads waiting to be generated and signed\n *\n * TODO support entry function payload from ABI builder\n */\n transactionsQueue = new AsyncQueue<\n [InputGenerateTransactionPayloadData, InputGenerateTransactionOptions | undefined]\n >();\n\n /**\n * signed transactions waiting to be submitted\n */\n outstandingTransactions = new AsyncQueue<[Promise<PendingTransactionResponse>, bigint]>();\n\n /**\n * transactions that have been submitted to chain\n */\n sentTransactions: Array<[string, bigint, any]> = [];\n\n /**\n * transactions that have been committed to chain\n */\n executedTransactions: Array<[string, bigint, any]> = [];\n\n /**\n * Provides a simple framework for receiving payloads to be processed.\n *\n * @param aptosConfig - a config object\n * @param sender - a sender as Account\n * @param maxWaitTime - the max wait time to wait before resyncing the sequence number\n * to the current on-chain state, default to 30\n * @param maximumInFlight - submit up to `maximumInFlight` transactions per account.\n * Mempool limits the number of transactions per account to 100, hence why we default to 100.\n * @param sleepTime - If `maximumInFlight` are in flight, wait `sleepTime` seconds before re-evaluating, default to 10\n */\n constructor(\n aptosConfig: AptosConfig,\n account: Account,\n maxWaitTime: number = 30,\n maximumInFlight: number = 100,\n sleepTime: number = 10,\n ) {\n super();\n this.aptosConfig = aptosConfig;\n this.account = account;\n this.started = false;\n this.accountSequnceNumber = new AccountSequenceNumber(\n aptosConfig,\n account,\n maxWaitTime,\n maximumInFlight,\n sleepTime,\n );\n }\n\n /**\n * Gets the current account sequence number,\n * generates the transaction with the account sequence number,\n * adds the transaction to the outstanding transaction queue\n * to be processed later.\n */\n async submitNextTransaction() {\n try {\n /* eslint-disable no-constant-condition */\n while (true) {\n const sequenceNumber = await this.accountSequnceNumber.nextSequenceNumber();\n if (sequenceNumber === null) return;\n const transaction = await this.generateNextTransaction(this.account, sequenceNumber);\n if (!transaction) return;\n const pendingTransaction = signAndSubmitTransaction({\n aptosConfig: this.aptosConfig,\n transaction,\n signer: this.account,\n });\n await this.outstandingTransactions.enqueue([pendingTransaction, sequenceNumber]);\n }\n } catch (error: any) {\n if (error instanceof AsyncQueueCancelledError) {\n return;\n }\n throw new Error(`Submit transaction failed for ${this.account.accountAddress.toString()} with error ${error}`);\n }\n }\n\n /**\n * Reads the outstanding transaction queue and submits the transaction to chain.\n *\n * If the transaction has fulfilled, it pushes the transaction to the processed\n * transactions queue and fires a transactionsFulfilled event.\n *\n * If the transaction has failed, it pushes the transaction to the processed\n * transactions queue with the failure reason and fires a transactionsFailed event.\n */\n async processTransactions() {\n try {\n /* eslint-disable no-constant-condition */\n while (true) {\n const awaitingTransactions = [];\n const sequenceNumbers = [];\n let [pendingTransaction, sequenceNumber] = await this.outstandingTransactions.dequeue();\n\n awaitingTransactions.push(pendingTransaction);\n sequenceNumbers.push(sequenceNumber);\n\n while (!this.outstandingTransactions.isEmpty()) {\n [pendingTransaction, sequenceNumber] = await this.outstandingTransactions.dequeue();\n\n awaitingTransactions.push(pendingTransaction);\n sequenceNumbers.push(sequenceNumber);\n }\n // send awaiting transactions to chain\n const sentTransactions = await Promise.allSettled(awaitingTransactions);\n for (let i = 0; i < sentTransactions.length && i < sequenceNumbers.length; i += 1) {\n // check sent transaction status\n const sentTransaction = sentTransactions[i];\n sequenceNumber = sequenceNumbers[i];\n if (sentTransaction.status === promiseFulfilledStatus) {\n // transaction sent to chain\n this.sentTransactions.push([sentTransaction.value.hash, sequenceNumber, null]);\n // check sent transaction execution\n this.emit(TransactionWorkerEventsEnum.TransactionSent, {\n message: `transaction hash ${sentTransaction.value.hash} has been committed to chain`,\n transactionHash: sentTransaction.value.hash,\n });\n await this.checkTransaction(sentTransaction, sequenceNumber);\n } else {\n // send transaction failed\n this.sentTransactions.push([sentTransaction.status, sequenceNumber, sentTransaction.reason]);\n this.emit(TransactionWorkerEventsEnum.TransactionSendFailed, {\n message: `failed to commit transaction ${this.sentTransactions.length} with error ${sentTransaction.reason}`,\n error: sentTransaction.reason,\n });\n }\n }\n this.emit(TransactionWorkerEventsEnum.ExecutionFinish, {\n message: `execute ${sentTransactions.length} transactions finished`,\n });\n }\n } catch (error: any) {\n if (error instanceof AsyncQueueCancelledError) {\n return;\n }\n throw new Error(`Process execution failed for ${this.account.accountAddress.toString()} with error ${error}`);\n }\n }\n\n /**\n * Once transaction has been sent to chain, we check for its execution status.\n * @param sentTransaction transactions that were sent to chain and are now waiting to be executed\n * @param sequenceNumber the account's sequence number that was sent with the transaction\n */\n async checkTransaction(sentTransaction: PromiseFulfilledResult<PendingTransactionResponse>, sequenceNumber: bigint) {\n try {\n const waitFor: Array<Promise<TransactionResponse>> = [];\n waitFor.push(waitForTransaction({ aptosConfig: this.aptosConfig, transactionHash: sentTransaction.value.hash }));\n const sentTransactions = await Promise.allSettled(waitFor);\n\n for (let i = 0; i < sentTransactions.length; i += 1) {\n const executedTransaction = sentTransactions[i];\n if (executedTransaction.status === promiseFulfilledStatus) {\n // transaction executed to chain\n this.executedTransactions.push([executedTransaction.value.hash, sequenceNumber, null]);\n this.emit(TransactionWorkerEventsEnum.TransactionExecuted, {\n message: `transaction hash ${executedTransaction.value.hash} has been executed on chain`,\n transactionHash: sentTransaction.value.hash,\n });\n } else {\n // transaction execution failed\n this.executedTransactions.push([executedTransaction.status, sequenceNumber, executedTransaction.reason]);\n this.emit(TransactionWorkerEventsEnum.TransactionExecutionFailed, {\n message: `failed to execute transaction ${this.executedTransactions.length} with error ${executedTransaction.reason}`,\n error: executedTransaction.reason,\n });\n }\n }\n } catch (error: any) {\n throw new Error(`Check transaction failed for ${this.account.accountAddress.toString()} with error ${error}`);\n }\n }\n\n /**\n * Push transaction to the transactions queue\n *\n * @param transactionData Transaction payload\n * @param transactionData.abi For all entry function payloads, the ABI to skip remote ABI lookups\n * @param options.maxGasAmount Maximum gas amount for the transaction\n * @param options.gasUnitPrice Gas unit price for the transaction\n * @param options.expireTimestamp expiration timestamp on the transaction\n * @param options.accountSequenceNumber the sequence number for the transaction\n */\n async push(\n transactionData: InputGenerateTransactionPayloadData,\n options?: InputGenerateTransactionOptions,\n ): Promise<void> {\n this.transactionsQueue.enqueue([transactionData, options]);\n }\n\n /**\n * Generates a signed transaction that can be submitted to chain\n * @param account an Aptos account\n * @param sequenceNumber a sequence number the transaction will be generated with\n * @returns\n */\n async generateNextTransaction(account: Account, sequenceNumber: bigint): Promise<SimpleTransaction | undefined> {\n if (this.transactionsQueue.isEmpty()) return undefined;\n const [transactionData, options] = await this.transactionsQueue.dequeue();\n return generateTransaction({\n aptosConfig: this.aptosConfig,\n sender: account.accountAddress,\n data: transactionData,\n options: { ...options, accountSequenceNumber: sequenceNumber },\n });\n }\n\n /**\n * Starts transaction submission and transaction processing.\n */\n async run() {\n try {\n while (!this.taskQueue.isCancelled()) {\n const task = await this.taskQueue.dequeue();\n await task();\n }\n } catch (error: any) {\n throw new Error(`Unable to start transaction batching: ${error}`);\n }\n }\n\n /**\n * Starts the transaction management process.\n */\n start() {\n if (this.started) {\n throw new Error(\"worker has already started\");\n }\n this.started = true;\n this.taskQueue.enqueue(() => this.submitNextTransaction());\n this.taskQueue.enqueue(() => this.processTransactions());\n this.run();\n }\n\n /**\n * Stops the the transaction management process.\n */\n stop() {\n if (this.taskQueue.isCancelled()) {\n throw new Error(\"worker has already stopped\");\n }\n this.started = false;\n this.taskQueue.cancel();\n }\n}\n"],"mappings":"kLAEA,OAAOA,MAAkB,gBAWlB,IAAMC,EAAyB,YAI1BC,OAEVA,EAAA,gBAAkB,kBAElBA,EAAA,sBAAwB,wBAExBA,EAAA,oBAAsB,sBAEtBA,EAAA,2BAA6B,6BAE7BA,EAAA,gBAAkB,kBAVRA,OAAA,IAmDCC,EAAN,cAAgCC,CAAsC,CAgD3E,YACEC,EACAC,EACAC,EAAsB,GACtBC,EAA0B,IAC1BC,EAAoB,GACpB,CACA,MAAM,EA/CR,KAAS,UAA6C,IAAIC,EAU1D,uBAAoB,IAAIA,EAOxB,6BAA0B,IAAIA,EAK9B,sBAAiD,CAAC,EAKlD,0BAAqD,CAAC,EAqBpD,KAAK,YAAcL,EACnB,KAAK,QAAUC,EACf,KAAK,QAAU,GACf,KAAK,qBAAuB,IAAIK,EAC9BN,EACAC,EACAC,EACAC,EACAC,CACF,CACF,CAQA,MAAM,uBAAwB,CAC5B,GAAI,CAEF,OAAa,CACX,IAAMG,EAAiB,MAAM,KAAK,qBAAqB,mBAAmB,EAC1E,GAAIA,IAAmB,KAAM,OAC7B,IAAMC,EAAc,MAAM,KAAK,wBAAwB,KAAK,QAASD,CAAc,EACnF,GAAI,CAACC,EAAa,OAClB,IAAMC,EAAqBC,EAAyB,CAClD,YAAa,KAAK,YAClB,YAAAF,EACA,OAAQ,KAAK,OACf,CAAC,EACD,MAAM,KAAK,wBAAwB,QAAQ,CAACC,EAAoBF,CAAc,CAAC,CACjF,CACF,OAASI,EAAY,CACnB,GAAIA,aAAiBC,EACnB,OAEF,MAAM,IAAI,MAAM,iCAAiC,KAAK,QAAQ,eAAe,SAAS,CAAC,eAAeD,CAAK,EAAE,CAC/G,CACF,CAWA,MAAM,qBAAsB,CAC1B,GAAI,CAEF,OAAa,CACX,IAAME,EAAuB,CAAC,EACxBC,EAAkB,CAAC,EACrB,CAACL,EAAoBF,CAAc,EAAI,MAAM,KAAK,wBAAwB,QAAQ,EAKtF,IAHAM,EAAqB,KAAKJ,CAAkB,EAC5CK,EAAgB,KAAKP,CAAc,EAE5B,CAAC,KAAK,wBAAwB,QAAQ,GAC3C,CAACE,EAAoBF,CAAc,EAAI,MAAM,KAAK,wBAAwB,QAAQ,EAElFM,EAAqB,KAAKJ,CAAkB,EAC5CK,EAAgB,KAAKP,CAAc,EAGrC,IAAMQ,EAAmB,MAAM,QAAQ,WAAWF,CAAoB,EACtE,QAASG,EAAI,EAAGA,EAAID,EAAiB,QAAUC,EAAIF,EAAgB,OAAQE,GAAK,EAAG,CAEjF,IAAMC,EAAkBF,EAAiBC,CAAC,EAC1CT,EAAiBO,EAAgBE,CAAC,EAC9BC,EAAgB,SAAWrB,GAE7B,KAAK,iBAAiB,KAAK,CAACqB,EAAgB,MAAM,KAAMV,EAAgB,IAAI,CAAC,EAE7E,KAAK,KAAK,kBAA6C,CACrD,QAAS,oBAAoBU,EAAgB,MAAM,IAAI,+BACvD,gBAAiBA,EAAgB,MAAM,IACzC,CAAC,EACD,MAAM,KAAK,iBAAiBA,EAAiBV,CAAc,IAG3D,KAAK,iBAAiB,KAAK,CAACU,EAAgB,OAAQV,EAAgBU,EAAgB,MAAM,CAAC,EAC3F,KAAK,KAAK,wBAAmD,CAC3D,QAAS,gCAAgC,KAAK,iBAAiB,MAAM,eAAeA,EAAgB,MAAM,GAC1G,MAAOA,EAAgB,MACzB,CAAC,EAEL,CACA,KAAK,KAAK,kBAA6C,CACrD,QAAS,WAAWF,EAAiB,MAAM,wBAC7C,CAAC,CACH,CACF,OAASJ,EAAY,CACnB,GAAIA,aAAiBC,EACnB,OAEF,MAAM,IAAI,MAAM,gCAAgC,KAAK,QAAQ,eAAe,SAAS,CAAC,eAAeD,CAAK,EAAE,CAC9G,CACF,CAOA,MAAM,iBAAiBM,EAAqEV,EAAwB,CAClH,GAAI,CACF,IAAMW,EAA+C,CAAC,EACtDA,EAAQ,KAAKC,EAAmB,CAAE,YAAa,KAAK,YAAa,gBAAiBF,EAAgB,MAAM,IAAK,CAAC,CAAC,EAC/G,IAAMF,EAAmB,MAAM,QAAQ,WAAWG,CAAO,EAEzD,QAAS,EAAI,EAAG,EAAIH,EAAiB,OAAQ,GAAK,EAAG,CACnD,IAAMK,EAAsBL,EAAiB,CAAC,EAC1CK,EAAoB,SAAWxB,GAEjC,KAAK,qBAAqB,KAAK,CAACwB,EAAoB,MAAM,KAAMb,EAAgB,IAAI,CAAC,EACrF,KAAK,KAAK,sBAAiD,CACzD,QAAS,oBAAoBa,EAAoB,MAAM,IAAI,8BAC3D,gBAAiBH,EAAgB,MAAM,IACzC,CAAC,IAGD,KAAK,qBAAqB,KAAK,CAACG,EAAoB,OAAQb,EAAgBa,EAAoB,MAAM,CAAC,EACvG,KAAK,KAAK,6BAAwD,CAChE,QAAS,iCAAiC,KAAK,qBAAqB,MAAM,eAAeA,EAAoB,MAAM,GACnH,MAAOA,EAAoB,MAC7B,CAAC,EAEL,CACF,OAAST,EAAY,CACnB,MAAM,IAAI,MAAM,gCAAgC,KAAK,QAAQ,eAAe,SAAS,CAAC,eAAeA,CAAK,EAAE,CAC9G,CACF,CAYA,MAAM,KACJU,EACAC,EACe,CACf,KAAK,kBAAkB,QAAQ,CAACD,EAAiBC,CAAO,CAAC,CAC3D,CAQA,MAAM,wBAAwBrB,EAAkBM,EAAgE,CAC9G,GAAI,KAAK,kBAAkB,QAAQ,EAAG,OACtC,GAAM,CAACc,EAAiBC,CAAO,EAAI,MAAM,KAAK,kBAAkB,QAAQ,EACxE,OAAOC,EAAoB,CACzB,YAAa,KAAK,YAClB,OAAQtB,EAAQ,eAChB,KAAMoB,EACN,QAAS,CAAE,GAAGC,EAAS,sBAAuBf,CAAe,CAC/D,CAAC,CACH,CAKA,MAAM,KAAM,CACV,GAAI,CACF,KAAO,CAAC,KAAK,UAAU,YAAY,GAEjC,MADa,MAAM,KAAK,UAAU,QAAQ,GAC/B,CAEf,OAASI,EAAY,CACnB,MAAM,IAAI,MAAM,yCAAyCA,CAAK,EAAE,CAClE,CACF,CAKA,OAAQ,CACN,GAAI,KAAK,QACP,MAAM,IAAI,MAAM,4BAA4B,EAE9C,KAAK,QAAU,GACf,KAAK,UAAU,QAAQ,IAAM,KAAK,sBAAsB,CAAC,EACzD,KAAK,UAAU,QAAQ,IAAM,KAAK,oBAAoB,CAAC,EACvD,KAAK,IAAI,CACX,CAKA,MAAO,CACL,GAAI,KAAK,UAAU,YAAY,EAC7B,MAAM,IAAI,MAAM,4BAA4B,EAE9C,KAAK,QAAU,GACf,KAAK,UAAU,OAAO,CACxB,CACF","names":["EventEmitter","promiseFulfilledStatus","TransactionWorkerEventsEnum","TransactionWorker","EventEmitter","aptosConfig","account","maxWaitTime","maximumInFlight","sleepTime","AsyncQueue","AccountSequenceNumber","sequenceNumber","transaction","pendingTransaction","signAndSubmitTransaction","error","AsyncQueueCancelledError","awaitingTransactions","sequenceNumbers","sentTransactions","i","sentTransaction","waitFor","waitForTransaction","executedTransaction","transactionData","options","generateTransaction"]}