1.0.1 • Published 2 months ago

@rosen-bridge/tx-pot v1.0.1

Weekly downloads
-
License
GPL-3.0
Repository
-
Last release
2 months ago

@rosen-bridge/tx-pot

Table of contents

Introduction

@rosen-bridge/tx-pot is a Typescript package to manage transactions storage, sign, submit and related processes.

Installation

npm:

npm i @rosen-bridge/tx-pot

yarn:

yarn add @rosen-bridge/tx-pot

Usage

The usage of @rosen-bridge/tx-pot package is explained briefly in several parts. Implementing network interface, setup and insert transaction steps are crucial to use the package. Other steps just explain how to utilize the package and better use cases.

TxPot requires a database connection and works with a single table, TransactionEntity. The primary key for this table is the pair of chain and transaction id. It also has various columns which will be explained in #insert-transaction.

Network Interface

Before diving into using TxPot, a network interface is required for each chain. The interface should implement the AbstractPotChainManager class. These interfaces should be registered in TxPot after the setup step. The interface functions are briefly described in the following.

getHeight

This function gets the blockchain's current height. It is required while processing transactions with sent status and is used to update the transaction lastCheck, which almost shows the last height that a transaction was valid. It is also required while setting a transaction as invalid.

getTxRequiredConfirmation

This function returns the required number of confirmations for a transaction based on its type. The transaction types are defined by the superior package and don't matter to TxPot.

@param txType <string> type of the transaction

getTxConfirmation

This function gets the number of confirmations for a transaction. Note that this function should return -1 if tx is not mined (e.g. is in mempool) or is not in the blockchain.

@param txId <string> the transaction id

isTxValid

This function checks if a transaction is still valid and can be sent to the network. For example, in UTxO-based blockchains, it should check if the input UTxOs are valid and still unspent. Also in Account-based blockchains, the state of the account (e.g. nonce in Ethereum) should be checked.

@param serializedTx <string> the serialized transaction
@param signingStatus <SigningStatus> the transaction sign status (0 for signed, 1 for unsigned)

submitTransaction

This function submits a transaction to the blockchain. The result doesn't matter to TxPot and the state of the transaction will be checked while processing sent transactions (see #process-signed-transactions).

@param serializedTx <string> the serialized transaction

isTxInMempool

This function checks if a transaction is in mempool. If the blockchain has no mempool or mempool usage is not required, it should just return false.

@param txId <string> the transaction id

Setup

TxPot is defined in Singleton architecture, i.e. a single object will be defined and used by the entire program.

To instantiate TxPot, the setup function should get called with typeorm data source.

import { TxPot } from '@rosen-bridge/tx-pot';
import { dataSource } from '../db/dataSource';

const txPot = TxPot.setup(dataSource);

A logger can be passed to TxPot. Example of integrating @rosen-bridge/winston-logger with TxPot:

import { TxPot } from '@rosen-bridge/tx-pot';
import { dataSource } from '../db/dataSource';
import WinstonLogger from '@rosen-bridge/winston-logger';

const logger = WinstonLogger.getInstance().getLogger(`TxPot`);

const txPot = TxPot.setup(dataSource, logger);

