UNPKG

int-cli

Version:

INT is the new generation of bottom-up created system of IoT and blockchain

501 lines (499 loc) 20.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const chain_1 = require("./chain"); const error_code_1 = require("../error_code"); const events_1 = require("events"); const LRUCache_1 = require("../lib/LRUCache"); const bignumber_js_1 = require("bignumber.js"); const util_1 = require("util"); const calculate_tx_limit_1 = require("../executor/calculate_tx_limit"); var SyncOptType; (function (SyncOptType) { SyncOptType[SyncOptType["updateTip"] = 0] = "updateTip"; SyncOptType[SyncOptType["popTx"] = 1] = "popTx"; SyncOptType[SyncOptType["addTx"] = 2] = "addTx"; })(SyncOptType || (SyncOptType = {})); class PendingTransactions extends events_1.EventEmitter { constructor(options) { super(); this.m_queueOpt = []; this.m_transactions = []; this.m_orphanTx = new Map(); this.m_mapNonce = new Map(); this.m_logger = options.logger; this.m_storageManager = options.storageManager; this.m_txLiveTime = options.overtime; this.m_handler = options.handler; this.m_maxPengdingCount = options.maxCount; this.m_maxphanTxCount = options.warnCount; this.m_txRecord = new LRUCache_1.LRUCache(this.m_maxPengdingCount); this.m_isPeer = options.isPeer; this.m_maxTxLimit = new bignumber_js_1.BigNumber(options.maxTxLimit); this.m_minTxLimit = new bignumber_js_1.BigNumber(options.minTxLimit); this.m_maxTxPrice = new bignumber_js_1.BigNumber(options.maxTxPrice); this.m_minTxPrice = new bignumber_js_1.BigNumber(options.minTxPrice); this.m_calcTxLimit = new calculate_tx_limit_1.CalcuateLimit(); } on(event, listener) { return super.on(event, listener); } once(event, listener) { return super.once(event, listener); } async addTransaction(tx) { let latest = this.m_txRecord.get(tx.hash); if (latest && Date.now() < latest) { this.m_logger.warn(`addTransaction failed, add too frequently,hash=${tx.hash}`); return error_code_1.ErrorCode.RESULT_TX_EXIST; } this.m_txRecord.set(tx.hash, Date.now() + 60 * 1000); let nCount = this._getPendingCount() + this.m_queueOpt.length; if (nCount >= this.m_maxPengdingCount) { let orphanTxcount = this._getOrphanTxCount(); this.m_logger.warn(`pengding count=${nCount}, maxPengdingCount=${this.m_maxPengdingCount},orphanTxcount=${orphanTxcount}`); return error_code_1.ErrorCode.RESULT_OUT_OF_MEMORY; } this.m_logger.debug(`addTransaction, txhash=${tx.hash}, nonce=${tx.nonce}, address=${tx.address}`); let bt = this.baseMethodChecker(tx); if (bt) { return bt; } const checker = this.m_handler.getTxPendingChecker(tx.method); if (!checker) { this.m_logger.error(`txhash=${tx.hash} method=${tx.method} has no match listener`); return error_code_1.ErrorCode.RESULT_TX_CHECKER_ERROR; } const err = checker(tx); if (err) { this.m_logger.error(`txhash=${tx.hash} checker error ${err}`); return err; } let retCode = await this._onCheck({ tx, ct: Date.now() }); if (retCode) { return retCode; } let opt = { _type: SyncOptType.addTx, param: { tx, ct: Date.now(), broadcastTimes: 0 } }; this._addPendingOpt(opt); return error_code_1.ErrorCode.RESULT_OK; } baseMethodChecker(tx) { if (util_1.isNullOrUndefined(tx.limit) || util_1.isNullOrUndefined(tx.price) || util_1.isNullOrUndefined(tx.value)) { return error_code_1.ErrorCode.RESULT_INVALID_PARAM; } if (!bignumber_js_1.BigNumber.isBigNumber(tx.limit) || !bignumber_js_1.BigNumber.isBigNumber(tx.price) || !bignumber_js_1.BigNumber.isBigNumber(tx.value)) { return error_code_1.ErrorCode.RESULT_NOT_BIGNUMBER; } if (!tx.limit.isInteger() || !tx.price.isInteger() || !tx.value.isInteger()) { return error_code_1.ErrorCode.RESULT_NOT_INTEGER; } if (tx.limit.isNegative() || tx.price.isNegative() || tx.value.isNegative()) { return error_code_1.ErrorCode.RESULT_CANT_BE_LESS_THAN_ZERO; } if (tx.price.gt(this.m_maxTxPrice)) { return error_code_1.ErrorCode.RESULT_PRICE_TOO_BIG; } if (tx.price.lt(this.m_minTxPrice)) { return error_code_1.ErrorCode.RESULT_PRICE_TOO_SMALL; } if (tx.limit.gt(this.m_maxTxLimit)) { return error_code_1.ErrorCode.RESULT_LIMIT_TOO_BIG; } if (tx.value.gt(new bignumber_js_1.BigNumber(1e+36))) { return error_code_1.ErrorCode.RESULT_OUT_OF_RANGE; } let txLimit = this.m_calcTxLimit.calcTxLimit(tx.method, tx.input); if (tx.limit.lt(this.m_minTxLimit) || tx.limit.lt(txLimit)) { return error_code_1.ErrorCode.RESULT_LIMIT_TOO_SMALL; } return error_code_1.ErrorCode.RESULT_OK; } popTransaction() { if (this.m_transactions.length > 0) { return this.m_transactions[0].tx; } else { return undefined; } } async updateTipBlock(header) { let svr = await this.m_storageManager.getSnapshotView(header.hash); if (svr.err) { this.m_logger.error(`updateTipBlock getSnapshotView failed, errcode=${svr.err},hash=${header.hash},number=${header.number}`); return svr.err; } if (this.m_curHeader) { this.m_storageManager.releaseSnapshotView(this.m_curHeader.hash); } this.m_curHeader = header; this.m_storageView = svr.storage; // 每10个块清除一次 tx record的数据 if (header.number % 10 == 0) { let txRecord; txRecord = this.m_txRecord; this.m_logger.info(`begin remove timeout tx of txRecord, block number=${header.number}, block hash=${header.hash}`); for (let [hash, value] of txRecord.m_memValue) { let t = Date.now(); if (t >= value[0]) { this.m_logger.debug(`txRecord remove timeout tx hash ${hash}, time ${value[0]}, date now ${t}`); this.m_txRecord.remove(hash); } } this.m_logger.info(`finish remove timeout tx of txRecord, block number=${header.number}, block hash=${header.hash}`); } //每100个区块进行一次孤块序列超时判断 if (header.number % 100 == 0) { this.m_logger.info(`clear timeout tx of orphanTxs,number=${header.number},hash=${header.hash}`); for (let [address, l] of this.m_orphanTx) { for (let i = 0; i < l.length; i++) { if (this._isTimeout(l[i])) { l.splice(i, 1); this.m_logger.debug(`clear one timeout orphan tx hash=${header.hash}`); i--; } } if (l.length === 0) { this.m_orphanTx.delete(address); this.m_logger.debug(`remove one empty orphan address address=${address}`); } } } this._addPendingOpt({ _type: SyncOptType.updateTip, param: undefined }); return error_code_1.ErrorCode.RESULT_OK; } init() { return error_code_1.ErrorCode.RESULT_OK; } uninit() { if (this.m_curHeader) { this.m_storageManager.releaseSnapshotView(this.m_curHeader.hash); delete this.m_storageView; delete this.m_curHeader; } this.m_mapNonce.clear(); } _isExist(tx) { for (let t of this.m_transactions) { if (t.tx.hash === tx.hash) { return true; } } for (let opt of this.m_queueOpt) { if (opt._type === SyncOptType.addTx) { if (opt.param.tx.hash === tx.hash) { return true; } } } if (!this.m_orphanTx.get(tx.address)) { return false; } for (let orphan of this.m_orphanTx.get(tx.address)) { if (tx.hash === orphan.tx.hash) { return true; } } return false; } async _addPendingOpt(opt) { if (opt._type === SyncOptType.updateTip) { for (let i = 0; i < this.m_queueOpt.length; i++) { if (this.m_queueOpt[i]._type === SyncOptType.addTx) { break; } else if (this.m_queueOpt[i]._type === SyncOptType.updateTip) { this.m_queueOpt.splice(i, 1); break; } } this.m_queueOpt.unshift(opt); } else if (opt._type === SyncOptType.addTx) { this.m_queueOpt.push(opt); } if (this.m_currAdding) { return; } while (this.m_queueOpt.length > 0) { this.m_currAdding = this.m_queueOpt.shift(); if (this.m_currAdding._type === SyncOptType.updateTip) { let pos = 0; for (pos = 0; pos < this.m_queueOpt.length; pos++) { if (this.m_queueOpt[pos]._type === SyncOptType.addTx) { break; } } for (let i = 0; i < this.m_transactions.length; i++) { this.m_queueOpt.splice(i + pos, 0, { _type: SyncOptType.addTx, param: this.m_transactions[i] }); } this.m_mapNonce = new Map(); this.m_transactions = []; } else if (this.m_currAdding._type === SyncOptType.addTx) { await this._addTx(this.m_currAdding.param); } this.m_currAdding = undefined; } } async _onCheck(txTime, txOld) { return error_code_1.ErrorCode.RESULT_OK; } async _onAddedTx(txTime, txOld) { if (!txOld) { this.m_mapNonce.set(txTime.tx.address, txTime.tx.nonce); } this.m_logger.debug(`_onAddedTx txhash ${txTime.tx.hash}, isPeer ${this.m_isPeer}, broadcast times ${txTime.broadcastTimes}, isBroadcast ${Date.now() >= txTime.ct + txTime.broadcastTimes * 60 * 1000}`); if (this.m_isPeer && Date.now() >= txTime.ct + txTime.broadcastTimes * 60 * 1000) { this.emit('txAdded', txTime.tx); return error_code_1.ErrorCode.RESULT_OK; } return error_code_1.ErrorCode.RESULT_CANCELED; } async _addTx(txTime) { if (this._isTimeout(txTime)) { this.m_logger.warn(`_addTx tx timeout, txhash=${txTime.tx.hash}`); return error_code_1.ErrorCode.RESULT_TIMEOUT; } let address = txTime.tx.address; let ret = await this.getStorageNonce(address); if (ret.err) { this.m_logger.error(`_addTx getNonce nonce error ${ret.err} address=${address}, txhash=${txTime.tx.hash}`); return ret.err; } if (ret.nonce + 1 > txTime.tx.nonce) { // this.m_logger.warn(`_addTx nonce small storagenonce=${ret.nonce!},txnonce=${txTime.tx.nonce}, txhash=${txTime.tx.hash}`); return error_code_1.ErrorCode.RESULT_OK; } let { err, nonce } = await this.getNonce(address); this.m_logger.debug(`_addTx, nonce=${nonce}, txNonce=${txTime.tx.nonce}, txhash=${txTime.tx.hash}, address=${txTime.tx.address}`); if (nonce + 1 === txTime.tx.nonce) { let retCode = await this._onCheck(txTime); if (retCode) { return retCode; } this._addToQueue(txTime, -1); let returnCode = await this._onAddedTx(txTime); if (!returnCode) { txTime.broadcastTimes = txTime.broadcastTimes + 1; } // //只有peer才会进行操作 // if(this.m_isPeer){ await this._scanOrphan(address); // } return error_code_1.ErrorCode.RESULT_OK; } if (nonce + 1 < txTime.tx.nonce) { let returnCode = await this._addToOrphanMayNonceExist(txTime); if (!returnCode) { this.m_txRecord.set(txTime.tx.hash, Date.now() + 60 * 60 * 1000); } return returnCode; } return await this._addToQueueMayNonceExist(txTime); } // 同个address的两个相同nonce的tx存在,且先前的也还没有入链 async _checkSmallNonceTx(txNew, txOld) { return error_code_1.ErrorCode.RESULT_ERROR_NONCE_IN_TX; } // 获取mem中的nonce值 async getNonce(address) { if (this.m_mapNonce.has(address)) { return { err: error_code_1.ErrorCode.RESULT_OK, nonce: this.m_mapNonce.get(address) }; } else { return await this.getStorageNonce(address); } } // 获取mem中的未处理的交易 async getPendingTransactions() { let pendingTransactions = []; for (let opt of this.m_queueOpt) { if (opt._type === SyncOptType.addTx) { pendingTransactions.push(opt.param); } } pendingTransactions = pendingTransactions.concat(this.m_transactions); for (let [address, l] of this.m_orphanTx) { pendingTransactions = pendingTransactions.concat(l); } return { err: error_code_1.ErrorCode.RESULT_OK, pendingTransactions: pendingTransactions }; } async getStorageNonce(s) { try { let dbr = await this.m_storageView.getReadableDataBase(chain_1.Chain.dbSystem); if (dbr.err) { this.m_logger.error(`get system database failed ${dbr.err}`); return { err: dbr.err }; } let nonceTableInfo = await dbr.value.getReadableKeyValue(chain_1.Chain.kvNonce); if (nonceTableInfo.err) { this.m_logger.error(`getStorageNonce, getReadableKeyValue failed,errcode=${nonceTableInfo.err}`); return { err: nonceTableInfo.err }; } let ret = await nonceTableInfo.kv.get(s); if (ret.err) { if (ret.err === error_code_1.ErrorCode.RESULT_NOT_FOUND) { return { err: error_code_1.ErrorCode.RESULT_OK, nonce: -1 }; } return { err: ret.err }; } return { err: error_code_1.ErrorCode.RESULT_OK, nonce: ret.value }; } catch (error) { this.m_logger.error(`getStorageNonce exception, error=${error},address=${s}`); return { err: error_code_1.ErrorCode.RESULT_EXCEPTION }; } } _addToOrphan(txTime) { let s = txTime.tx.address; let l; if (this.m_orphanTx.has(s)) { l = this.m_orphanTx.get(s); } else { l = new Array(); this.m_orphanTx.set(s, l); } if (l.length === 0) { l.push(txTime); } else { for (let i = 0; i < l.length; i++) { if (txTime.tx.nonce < l[i].tx.nonce) { l.splice(i, 0, txTime); return; } } l.push(txTime); } } async _scanOrphan(s) { if (!this.m_orphanTx.has(s)) { return; } let l = this.m_orphanTx.get(s); let { err, nonce } = await this.getNonce(s); while (true) { if (l.length === 0) { this.m_orphanTx.delete(s); break; } if (this._isTimeout(l[0])) { l.shift(); continue; } if (nonce + 1 === l[0].tx.nonce) { let txTime = l.shift(); this._addPendingOpt({ _type: SyncOptType.addTx, param: txTime }); } break; } } _isTimeout(txTime) { return Date.now() >= txTime.ct + this.m_txLiveTime * 1000; } _addToQueue(txTime, pos) { if (pos === -1) { this.m_transactions.push(txTime); } else { this.m_transactions.splice(pos, 0, txTime); } this.m_txRecord.set(txTime.tx.hash, Date.now() + 60 * 60 * 1000); } _getPendingCount() { let count = this.m_transactions.length; for (let [address, l] of this.m_orphanTx) { count += l.length; } return count; } _getOrphanTxCount() { let count = 0; for (let [address, l] of this.m_orphanTx) { count += l.length; } return count; } async _addToQueueMayNonceExist(txTime) { for (let i = 0; i < this.m_transactions.length; i++) { if (this.m_transactions[i].tx.address === txTime.tx.address && this.m_transactions[i].tx.nonce === txTime.tx.nonce) { let txOld = this.m_transactions[i]; if (this._isTimeout(this.m_transactions[i])) { let retCode = await this._onCheck(txTime, txOld); if (retCode) { return retCode; } this.m_transactions.splice(i, 1); this._addToQueue(txTime, i); let returnCode = await this._onAddedTx(txTime, txOld); if (!returnCode) { txTime.broadcastTimes = txTime.broadcastTimes + 1; } return error_code_1.ErrorCode.RESULT_OK; } let _err = await this._checkSmallNonceTx(txTime.tx, this.m_transactions[i].tx); if (_err === error_code_1.ErrorCode.RESULT_OK) { let retCode = await this._onCheck(txTime, txOld); if (retCode) { return retCode; } this.m_transactions.splice(i, 1); this._addToQueue(txTime, i); let returnCode = await this._onAddedTx(txTime, txOld); if (!returnCode) { txTime.broadcastTimes = txTime.broadcastTimes + 1; } return error_code_1.ErrorCode.RESULT_OK; } return _err; } } return error_code_1.ErrorCode.RESULT_ERROR_NONCE_IN_TX; } async _addToOrphanMayNonceExist(txTime) { let s = txTime.tx.address; let l; //判断孤交易池是否已经满了 let orphanNumber = this._getOrphanTxCount(); if (orphanNumber >= this.m_maxphanTxCount) { this.m_logger.error(`orphan tx pool is full,orphan number=${orphanNumber},m_transactionsNumber=${this.m_transactions.length}`); return error_code_1.ErrorCode.RESULT_OUT_OF_MEMORY; } else { this.m_logger.debug(`add orphan tx ,orphan number=${orphanNumber},m_transactionsNumber=${this.m_transactions.length}`); } if (this.m_orphanTx.has(s)) { l = this.m_orphanTx.get(s); } else { l = new Array(); this.m_orphanTx.set(s, l); } if (l.length === 0) { l.push(txTime); return error_code_1.ErrorCode.RESULT_OK; } for (let i = 0; i < l.length; i++) { if (txTime.tx.nonce === l[i].tx.nonce) { let txOld = l[i].tx; if (this._isTimeout(l[i])) { l.splice(i, 1, txTime); return error_code_1.ErrorCode.RESULT_OK; } let _err = await this._checkSmallNonceTx(txTime.tx, l[i].tx); if (_err === error_code_1.ErrorCode.RESULT_OK) { l.splice(i, 1, txTime); return error_code_1.ErrorCode.RESULT_OK; } return _err; } if (txTime.tx.nonce < l[i].tx.nonce) { l.splice(i, 0, txTime); return error_code_1.ErrorCode.RESULT_OK; } } l.push(txTime); return error_code_1.ErrorCode.RESULT_OK; } } exports.PendingTransactions = PendingTransactions;