Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@liskhq/lisk-transaction-pool

Package Overview
Dependencies
Maintainers
3
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@liskhq/lisk-transaction-pool - npm Package Compare versions

Comparing version 0.2.1 to 0.3.0

dist-node/errors.d.ts

2

dist-node/index.d.ts
export * from './transaction_pool';
export { MaxHeap } from './max_heap';
export { MinHeap } from './min_heap';

@@ -7,2 +7,6 @@ "use strict";

__export(require("./transaction_pool"));
var max_heap_1 = require("./max_heap");
exports.MaxHeap = max_heap_1.MaxHeap;
var min_heap_1 = require("./min_heap");
exports.MinHeap = min_heap_1.MinHeap;
//# sourceMappingURL=index.js.map

154

dist-node/transaction_pool.d.ts
/// <reference types="node" />
import { EventEmitter } from 'events';
import { CheckerFunction, TransactionResponse } from './check_transactions';
import { Queue } from './queue';
export interface TransactionObject {
readonly id: string;
receivedAt?: Date;
readonly asset: {
[key: string]: string | number | ReadonlyArray<string> | undefined;
};
readonly senderPublicKey: string;
signatures?: ReadonlyArray<string>;
readonly type: number;
containsUniqueData?: boolean;
verifiedOnce?: boolean;
import { Status, Transaction, TransactionError, TransactionResponse } from './types';
declare type ApplyFunction = (transactions: ReadonlyArray<Transaction>) => Promise<ReadonlyArray<TransactionResponse>>;
export interface TransactionPoolConfig {
readonly maxTransactions?: number;
readonly maxTransactionsPerAccount?: number;
readonly transactionExpiryTime?: number;
readonly minEntranceFeePriority?: bigint;
readonly transactionReorganizationInterval?: number;
readonly minReplacementFeeDifference?: bigint;
readonly applyTransactions: ApplyFunction;
}
export interface SignatureObject {
transactionId: string;
signature: string;
publicKey: string;
interface AddTransactionResponse {
readonly status: Status;
readonly errors: ReadonlyArray<TransactionError>;
}
export interface TransactionFunctions {
isExpired(date: Date): boolean;
verifyAgainstOtherTransactions(otherTransactions: ReadonlyArray<Transaction>): boolean;
addVerifiedSignature(signature: string): TransactionResponse;
isReady(): boolean;
export declare const DEFAULT_MAX_TRANSACTIONS = 4096;
export declare const DEFAULT_MAX_TRANSACTIONS_PER_ACCOUNT = 64;
export declare const DEFAULT_MIN_ENTRANCE_FEE_PRIORITY: bigint;
export declare const DEFAULT_EXPIRY_TIME: number;
export declare const DEFAULT_EXPIRE_INTERVAL: number;
export declare const DEFAULT_MINIMUM_REPLACEMENT_FEE_DIFFERENCE: bigint;
export declare const DEFAULT_REORGANIZE_TIME = 500;
export declare const events: {
EVENT_TRANSACTION_REMOVED: string;
};
export declare class TransactionPool {
events: EventEmitter;
private _allTransactions;
private _transactionList;
private readonly _applyFunction;
private readonly _maxTransactions;
private readonly _maxTransactionsPerAccount;
private readonly _transactionExpiryTime;
private readonly _minEntranceFeePriority;
private readonly _transactionReorganizationInterval;
private readonly _minReplacementFeeDifference;
private readonly _reorganizeJob;
private readonly _feePriorityQueue;
private readonly _expireJob;
constructor(config: TransactionPoolConfig);
start(): Promise<void>;
stop(): void;
getAll(): ReadonlyArray<Transaction>;
get(id: string): Transaction | undefined;
contains(id: string): boolean;
add(incomingTx: Transaction): Promise<AddTransactionResponse>;
remove(tx: Transaction): boolean;
getProcessableTransactions(): {
readonly [address: string]: ReadonlyArray<Transaction>;
};
private _calculateFeePriority;
private _getStatus;
private _evictUnprocessable;
private _evictProcessable;
private _reorganize;
private _expire;
}
export interface TransactionPoolConfiguration {
readonly expireTransactionsInterval: number;
readonly maxTransactionsPerQueue: number;
readonly receivedTransactionsLimitPerProcessing: number;
readonly receivedTransactionsProcessingInterval: number;
readonly validatedTransactionsLimitPerProcessing: number;
readonly validatedTransactionsProcessingInterval: number;
readonly verifiedTransactionsLimitPerProcessing: number;
readonly verifiedTransactionsProcessingInterval: number;
readonly pendingTransactionsProcessingLimit: number;
}
export interface AddTransactionResult {
readonly alreadyExists: boolean;
readonly isFull: boolean;
readonly queueName: QueueNames;
}
interface TransactionPoolDependencies {
processTransactions: CheckerFunction;
validateTransactions: CheckerFunction;
verifyTransactions: CheckerFunction;
}
declare type TransactionPoolOptions = TransactionPoolConfiguration & TransactionPoolDependencies;
export declare type Transaction = TransactionObject & TransactionFunctions;
export declare type QueueNames = 'received' | 'validated' | 'verified' | 'pending' | 'ready';
interface Queues {
readonly [queue: string]: Queue;
}
export declare const EVENT_ADDED_TRANSACTIONS = "transactionsAdded";
export declare const EVENT_REMOVED_TRANSACTIONS = "transactionsRemoved";
export declare const EVENT_VERIFIED_TRANSACTION_ONCE = "transactionVerifiedOnce";
export declare const ACTION_ADD_VERIFIED_REMOVED_TRANSACTIONS = "addVerifiedRemovedTransactions";
export declare const ACTION_REMOVE_CONFIRMED_TRANSACTIONS = "removeConfirmedTransactions";
export declare const ACTION_ADD_TRANSACTIONS = "addTransactions";
export declare const ACTION_EXPIRE_TRANSACTIONS = "expireTransactions";
export declare const ACTION_PROCESS_VERIFIED_TRANSACTIONS = "processVerifiedTransactions";
export declare const ACTION_VALIDATE_RECEIVED_TRANSACTIONS = "validateReceivedTransactions";
export declare const ACTION_VERIFY_VALIDATED_TRANSACTIONS = "verifyValidatedTransactions";
export declare const ACTION_ADD_VERIFIED_TRANSACTIONS = "addVerifiedTransactions";
export declare const ACTION_ADD_PENDING_TRANSACTIONS = "addPendingTransactions";
export declare class TransactionPool extends EventEmitter {
private readonly _pendingTransactionsProcessingLimit;
private readonly _expireTransactionsInterval;
private readonly _expireTransactionsJob;
private readonly _maxTransactionsPerQueue;
private readonly _queues;
private readonly _receivedTransactionsProcessingInterval;
private readonly _receivedTransactionsProcessingLimitPerInterval;
private readonly _validatedTransactionsProcessingInterval;
private readonly _validatedTransactionsProcessingLimitPerInterval;
private readonly _verifiedTransactionsProcessingInterval;
private readonly _verifiedTransactionsProcessingLimitPerInterval;
private readonly _validateTransactions;
private readonly _validateTransactionsJob;
private readonly _verifyTransactions;
private readonly _verifyTransactionsJob;
private readonly _processTransactions;
private readonly _processTransactionsJob;
constructor({ expireTransactionsInterval, maxTransactionsPerQueue, receivedTransactionsProcessingInterval, receivedTransactionsLimitPerProcessing, validatedTransactionsProcessingInterval, validatedTransactionsLimitPerProcessing, verifiedTransactionsProcessingInterval, verifiedTransactionsLimitPerProcessing, pendingTransactionsProcessingLimit, validateTransactions, verifyTransactions, processTransactions, }: TransactionPoolOptions);
cleanup(): void;
addTransaction(transaction: Transaction): AddTransactionResult;
addPendingTransaction(transaction: Transaction): AddTransactionResult;
addVerifiedTransaction(transaction: Transaction): AddTransactionResult;
addVerifiedRemovedTransactions(transactions: ReadonlyArray<Transaction>): void;
addVerifiedSignature(signatureObject: SignatureObject): TransactionResponse;
existsInTransactionPool(id: string): boolean;
findInTransactionPool(id: string): Transaction | undefined;
get queues(): Queues;
getProcessableTransactions(limit: number): ReadonlyArray<Transaction>;
removeConfirmedTransactions(transactions: ReadonlyArray<Transaction>): void;
reverifyTransactionsFromSenders(senderPublicKeys: ReadonlyArray<string>): void;
validateTransactionAgainstTransactionsInPool(transaction: Transaction): boolean;
private addTransactionToQueue;
private expireTransactions;
private processVerifiedTransactions;
private removeTransactionsFromQueues;
private validateReceivedTransactions;
private verifyValidatedTransactions;
}
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lisk_cryptography_1 = require("@liskhq/lisk-cryptography");
const Debug = require("debug");
const events_1 = require("events");
const check_transactions_1 = require("./check_transactions");
const errors_1 = require("./errors");
const job_1 = require("./job");
const queue_1 = require("./queue");
const queueCheckers = require("./queue_checkers");
const DEFAULT_PENDING_TRANSACTIONS_PROCESSING_LIMIT = 5;
const DEFAULT_EXPIRE_TRANSACTION_INTERVAL = 30000;
const DEFAULT_MAX_TRANSACTIONS_PER_QUEUE = 1000;
const DEFAULT_RECEIVED_TRANSACTIONS_PROCESSING_INTERVAL = 30000;
const DEFAULT_RECEIVED_TRANSACTIONS_LIMIT_PER_PROCESSING = 100;
const DEFAULT_VALIDATED_TRANSACTIONS_PROCESSING_INTERVAL = 30000;
const DEFAULT_VALIDATED_TRANSACTIONS_LIMIT_PER_PROCESSING = 100;
const DEFAULT_VERIFIED_TRANSACTIONS_PROCESSING_INTERVAL = 30000;
const DEFAULT_VERIFIED_TRANSACTIONS_LIMIT_PER_PROCESSING = 100;
exports.EVENT_ADDED_TRANSACTIONS = 'transactionsAdded';
exports.EVENT_REMOVED_TRANSACTIONS = 'transactionsRemoved';
exports.EVENT_VERIFIED_TRANSACTION_ONCE = 'transactionVerifiedOnce';
exports.ACTION_ADD_VERIFIED_REMOVED_TRANSACTIONS = 'addVerifiedRemovedTransactions';
exports.ACTION_REMOVE_CONFIRMED_TRANSACTIONS = 'removeConfirmedTransactions';
exports.ACTION_ADD_TRANSACTIONS = 'addTransactions';
exports.ACTION_EXPIRE_TRANSACTIONS = 'expireTransactions';
exports.ACTION_PROCESS_VERIFIED_TRANSACTIONS = 'processVerifiedTransactions';
exports.ACTION_VALIDATE_RECEIVED_TRANSACTIONS = 'validateReceivedTransactions';
exports.ACTION_VERIFY_VALIDATED_TRANSACTIONS = 'verifyValidatedTransactions';
exports.ACTION_ADD_VERIFIED_TRANSACTIONS = 'addVerifiedTransactions';
exports.ACTION_ADD_PENDING_TRANSACTIONS = 'addPendingTransactions';
class TransactionPool extends events_1.EventEmitter {
constructor({ expireTransactionsInterval = DEFAULT_EXPIRE_TRANSACTION_INTERVAL, maxTransactionsPerQueue = DEFAULT_MAX_TRANSACTIONS_PER_QUEUE, receivedTransactionsProcessingInterval = DEFAULT_RECEIVED_TRANSACTIONS_PROCESSING_INTERVAL, receivedTransactionsLimitPerProcessing = DEFAULT_RECEIVED_TRANSACTIONS_LIMIT_PER_PROCESSING, validatedTransactionsProcessingInterval = DEFAULT_VALIDATED_TRANSACTIONS_PROCESSING_INTERVAL, validatedTransactionsLimitPerProcessing = DEFAULT_VALIDATED_TRANSACTIONS_LIMIT_PER_PROCESSING, verifiedTransactionsProcessingInterval = DEFAULT_VERIFIED_TRANSACTIONS_PROCESSING_INTERVAL, verifiedTransactionsLimitPerProcessing = DEFAULT_VERIFIED_TRANSACTIONS_LIMIT_PER_PROCESSING, pendingTransactionsProcessingLimit = DEFAULT_PENDING_TRANSACTIONS_PROCESSING_LIMIT, validateTransactions, verifyTransactions, processTransactions, }) {
super();
this._maxTransactionsPerQueue = maxTransactionsPerQueue;
this._pendingTransactionsProcessingLimit = pendingTransactionsProcessingLimit;
this._queues = {
received: new queue_1.Queue(),
validated: new queue_1.Queue(),
verified: new queue_1.Queue(),
pending: new queue_1.Queue(),
ready: new queue_1.Queue(),
};
this._expireTransactionsInterval = expireTransactionsInterval;
this._expireTransactionsJob = new job_1.Job(this.expireTransactions.bind(this), this._expireTransactionsInterval);
this._expireTransactionsJob.start();
this._receivedTransactionsProcessingInterval = receivedTransactionsProcessingInterval;
this._receivedTransactionsProcessingLimitPerInterval = receivedTransactionsLimitPerProcessing;
this._validateTransactions = validateTransactions;
this._validateTransactionsJob = new job_1.Job(this.validateReceivedTransactions.bind(this), this._receivedTransactionsProcessingInterval);
this._validateTransactionsJob.start();
this._validatedTransactionsProcessingInterval = validatedTransactionsProcessingInterval;
this._validatedTransactionsProcessingLimitPerInterval = validatedTransactionsLimitPerProcessing;
this._verifyTransactions = verifyTransactions;
this._verifyTransactionsJob = new job_1.Job(this.verifyValidatedTransactions.bind(this), this._validatedTransactionsProcessingInterval);
this._verifyTransactionsJob.start();
this._verifiedTransactionsProcessingInterval = verifiedTransactionsProcessingInterval;
this._verifiedTransactionsProcessingLimitPerInterval = verifiedTransactionsLimitPerProcessing;
this._processTransactions = processTransactions;
this._processTransactionsJob = new job_1.Job(this.processVerifiedTransactions.bind(this), this._verifiedTransactionsProcessingInterval);
this._processTransactionsJob.start();
const min_heap_1 = require("./min_heap");
const transaction_list_1 = require("./transaction_list");
const types_1 = require("./types");
const debug = Debug('lisk:transaction_pool');
exports.DEFAULT_MAX_TRANSACTIONS = 4096;
exports.DEFAULT_MAX_TRANSACTIONS_PER_ACCOUNT = 64;
exports.DEFAULT_MIN_ENTRANCE_FEE_PRIORITY = BigInt(0);
exports.DEFAULT_EXPIRY_TIME = 3 * 60 * 60 * 1000;
exports.DEFAULT_EXPIRE_INTERVAL = 60 * 60 * 1000;
exports.DEFAULT_MINIMUM_REPLACEMENT_FEE_DIFFERENCE = BigInt(10);
exports.DEFAULT_REORGANIZE_TIME = 500;
exports.events = {
EVENT_TRANSACTION_REMOVED: 'EVENT_TRANSACTION_REMOVED',
};
class TransactionPool {
constructor(config) {
var _a, _b, _c, _d, _e, _f;
this.events = new events_1.EventEmitter();
this._feePriorityQueue = new min_heap_1.MinHeap();
this._allTransactions = {};
this._transactionList = {};
this._applyFunction = config.applyTransactions;
this._maxTransactions = (_a = config.maxTransactions) !== null && _a !== void 0 ? _a : exports.DEFAULT_MAX_TRANSACTIONS;
this._maxTransactionsPerAccount = (_b = config.maxTransactionsPerAccount) !== null && _b !== void 0 ? _b : exports.DEFAULT_MAX_TRANSACTIONS_PER_ACCOUNT;
this._transactionExpiryTime = (_c = config.transactionExpiryTime) !== null && _c !== void 0 ? _c : exports.DEFAULT_EXPIRY_TIME;
this._minEntranceFeePriority = (_d = config.minEntranceFeePriority) !== null && _d !== void 0 ? _d : exports.DEFAULT_MIN_ENTRANCE_FEE_PRIORITY;
this._transactionReorganizationInterval = (_e = config.transactionReorganizationInterval) !== null && _e !== void 0 ? _e : exports.DEFAULT_REORGANIZE_TIME;
this._minReplacementFeeDifference = (_f = config.minReplacementFeeDifference) !== null && _f !== void 0 ? _f : exports.DEFAULT_MINIMUM_REPLACEMENT_FEE_DIFFERENCE;
this._reorganizeJob = new job_1.Job(() => this._reorganize(), this._transactionReorganizationInterval);
this._expireJob = new job_1.Job(() => this._expire(), exports.DEFAULT_EXPIRE_INTERVAL);
}
cleanup() {
this.removeTransactionsFromQueues(Object.keys(this.queues), queueCheckers.returnTrueUntilLimit(this._maxTransactionsPerQueue));
this._expireTransactionsJob.stop();
this._validateTransactionsJob.stop();
this._verifyTransactionsJob.stop();
this._processTransactionsJob.stop();
async start() {
this._reorganizeJob.start();
this._expireJob.start();
}
addTransaction(transaction) {
const receivedQueue = 'received';
transaction.verifiedOnce = false;
return this.addTransactionToQueue(receivedQueue, transaction);
stop() {
this._reorganizeJob.stop();
this._expireJob.stop();
}
addPendingTransaction(transaction) {
const pendingQueue = 'pending';
return this.addTransactionToQueue(pendingQueue, transaction);
getAll() {
return Object.values(this._allTransactions);
}
addVerifiedTransaction(transaction) {
const verifiedQueue = 'verified';
return this.addTransactionToQueue(verifiedQueue, transaction);
get(id) {
return this._allTransactions[id];
}
addVerifiedRemovedTransactions(transactions) {
const { received, validated, ...otherQueues } = this._queues;
const removedTransactionsByRecipientIdFromValidatedQueue = this._queues.validated.removeFor(queueCheckers.checkTransactionForSenderIdWithRecipientIds(transactions));
this._queues.received.enqueueMany(removedTransactionsByRecipientIdFromValidatedQueue);
const removedTransactionsByRecipientIdFromOtherQueues = this.removeTransactionsFromQueues(Object.keys(otherQueues), queueCheckers.checkTransactionForSenderIdWithRecipientIds(transactions));
this._queues.validated.enqueueMany(removedTransactionsByRecipientIdFromOtherQueues);
this._queues.verified.enqueueMany(transactions);
this.emit(exports.EVENT_ADDED_TRANSACTIONS, {
action: exports.ACTION_ADD_VERIFIED_REMOVED_TRANSACTIONS,
to: 'verified',
payload: transactions,
});
contains(id) {
return this._allTransactions[id] !== undefined;
}
addVerifiedSignature(signatureObject) {
const transaction = this.findInTransactionPool(signatureObject.transactionId);
if (transaction) {
return transaction.addVerifiedSignature(signatureObject.signature);
async add(incomingTx) {
if (this._allTransactions[incomingTx.id]) {
debug('Received duplicate transaction', incomingTx.id);
return { status: types_1.Status.OK, errors: [] };
}
return {
id: signatureObject.transactionId,
status: check_transactions_1.Status.FAIL,
errors: [new Error('Could not find transaction in transaction pool')],
};
}
existsInTransactionPool(id) {
return Object.keys(this._queues).reduce((previousValue, queueName) => previousValue || this._queues[queueName].exists(id), false);
}
findInTransactionPool(id) {
return Object.keys(this._queues).reduce((previousValue, queueName) => previousValue || this._queues[queueName].index[id], undefined);
}
get queues() {
return this._queues;
}
getProcessableTransactions(limit) {
return this._queues.ready.peekUntil(queueCheckers.returnTrueUntilLimit(limit));
}
removeConfirmedTransactions(transactions) {
const removedTransactions = this.removeTransactionsFromQueues(Object.keys(this._queues), queueCheckers.checkTransactionForId(transactions));
const { received, validated, ...otherQueues } = this._queues;
const confirmedTransactionsWithUniqueData = transactions.filter((transaction) => transaction.containsUniqueData);
const removedTransactionsBySenderPublicKeysFromValidatedQueue = this._queues.validated.removeFor(queueCheckers.checkTransactionForSenderPublicKey(transactions));
const removedTransactionsByTypesFromValidatedQueue = this._queues.validated.removeFor(queueCheckers.checkTransactionForTypes(confirmedTransactionsWithUniqueData));
this._queues.received.enqueueMany([
...removedTransactionsBySenderPublicKeysFromValidatedQueue,
...removedTransactionsByTypesFromValidatedQueue,
]);
const removedTransactionsBySenderPublicKeysFromOtherQueues = this.removeTransactionsFromQueues(Object.keys(otherQueues), queueCheckers.checkTransactionForSenderPublicKey(transactions));
const removedTransactionsByTypesFromOtherQueues = this.removeTransactionsFromQueues(Object.keys(otherQueues), queueCheckers.checkTransactionForTypes(confirmedTransactionsWithUniqueData));
this.emit(exports.EVENT_REMOVED_TRANSACTIONS, {
action: exports.ACTION_REMOVE_CONFIRMED_TRANSACTIONS,
payload: removedTransactions,
});
this._queues.validated.enqueueMany([
...removedTransactionsBySenderPublicKeysFromOtherQueues,
...removedTransactionsByTypesFromOtherQueues,
]);
}
reverifyTransactionsFromSenders(senderPublicKeys) {
const { received, validated, ...otherQueues } = this._queues;
const senderProperty = 'senderPublicKey';
const removedTransactionsBySenderPublicKeysFromValidatedQueue = this._queues.validated.removeFor(queueCheckers.checkTransactionPropertyForValues(senderPublicKeys, senderProperty));
this._queues.received.enqueueMany(removedTransactionsBySenderPublicKeysFromValidatedQueue);
const removedTransactionsBySenderPublicKeysFromOtherQueues = this.removeTransactionsFromQueues(Object.keys(otherQueues), queueCheckers.checkTransactionPropertyForValues(senderPublicKeys, senderProperty));
this._queues.validated.enqueueMany(removedTransactionsBySenderPublicKeysFromOtherQueues);
}
validateTransactionAgainstTransactionsInPool(transaction) {
return transaction.verifyAgainstOtherTransactions([
...this.queues.ready.transactions,
...this.queues.pending.transactions,
...this.queues.verified.transactions,
]);
}
addTransactionToQueue(queueName, transaction) {
if (this.existsInTransactionPool(transaction.id)) {
return {
isFull: false,
alreadyExists: true,
queueName,
};
incomingTx.feePriority = this._calculateFeePriority(incomingTx);
if (incomingTx.feePriority < this._minEntranceFeePriority) {
const error = new errors_1.TransactionPoolError(`Rejecting transaction due to failed minimum entrance fee priority requirement`, incomingTx.id, '.fee', incomingTx.feePriority.toString(), this._minEntranceFeePriority.toString());
return { status: types_1.Status.FAIL, errors: [error] };
}
if (this._queues[queueName].size() >= this._maxTransactionsPerQueue) {
const lowestFeePriorityTrx = this._feePriorityQueue.peek();
if (Object.keys(this._allTransactions).length >= this._maxTransactions &&
lowestFeePriorityTrx &&
incomingTx.feePriority <= lowestFeePriorityTrx.key) {
const error = new errors_1.TransactionPoolError(`Rejecting transaction due to fee priority when the pool is full`, incomingTx.id, '.fee', incomingTx.feePriority.toString(), lowestFeePriorityTrx.key.toString());
return { status: types_1.Status.FAIL, errors: [error] };
}
const incomingTxAddress = lisk_cryptography_1.getAddressFromPublicKey(incomingTx.senderPublicKey);
const transactionsResponses = await this._applyFunction([incomingTx]);
const txStatus = this._getStatus(transactionsResponses[0]);
if (txStatus === types_1.TransactionStatus.INVALID) {
return { status: types_1.Status.FAIL, errors: transactionsResponses[0].errors };
}
const exceededTransactionsCount = Object.keys(this._allTransactions).length - this._maxTransactions;
if (exceededTransactionsCount >= 0) {
const isEvicted = this._evictUnprocessable();
if (!isEvicted) {
this._evictProcessable();
}
}
if (!this._transactionList[incomingTxAddress]) {
this._transactionList[incomingTxAddress] = new transaction_list_1.TransactionList(incomingTxAddress, {
maxSize: this._maxTransactionsPerAccount,
minReplacementFeeDifference: this._minReplacementFeeDifference,
});
}
const { added, removedID, reason } = this._transactionList[incomingTxAddress].add(incomingTx, txStatus === types_1.TransactionStatus.PROCESSABLE);
if (!added) {
return {
isFull: true,
alreadyExists: false,
queueName,
status: types_1.Status.FAIL,
errors: [new errors_1.TransactionPoolError(reason, incomingTx.id)],
};
}
transaction.receivedAt = new Date();
this._queues[queueName].enqueueOne(transaction);
this.emit(exports.EVENT_ADDED_TRANSACTIONS, {
action: exports.ACTION_ADD_TRANSACTIONS,
to: queueName,
payload: [transaction],
});
if (queueName === 'verified' || queueName === 'pending') {
this.emit(exports.EVENT_VERIFIED_TRANSACTION_ONCE, {
action: queueName === 'verified'
? exports.ACTION_ADD_VERIFIED_TRANSACTIONS
: exports.ACTION_ADD_PENDING_TRANSACTIONS,
payload: [transaction],
if (removedID) {
debug('Removing from transaction pool with id', removedID);
const removedTx = this._allTransactions[removedID];
delete this._allTransactions[removedID];
this.events.emit(exports.events.EVENT_TRANSACTION_REMOVED, {
id: removedTx.id,
nonce: removedTx.nonce.toString(),
senderPublicKey: removedTx.senderPublicKey,
reason: 'Transaction List executed remove',
});
}
return {
isFull: false,
alreadyExists: false,
queueName,
};
incomingTx.receivedAt = new Date();
this._allTransactions[incomingTx.id] = incomingTx;
this._feePriorityQueue.push(this._calculateFeePriority(incomingTx), incomingTx.id);
return { status: types_1.Status.OK, errors: [] };
}
async expireTransactions() {
const expiredTransactions = this.removeTransactionsFromQueues(Object.keys(this._queues), queueCheckers.checkTransactionForExpiry());
this.emit(exports.EVENT_REMOVED_TRANSACTIONS, {
action: exports.ACTION_EXPIRE_TRANSACTIONS,
payload: expiredTransactions,
});
return expiredTransactions;
remove(tx) {
var _a;
const foundTx = this._allTransactions[tx.id];
if (!foundTx) {
return false;
}
delete this._allTransactions[tx.id];
debug('Removing from transaction pool with id', tx.id);
const senderId = lisk_cryptography_1.getAddressFromPublicKey(foundTx.senderPublicKey);
this._transactionList[senderId].remove(tx.nonce);
if (this._transactionList[senderId].size === 0) {
delete this._transactionList[senderId];
}
this._feePriorityQueue.clear();
for (const txObject of this.getAll()) {
this._feePriorityQueue.push((_a = txObject.feePriority) !== null && _a !== void 0 ? _a : this._calculateFeePriority(txObject), txObject.id);
}
return true;
}
async processVerifiedTransactions() {
const transactionsInReadyQueue = this._queues.ready.size();
const transactionsInVerifiedQueue = this._queues.verified.size();
const processableTransactionsInPendingQueue = this._queues.pending.sizeBy(transaction => transaction.isReady());
if (transactionsInReadyQueue >=
this._verifiedTransactionsProcessingLimitPerInterval ||
(transactionsInVerifiedQueue === 0 &&
processableTransactionsInPendingQueue === 0)) {
return {
passedTransactions: [],
failedTransactions: [],
};
getProcessableTransactions() {
const processableTransactions = {};
for (const address of Object.keys(this._transactionList)) {
const transactions = this._transactionList[address].getProcessable();
if (transactions.length !== 0) {
processableTransactions[address] = [...transactions];
}
}
const additionalTransactionsToProcessLimit = this._verifiedTransactionsProcessingLimitPerInterval -
transactionsInReadyQueue;
const transactionsFromPendingQueueLimit = Math.min(additionalTransactionsToProcessLimit, this._pendingTransactionsProcessingLimit);
const transactionsFromPendingQueue = this._queues.pending
.filter(transaction => transaction.isReady())
.slice(0, transactionsFromPendingQueueLimit);
const additionalVerifiedTransactionsToProcessLimit = additionalTransactionsToProcessLimit -
transactionsFromPendingQueue.length;
const transactionsFromVerifiedQueue = this._queues.verified.peekUntil(queueCheckers.returnTrueUntilLimit(additionalVerifiedTransactionsToProcessLimit));
const transactionsFromReadyQueue = this._queues.ready.peekUntil(queueCheckers.returnTrueUntilLimit(transactionsInReadyQueue));
const toProcessTransactions = [
...transactionsFromReadyQueue,
...transactionsFromPendingQueue,
...transactionsFromVerifiedQueue,
];
const { passedTransactions, failedTransactions, } = await check_transactions_1.checkTransactionsWithPassAndFail(toProcessTransactions, this._processTransactions);
const { received, validated, ...otherQueues } = this._queues;
const removedTransactions = this.removeTransactionsFromQueues(Object.keys(otherQueues), queueCheckers.checkTransactionForId(failedTransactions));
this._queues.ready.enqueueMany(this._queues.ready.removeFor(queueCheckers.checkTransactionForId(passedTransactions)));
this._queues.ready.enqueueMany(this._queues.verified.removeFor(queueCheckers.checkTransactionForId(passedTransactions)));
this._queues.ready.enqueueMany(this._queues.pending.removeFor(queueCheckers.checkTransactionForId(passedTransactions)));
this.emit(exports.EVENT_REMOVED_TRANSACTIONS, {
action: exports.ACTION_PROCESS_VERIFIED_TRANSACTIONS,
payload: removedTransactions,
});
return {
passedTransactions,
failedTransactions,
};
return processableTransactions;
}
removeTransactionsFromQueues(queueNames, condition) {
return queueNames
.map(queueName => this._queues[queueName].removeFor(condition))
.reduce((transactionsAccumulatedFromQueues, transactionsFromCurrentQueue) => transactionsAccumulatedFromQueues.concat(transactionsFromCurrentQueue), []);
_calculateFeePriority(trx) {
return (trx.fee - trx.minFee) / BigInt(trx.getBytes().length);
}
async validateReceivedTransactions() {
if (this.queues.validated.size() >= this._maxTransactionsPerQueue ||
this.queues.received.size() === 0) {
return {
passedTransactions: [],
failedTransactions: [],
};
_getStatus(txResponse) {
if (txResponse.status === types_1.Status.OK) {
debug('Received PROCESSABLE transaction');
return types_1.TransactionStatus.PROCESSABLE;
}
const toValidateTransactions = this._queues.received.peekUntil(queueCheckers.returnTrueUntilLimit(this._receivedTransactionsProcessingLimitPerInterval));
const { passedTransactions, failedTransactions, } = await check_transactions_1.checkTransactionsWithPassAndFail(toValidateTransactions, this._validateTransactions);
const removedTransactions = this._queues.received.removeFor(queueCheckers.checkTransactionForId(failedTransactions));
this._queues.validated.enqueueMany(this._queues.received.removeFor(queueCheckers.checkTransactionForId(passedTransactions)));
this.emit(exports.EVENT_REMOVED_TRANSACTIONS, {
action: exports.ACTION_VALIDATE_RECEIVED_TRANSACTIONS,
payload: removedTransactions,
const txResponseErrors = txResponse.errors;
if (txResponse.errors.length === 1 &&
txResponseErrors[0].dataPath === '.nonce' &&
txResponseErrors[0].actual &&
txResponseErrors[0].expected &&
BigInt(txResponseErrors[0].actual) > BigInt(txResponseErrors[0].expected)) {
debug('Received UNPROCESSABLE transaction');
return types_1.TransactionStatus.UNPROCESSABLE;
}
debug('Received INVALID transaction');
return types_1.TransactionStatus.INVALID;
}
_evictUnprocessable() {
const unprocessableFeePriorityHeap = new min_heap_1.MinHeap();
for (const txList of Object.values(this._transactionList)) {
const unprocessableTransactions = txList.getUnprocessable();
for (const unprocessableTx of unprocessableTransactions) {
unprocessableFeePriorityHeap.push(unprocessableTx.feePriority, unprocessableTx);
}
}
if (unprocessableFeePriorityHeap.count < 1) {
return false;
}
const evictedTransaction = unprocessableFeePriorityHeap.pop();
if (!evictedTransaction) {
return false;
}
this.events.emit(exports.events.EVENT_TRANSACTION_REMOVED, {
id: evictedTransaction.value.id,
nonce: evictedTransaction.value.nonce.toString(),
senderPublicKey: evictedTransaction.value.senderPublicKey,
reason: 'Pool exceeded the size limit',
});
return {
passedTransactions,
failedTransactions,
};
return this.remove(evictedTransaction.value);
}
async verifyValidatedTransactions() {
if (this.queues.verified.size() >= this._maxTransactionsPerQueue ||
this.queues.validated.size() === 0) {
return {
passedTransactions: [],
failedTransactions: [],
pendingTransactions: [],
};
_evictProcessable() {
const processableFeePriorityHeap = new min_heap_1.MinHeap();
for (const txList of Object.values(this._transactionList)) {
const processableTransactions = txList.getProcessable();
if (processableTransactions.length) {
const processableTransactionWithHighestNonce = processableTransactions[processableTransactions.length - 1];
processableFeePriorityHeap.push(processableTransactionWithHighestNonce.feePriority, processableTransactionWithHighestNonce);
}
}
const toVerifyTransactions = this._queues.validated.peekUntil(queueCheckers.returnTrueUntilLimit(this._validatedTransactionsProcessingLimitPerInterval));
const { failedTransactions, pendingTransactions, passedTransactions, } = await check_transactions_1.checkTransactionsWithPassFailAndPending(toVerifyTransactions, this._verifyTransactions);
const removedTransactions = this._queues.validated.removeFor(queueCheckers.checkTransactionForId(failedTransactions));
this._queues.verified.enqueueMany(this._queues.validated.removeFor(queueCheckers.checkTransactionForId(passedTransactions)));
this._queues.pending.enqueueMany(this._queues.validated.removeFor(queueCheckers.checkTransactionForId(pendingTransactions)));
this.emit(exports.EVENT_REMOVED_TRANSACTIONS, {
action: exports.ACTION_VERIFY_VALIDATED_TRANSACTIONS,
payload: removedTransactions,
if (processableFeePriorityHeap.count < 1) {
return false;
}
const evictedTransaction = processableFeePriorityHeap.pop();
if (!evictedTransaction) {
return false;
}
this.events.emit(exports.events.EVENT_TRANSACTION_REMOVED, {
id: evictedTransaction.value.id,
nonce: evictedTransaction.value.nonce.toString(),
senderPublicKey: evictedTransaction.value.senderPublicKey,
reason: 'Pool exceeded the size limit',
});
const transactionsVerifiedForFirstTime = [
...pendingTransactions,
...passedTransactions,
].filter(transaction => transaction.verifiedOnce === false);
transactionsVerifiedForFirstTime.forEach(transaction => delete transaction.verifiedOnce);
this.emit(exports.EVENT_VERIFIED_TRANSACTION_ONCE, {
action: exports.ACTION_VERIFY_VALIDATED_TRANSACTIONS,
payload: transactionsVerifiedForFirstTime,
});
return {
passedTransactions,
failedTransactions,
pendingTransactions,
};
return this.remove(evictedTransaction.value);
}
async _reorganize() {
for (const txList of Object.values(this._transactionList)) {
const promotableTransactions = txList.getPromotable();
if (!promotableTransactions.length) {
continue;
}
const processableTransactions = txList.getProcessable();
const allTransactions = [
...processableTransactions,
...promotableTransactions,
];
const applyResults = await this._applyFunction(allTransactions);
const successfulTransactionIds = [];
let nonProcessableIds = [];
let firstInvalidTransactionId;
for (const result of applyResults) {
const txApplyStatus = this._getStatus(result);
if (txApplyStatus === types_1.TransactionStatus.INVALID) {
firstInvalidTransactionId = result.id;
break;
}
if (txApplyStatus === types_1.TransactionStatus.UNPROCESSABLE) {
nonProcessableIds.push(result.id);
}
successfulTransactionIds.push(result.id);
}
const trxsToPromote = successfulTransactionIds.filter(id => !nonProcessableIds.includes(id));
txList.promote(promotableTransactions.filter(tx => trxsToPromote.includes(tx.id)));
const invalidTransaction = firstInvalidTransactionId
? allTransactions.find(tx => tx.id == firstInvalidTransactionId)
: undefined;
if (invalidTransaction) {
for (const tx of allTransactions) {
if (tx.nonce >= invalidTransaction.nonce) {
this.events.emit(exports.events.EVENT_TRANSACTION_REMOVED, {
id: tx.id,
nonce: tx.nonce.toString(),
senderPublicKey: tx.senderPublicKey,
reason: `Invalid transaction ${invalidTransaction.id}`,
});
this.remove(tx);
}
}
}
}
}
async _expire() {
for (const transaction of Object.values(this._allTransactions)) {
const timeDifference = Math.round(Math.abs(transaction.receivedAt.getTime() - new Date().getTime()));
if (timeDifference > this._transactionExpiryTime) {
this.events.emit(exports.events.EVENT_TRANSACTION_REMOVED, {
id: transaction.id,
nonce: transaction.nonce.toString(),
senderPublicKey: transaction.senderPublicKey,
reason: 'Transaction exceeded the expiry time',
});
this.remove(transaction);
}
}
}
}
exports.TransactionPool = TransactionPool;
//# sourceMappingURL=transaction_pool.js.map
{
"name": "@liskhq/lisk-transaction-pool",
"version": "0.2.1",
"version": "0.3.0",
"description": "Transaction pool library for use with Lisk-related software",

@@ -26,3 +26,2 @@ "author": "Lisk Foundation <admin@lisk.io>, lightcurve GmbH <admin@lightcurve.io>",

"scripts": {
"transpile": "tsc",
"clean": "./scripts/clean.sh",

@@ -32,40 +31,31 @@ "format": "prettier --write '**/*'",

"lint:fix": "npm run lint -- --fix",
"test": "TS_NODE_PROJECT=./test/tsconfig.json nyc mocha test/{,/**/}/*.ts",
"test": "jest",
"test:watch": "npm test -- --watch",
"test:watch:min": "npm run test:watch -- --reporter=min",
"test:node": "npm run build:check",
"cover": "if [ -z $JENKINS_HOME ]; then npm run cover:local; else npm run cover:ci; fi",
"cover:base": "nyc report",
"cover:local": "npm run cover:base -- --reporter=html --reporter=text",
"cover:ci": "npm run cover:base -- --reporter=text",
"prebuild:node": "rm -r dist-node/* || mkdir dist-node || true",
"build:node": "npm run transpile",
"prebuild": "npm run prebuild:node",
"build": "npm run build:node",
"prebuild": "rm -r dist-node/* || mkdir dist-node || true",
"build": "tsc",
"build:check": "node -e \"require('./dist-node')\"",
"prepublishOnly": "npm run lint && npm test && npm run build && npm run build:check"
},
"dependencies": {
"@liskhq/lisk-cryptography": "^2.5.0",
"debug": "4.1.1"
},
"devDependencies": {
"@types/chai": "4.1.7",
"@types/expect": "1.20.3",
"@types/mocha": "5.2.5",
"@types/node": "^12.12.11",
"@types/sinon-chai": "3.2.2",
"chai": "4.2.0",
"mocha": "5.2.0",
"nyc": "14.1.1",
"@types/debug": "4.1.5",
"@types/jest": "25.1.3",
"@types/jest-when": "2.7.0",
"@types/node": "12.12.11",
"jest": "25.1.0",
"jest-extended": "0.11.5",
"jest-when": "2.7.0",
"prettier": "1.19.1",
"sinon": "7.2.3",
"sinon-chai": "3.3.0",
"source-map-support": "0.5.10",
"ts-node": "8.5.2",
"tsconfig-paths": "3.8.0",
"tslint": "5.20.1",
"source-map-support": "0.5.16",
"ts-jest": "25.2.1",
"ts-node": "8.6.2",
"tsconfig-paths": "3.9.0",
"tslint": "6.0.0",
"tslint-config-prettier": "1.18.0",
"tslint-immutable": "6.0.1",
"typescript": "3.7.2"
},
"dependencies": {
"@liskhq/lisk-cryptography": "2.4.2"
"typescript": "3.8.3"
}
}

@@ -27,15 +27,3 @@ # lisk-transaction-pool

---
Copyright © 2016-2019 Lisk Foundation
Copyright © 2015 Crypti
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[lisk core github]: https://github.com/LiskHQ/lisk
[lisk documentation site]: https://lisk.io/documentation/lisk-elements

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc