{"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":"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","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"]}