After instantiating TxPot all supported chains interfaces should be registered in (see #network-interface for more details).

import { ErgoChainManager } from 'managers/ErgoChainManager';
import { CardanoChainManager } from 'managers/CardanoChainManager';

txPot.registerChain(`ergo`, ErgoChainManager);
txPot.registerChain(`cardano`, CardanoChainManager);

Validator and callback functions can be registered to TxPot but they are optional (see #transaction-validations and #status-change-notification for more details).

Insert Transaction

Transaction can be inserted using the addTx function. It inserts transactions with approved status by default and 0 for the lastCheck column. For inserting signed transactions, the signed status should be passed as initial status. In this case, don't forget to also pass the current blockchain height as lastCheck argument. There are two columns to store arbitrary data for transactions, extra and extra2. The difference is transactions can be filtered and fetched by their extra field while it is not possible with extra2. If some foreign key concept is required for transactions, it is suggested to store the key in the extra column.

Example of inserting a transaction into TxPot:

await txPot.addTx(
  'a742019b9c3963713dccda20e38717f8854d7d2ede97899f7eba78f67acef10b',
  'ergo',
  'my-type',
  1,
  'your-serialized-tx'
);

Example of inserting a signed transaction into TxPot:

import { TransactionStatus } from '@rosen-bridge/tx-pot';

await txPot.addTx(
  'a742019b9c3963713dccda20e38717f8854d7d2ede97899f7eba78f67acef10b',
  'ergo',
  'my-type',
  1,
  'your-serialized-tx',
  TransactionStatus.SIGNED,
  1000000
);

Example of inserting a transaction with arbitrary data into TxPot (can be combined with above example to insert signed transaction):

import { TransactionStatus } from '@rosen-bridge/tx-pot';

await txPot.addTx(
  'a742019b9c3963713dccda20e38717f8854d7d2ede97899f7eba78f67acef10b',
  'ergo',
  'my-type',
  1,
  'your-serialized-tx',
  undefined,
  undefined,
  'your-foreign-key',
  '{ yourData: "any-data", secondKey: 100 }'
);

Transactions can be inserted with any other statuses, though it is not recommended.

Process Signed Transactions

Signed transactions are processed by TxPot itself. The process will be executed by the update function. It is recommended to execute it on the interval based on the minimum blockchain block time. Example:

const txPotJob = async () => {
  const txPot = await TxPot.getInstance();
  txPot.update().then(() => {
    setTimeout(txPotJob, interval);
  });
};

This function only processes transactions with signed and sent statuses.

Transactions with signed status will be validated by the submit validators (if any are registered) and if all are passed, it will be submitted to the network and its status will be updated to sent.

Processing sent transactions is a bit more complicated. There are three cases.

If the transaction is mined and confirmed enough, its status will be updated to completed.

If the transaction is mined but enough confirmation is not passed yet, the lastCheck column is updated to the current height of the blockchain.

If the transaction is not mined, it will be searched for in mempool. If it is in mempool, the lastCheck column is updated to the current height of the blockchain. If it is not in mempool, the transaction will be validated.

There may be two validations here. The first one is the chain validation, which is the isTxValid function in the network interface (described in Network Interface ). The second one is any registered validator by the superior package (see #transaction-validations for more details). Note that submit validators won't be checked in here.

If the transaction is still valid, it will be submitted to the network. If it's not, the lastCheck column will be checked and only if enough blocks are passed from it, the status will be updated to invalid.

There are other statuses for transactions, but they will not be processed by TxPot and the the superior package should process them itself. It is recommended to process them before calling update:

const txPotJob = async () => {
  const txPot = await TxPot.getInstance();

  const approvedTxs = await TxPot.getTxsByStatus(
    TransactionStatus.APPROVED,
    true // only returns valid txs (also mutate invalid ones)
  );
  await processApprovedTxs(approvedTxs);

  const signFailedTxs = await TxPot.getTxsByStatus(
    TransactionStatus.SIGN_FAILED
  );
  await processSignFailedTxs(signFailedTxs);

  txPot.update().then(() => {
    setTimeout(txPotJob, interval);
  });
};

The validate flag in getTxsByStatus arguments specifies if the fetched transactions should be validated. Similar to sent transactions, chain validation and the registered validators will be checked. Note that submit validators won't be checked in here either.

Again similar to sent transactions, if the transaction is not valid, the lastCheck column will be checked and only if enough blocks are passed from it, the status will be updated to invalid. The remaining valid transactions will be returned.

Transaction Validations

There are three types of validators in TxPot. The first one is to validate chain conditions, which are checked by the chain manager (isTxValid function described in Network Interface ). The second one is the validators registered by the superior package using registerValidator. The third one is the submit-only validators, which should be registered by registerSubmitValidator. Note that the third one will be called only before attempting to submit a transaction and won't be called in any other cases (such as processing sent transactions).

The second type of validator, requires chain, transaction type and an id to be registered. Id only identifies the validator and won't be used while validating transactions. Multiple validators can be registered for a single chain and transaction type using different ids. Registering a new validator with the same chain, type and id will override the previous one. Example:

import { s1ErgoPaymentTxValidator } from '../service1/validators';
import { s2ErgoPaymentTxValidator } from '../service1/validators';

txPot.registerValidator(
  `ergo`,
  `payment`,
  `service-1`,
  s1ErgoPaymentTxValidator
);
txPot.registerValidator(
  `ergo`,
  `payment`,
  `service-2`,
  s2ErgoPaymentTxValidator
);

the submit-only validators only require chain and id. Similar to the previous one, registering multiple validators is allowed and registering the validator with duplicate chain and id overrides the previous one. Example:

import { s1ErgoSubmitValidator } from '../service1/validators';

txPot.registerSubmitValidator(`ergo`, `service-1`, s1ErgoSubmitValidator);

The validators can also be removed using unregister functions:

txPot.unregisterValidator(`ergo`, `payment`, `service-1`);

txPot.unregisterSubmitValidator(`ergo`, `service-1`);

Status Change Notification

The superior package can be notified when a transaction status is changed. The notification process will be done based on the transaction type and the new status.

In order to perform an action on status change, a callback should be registered to TxPot. Similar to validators, registering multiple callbacks is allowed and registering callback with duplicate type, status and id overrides the previous one. Example:

import { onPaymentComplete } from '../service1/jobs';

txPot.registerCallback(
  `payment`,
  TransactionStatus.COMPLETED,
  'service-1',
  onPaymentComplete
);

Removing callbacks is done by unregisterCallback:

txPot.unregisterCallback(`payment`, TransactionStatus.COMPLETED, 'service-1');

Fetch Transactions

Some functions are defined to update and fetch transactions. The getTxsQuery function is defined for general use cases and always returns the list of transactions. It gets the list of queries and merges the result (technically, queries are merged into a single query with OR clause). Each query supports conditions on six fields of the transaction. The available conditions are described in the following.

txId

Transactions can be fetched by id in two ways. Fetching transactions with a single id, or list of ids.

// fetch transactions with single id
txPot.getTxsQuery([
  {
    txId: '48ffe9014a3e3370df3f6eadcaf6ffa2de96cc94f3c4170c65e561beecdb1da9',
  },
]);
// fetch transactions with list of ids
txPot.getTxsQuery([
  {
    txId: [
      '48ffe9014a3e3370df3f6eadcaf6ffa2de96cc94f3c4170c65e561beecdb1da9',
      '377c038d4bf522617ef7a74254be48c97d692787179cb091444ce808b63ced2d',
    ],
  },
]);

chain

Only a single chain can be specified in the query. Example:

txPot.getTxsQuery([
  {
    chain: 'ergo',
  },
]);

txType

Only a single transaction type can be specified in the query. Example:

txPot.getTxsQuery([
  {
    txType: 'payment',
  },
]);

status

The status column can be specified in four ways:

  • single status
txPot.getTxsQuery([
  {
    status: {
      not: false,
      value: TransactionStatus.APPROVED,
    },
  },
]);
  • multiple statuses
txPot.getTxsQuery([
  {
    status: {
      not: false,
      value: [TransactionStatus.APPROVED, TransactionStatus.SIGN_FAILED],
    },
  },
]);
  • all statuses except a single one
txPot.getTxsQuery([
  {
    status: {
      not: true,
      value: TransactionStatus.INVALID,
    },
  },
]);
  • all statuses except multiple ones
txPot.getTxsQuery([
  {
    status: {
      not: true,
      value: [TransactionStatus.COMPLETED, TransactionStatus.INVALID],
    },
  },
]);

failedInSign

The failedInSign column is also available to query.

txPot.getTxsQuery([
  {
    failedInSign: true,
  },
]);

extra

similar to txId, the extra column supports single and multiple value conditions.

// fetch transactions with single data
txPot.getTxsQuery([
  {
    extra: 'arbitrary-data',
  },
]);
// fetch transactions with list of data
txPot.getTxsQuery([
  {
    extra: ['arbitrary-data-1', 'arbitrary-data-2'],
  },
]);

The queries can be combined. Example:

const unsignedTxsToRetry = {
  status: {
    not: false,
    value: [TransactionStatus.IN_SIGN, TransactionStatus.SIGN_FAILED],
  },
  failedInSign: true,
};

const importantTxs = {
  extra: `important!`,
};

txPot.getTxsQuery([unsignedTxsToRetry, importantTxs]);
1.0.1

2 months ago

1.0.0

3 months ago

0.1.0

4 months ago