()////
Transaction Controller is an aggregate of sub-controllers and trackers exposed to the MetaMask controller.
交易控制器是暴露于metamask控制器的子控制器和跟踪器的集合
- txStateManager responsible for the state of a transaction and storing the transaction负责交易状态和存储交易
- pendingTxTracker watching blocks for transactions to be include and emitting confirmed events监视包含交易的块并发出已确认的事件
- txGasUtil gas calculations and safety buffering gas计算和安全缓存
- nonceTracker calculating nonces 计算nonce
tx-state-manager.js
下面代码的构造函数中this的很多调用方法都是在这里定义的
const extend = require('xtend')const EventEmitter = require('events') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const log = require('loglevel') const txStateHistoryHelper = require('./lib/tx-state-history-helper') const createId = require('../../lib/random-id') const { getFinalStates } = require('./lib/util') /** TransactionStateManager is responsible for the state of a transaction and storing the transaction it also has some convenience methods for finding subsets of transactions * *STATUS METHODS statuses: 交易的状态 - `'unapproved'` the user has not responded - `'rejected'` the user has responded no! - `'approved'` the user has approved the tx - `'signed'` the tx is signed - `'submitted'` the tx is sent to a server - `'confirmed'` the tx has been included in a block. - `'failed'` the tx failed for some reason, included on tx data. - `'dropped'` the tx nonce was already used @param opts {object} @param {object} [opts.initState={ transactions: [] }] initial transactions list with the key transaction {array} @param {number} [opts.txHistoryLimit] limit for how many finished transactions can hang around in state @param {function} opts.getNetwork return network number @class */ class TransactionStateManager extends EventEmitter { constructor ({ initState, txHistoryLimit, getNetwork }) { super() this.store = new ObservableStore( extend({ transactions: [], }, initState)) this.txHistoryLimit = txHistoryLimit this.getNetwork = getNetwork } /** @param opts {object} - the object to use when overwriting defaults @returns {txMeta} the default txMeta object */ generateTxMeta (opts) {//一开始生成tx return extend({ id: createId(), time: (new Date()).getTime(), status: 'unapproved',//user还没有response metamaskNetworkId: this.getNetwork(),//此时连接的网络 loadingDefaults: true, }, opts) } /** @returns {array} of txMetas that have been filtered for only the current network */ getTxList () { const network = this.getNetwork() const fullTxList = this.getFullTxList() return fullTxList.filter((txMeta) => txMeta.metamaskNetworkId === network) //只得到network为现在连接的这个网络的交易 } /** @returns {array} of all the txMetas in store */ getFullTxList () {//得到所有的交易 return this.store.getState().transactions } /** @returns {array} the tx list whos status is unapproved */ getUnapprovedTxList () {//得到状态为unapproved的交易 const txList = this.getTxsByMetaData('status', 'unapproved') return txList.reduce((result, tx) => { result[tx.id] = tx//根据id来索引得到交易信息 return result }, {}) } /** @param [address] {string} - hex prefixed address to sort the txMetas for [optional] @returns {array} the tx list whos status is submitted if no address is provide returns all txMetas who's status is submitted for the current network */ getPendingTransactions (address) {//得到状态为submitted而且from为address的交易 const opts = { status: 'submitted' } if (address) opts.from = address return this.getFilteredTxList(opts) } /** @param [address] {string} - hex prefixed address to sort the txMetas for [optional] @returns {array} the tx list whos status is confirmed if no address is provide returns all txMetas who's status is confirmed for the current network */ getConfirmedTransactions (address) { //得到状态为confirmed而且from为address的交易 const opts = { status: 'confirmed' } if (address) opts.from = address return this.getFilteredTxList(opts) } /** Adds the txMeta to the list of transactions in the store. if the list is over txHistoryLimit it will remove a transaction that is in its final state it will allso add the key `history` to the txMeta with the snap shot of the original object @param txMeta {Object} @returns {object} the txMeta */ addTx (txMeta) { this.once(`${txMeta.id}:signed`, function (txId) { this.removeAllListeners(`${txMeta.id}:rejected`) }) this.once(`${txMeta.id}:rejected`, function (txId) { this.removeAllListeners(`${txMeta.id}:signed`) }) // initialize history txMeta.history = [] // capture initial snapshot of txMeta for history const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) txMeta.history.push(snapshot) const transactions = this.getFullTxList() const txCount = transactions.length const txHistoryLimit = this.txHistoryLimit // checks if the length of the tx history is // longer then desired persistence limit // and then if it is removes only confirmed // or rejected tx's. // not tx's that are pending or unapproved if (txCount > txHistoryLimit - 1) { const index = transactions.findIndex((metaTx) => { return getFinalStates().includes(metaTx.status) }) if (index !== -1) { transactions.splice(index, 1) } } transactions.push(txMeta) this._saveTxList(transactions) return txMeta } /** @param txId {number} @returns {object} the txMeta who matches the given id if none found for the network returns undefined */ getTx (txId) { const txMeta = this.getTxsByMetaData('id', txId)[0] return txMeta } /** updates the txMeta in the list and adds a history entry @param txMeta {Object} - the txMeta to update @param [note] {string} - a note about the update for history */ updateTx (txMeta, note) { // validate txParams if (txMeta.txParams) { if (typeof txMeta.txParams.data === 'undefined') { delete txMeta.txParams.data } this.validateTxParams(txMeta.txParams) } // create txMeta snapshot for history const currentState = txStateHistoryHelper.snapshotFromTxMeta(txMeta) // recover previous tx state obj const previousState = txStateHistoryHelper.replayHistory(txMeta.history) // generate history entry and add to history const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState, note) txMeta.history.push(entry) // commit txMeta to state const txId = txMeta.id const txList = this.getFullTxList() const index = txList.findIndex(txData => txData.id === txId) txList[index] = txMeta this._saveTxList(txList) } /** merges txParams obj onto txMeta.txParams use extend to ensure that all fields are filled @param txId {number} - the id of the txMeta @param txParams {object} - the updated txParams */ updateTxParams (txId, txParams) { const txMeta = this.getTx(txId) txMeta.txParams = extend(txMeta.txParams, txParams) this.updateTx(txMeta, `txStateManager#updateTxParams`) } /** validates txParams members by type @param txParams {object} - txParams to validate */ validateTxParams (txParams) { Object.keys(txParams).forEach((key) => { const value = txParams[key] // validate types switch (key) { case 'chainId': if (typeof value !== 'number' && typeof value !== 'string') throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`) break default: if (typeof value !== 'string') throw new Error(`${key} in txParams is not a string. got: (${value})`) if (!ethUtil.isHexPrefixed(value)) throw new Error(`${key} in txParams is not hex prefixed. got: (${value})`) break } }) } /** @param opts {object} - an object of fields to search for eg: let thingsToLookFor = { to: '0x0..', from: '0x0..', status: 'signed', err: undefined, }
@param [initialList=this.getTxList()] @returns a {array} of txMeta with all options matching */ /* ****************HINT**************** | `err: undefined` is like looking | | for a tx with no err | | so you can also search txs that | | dont have something as well by | | setting the value as undefined | ************************************ this is for things like filtering a the tx list for only tx's from 1 account or for filltering for all txs from one account and that have been 'confirmed' */ getFilteredTxList (opts, initialList) { let filteredTxList = initialList Object.keys(opts).forEach((key) => { filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList) }) return filteredTxList } /** @param key {string} - the key to check @param value - the value your looking for @param [txList=this.getTxList()] {array} - the list to search. default is the txList from txStateManager#getTxList @returns {array} a list of txMetas who matches the search params */ getTxsByMetaData (key, value, txList = this.getTxList()) { return txList.filter((txMeta) => { if (key in txMeta.txParams) { return txMeta.txParams[key] === value } else { return txMeta[key] === value } }) } // get::set status /** @param txId {number} - the txMeta Id @return {string} the status of the tx. */ getTxStatus (txId) { const txMeta = this.getTx(txId) return txMeta.status } /** should update the status of the tx to 'rejected'. @param txId {number} - the txMeta Id */ setTxStatusRejected (txId) { this._setTxStatus(txId, 'rejected') this._removeTx(txId) } /** should update the status of the tx to 'unapproved'. @param txId {number} - the txMeta Id */ setTxStatusUnapproved (txId) { this._setTxStatus(txId, 'unapproved') } /** should update the status of the tx to 'approved'. @param txId {number} - the txMeta Id */ setTxStatusApproved (txId) { this._setTxStatus(txId, 'approved') } /** should update the status of the tx to 'signed'. @param txId {number} - the txMeta Id */ setTxStatusSigned (txId) { this._setTxStatus(txId, 'signed') } /** should update the status of the tx to 'submitted'. and add a time stamp for when it was called @param txId {number} - the txMeta Id */ setTxStatusSubmitted (txId) { const txMeta = this.getTx(txId) txMeta.submittedTime = (new Date()).getTime() this.updateTx(txMeta, 'txStateManager - add submitted time stamp') this._setTxStatus(txId, 'submitted') } /** should update the status of the tx to 'confirmed'. @param txId {number} - the txMeta Id */ setTxStatusConfirmed (txId) { this._setTxStatus(txId, 'confirmed') } /** should update the status of the tx to 'dropped'. @param txId {number} - the txMeta Id */ setTxStatusDropped (txId) { this._setTxStatus(txId, 'dropped') } /** should update the status of the tx to 'failed'. and put the error on the txMeta @param txId {number} - the txMeta Id @param err {erroObject} - error object */ setTxStatusFailed (txId, err) { const txMeta = this.getTx(txId) txMeta.err = { message: err.toString(), rpc: err.value, stack: err.stack, } this.updateTx(txMeta) this._setTxStatus(txId, 'failed') } /** Removes transaction from the given address for the current network from the txList @param address {string} - hex string of the from address on the txParams to remove */ wipeTransactions (address) { // network only tx const txs = this.getFullTxList() const network = this.getNetwork() // Filter out the ones from the current account and network const otherAccountTxs = txs.filter((txMeta) => !(txMeta.txParams.from === address && txMeta.metamaskNetworkId === network)) // Update state this._saveTxList(otherAccountTxs) } // // PRIVATE METHODS // // STATUS METHODS // statuses: // - `'unapproved'` the user has not responded // - `'rejected'` the user has responded no! // - `'approved'` the user has approved the tx // - `'signed'` the tx is signed // - `'submitted'` the tx is sent to a server // - `'confirmed'` the tx has been included in a block. // - `'failed'` the tx failed for some reason, included on tx data. // - `'dropped'` the tx nonce was already used /** @param txId {number} - the txMeta Id @param status {string} - the status to set on the txMeta @emits tx:status-update - passes txId and status @emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta @emits update:badge */ _setTxStatus (txId, status) { const txMeta = this.getTx(txId) txMeta.status = status setTimeout(() => { try { this.updateTx(txMeta, `txStateManager: setting status to ${status}`) this.emit(`${txMeta.id}:${status}`, txId) this.emit(`tx:status-update`, txId, status) if (['submitted', 'rejected', 'failed'].includes(status)) { this.emit(`${txMeta.id}:finished`, txMeta) } this.emit('update:badge') } catch (error) { log.error(error) } }) } /** Saves the new/updated txList. @param transactions {array} - the list of transactions to save */ // Function is intended only for internal use _saveTxList (transactions) { this.store.updateState({ transactions }) } _removeTx (txId) { const transactionList = this.getFullTxList() this._saveTxList(transactionList.filter((txMeta) => txMeta.id !== txId)) } } module.exports = TransactionStateManager
enums.js
说明交易目前的状态
const TRANSACTION_TYPE_CANCEL = 'cancel' //交易被拒绝const TRANSACTION_TYPE_RETRY = 'retry' //重试交易const TRANSACTION_TYPE_STANDARD = 'standard' const TRANSACTION_STATUS_APPROVED = 'approved' //交易被批准module.exports = { TRANSACTION_TYPE_CANCEL, TRANSACTION_TYPE_RETRY, TRANSACTION_TYPE_STANDARD, TRANSACTION_STATUS_APPROVED,}
nonce-tracker.js
之前要知道的知识点:
1)await-semaphore:信号量,用于实现互斥
new Mutex()
An alias for new Semaphore(1)
. Mutex has the same methods as Semaphore.
即new Semaphore(1)的别名,有着相同的方法
import {Mutex} from 'await-semaphore'; var mutex = new Mutex();
new Semaphore(count: number)
Create a new semaphore with the given count.
import {Semaphore} from 'await-semaphore'; var semaphore = new Semaphore(10);
semaphore.acquire(): Promise<() => void>
Acquire the semaphore and returns a promise for the release function. Be sure to handle release for exception case.
semaphore.acquire().then(release => { //critical section... doSomething() .then(res => { //... release(); }) .catch(err => { //... release(); });});
promise style (javascript)
var semaphore = new Semaphore(10); function niceFetch(url) { return semaphore.acquire()//获得信号,阻止别的线程调用该共享资源 .then(release => { return fetch(url)//运行语句 .then(result => { release();//释放信号,让别的线程使用 return result; }); });}
async/await style (typescript)
var semaphore = new Semaphore(10); async function niceFetch(url) { var release = await semaphore.acquire(); var result = await fetch(url); release(); return result;}
/////nonce-tracker.js
这个代码的作用就是得到address账户下一个交易的nonce值
const EthQuery = require('ethjs-query')const assert = require('assert')const Mutex = require('await-semaphore').Mutex/** @param opts {Object} @param {Object} opts.provider a ethereum provider @param {Function} opts.getPendingTransactions a function that returns an array of txMeta whosee status is `submitted` @param {Function} opts.getConfirmedTransactions a function that returns an array of txMeta whose status is `confirmed` @class*/class NonceTracker { constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) { this.provider = provider this.blockTracker = blockTracker this.ethQuery = new EthQuery(provider) this.getPendingTransactions = getPendingTransactions this.getConfirmedTransactions = getConfirmedTransactions this.lockMap = {} } /** @returns {Promise
pending-tx-tracker.js
const EventEmitter = require('events')const log = require('loglevel')const EthQuery = require('ethjs-query')/** Event emitter utility class for tracking the transactions as they go from a pending state to a confirmed (mined in a block) state As well as continues broadcast while in the pending state@param config {object} - non optional configuration object consists of: @param {Object} config.provider - A network provider. @param {Object} config.nonceTracker see nonce tracker @param {function} config.getPendingTransactions a function for getting an array of transactions, @param {function} config.publishTransaction a async function for publishing raw transactions,@class*///通过对pending状态的交易进行nonce,信息,blocknumber等信息进行判断来得到其目前结果class PendingTransactionTracker extends EventEmitter { constructor (config) { super() this.query = new EthQuery(config.provider) this.nonceTracker = config.nonceTracker this.getPendingTransactions = config.getPendingTransactions this.getCompletedTransactions = config.getCompletedTransactions this.publishTransaction = config.publishTransaction this.confirmTransaction = config.confirmTransaction } /** checks the network for signed txs and releases the nonce global lock if it is */ //更新pending 交易的状态 async updatePendingTxs () { // in order to keep the nonceTracker accurate we block it while updating pending transactions const nonceGlobalLock = await this.nonceTracker.getGlobalLock()//得到全局的互斥锁,即该操作此时只能它在进行 try { const pendingTxs = this.getPendingTransactions() await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)))//通过其信息的查看来得到交易txMeta此时的状态,是failed、confirmed、warning } catch (err) { log.error('PendingTransactionTracker - Error updating pending transactions') log.error(err) } nonceGlobalLock.releaseLock() } /** Will resubmit any transactions who have not been confirmed in a block @param block {object} - a block object @emits tx:warning */ //重新再呈递一次之前没有被confirmed的交易,如果再失败,就得到其错误的原因 resubmitPendingTxs (blockNumber) { const pending = this.getPendingTransactions() // only try resubmitting if their are transactions to resubmit if (!pending.length) return pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => { /* Dont marked as failed if the error is a "known" transaction warning "there is already a transaction with the same sender-nonce but higher/same gas price" Also don't mark as failed if it has ever been broadcast successfully. A successful broadcast means it may still be mined. */ const errorMessage = err.message.toLowerCase() const isKnownTx = ( // geth errorMessage.includes('replacement transaction underpriced') || errorMessage.includes('known transaction') || // parity errorMessage.includes('gas price too low to replace') || errorMessage.includes('transaction with the same hash was already imported') || // other errorMessage.includes('gateway timeout') || errorMessage.includes('nonce too low') ) // ignore resubmit warnings, return early if (isKnownTx) return // encountered real error - transition to error state txMeta.warning = { error: errorMessage, message: 'There was an error when resubmitting this transaction.', } this.emit('tx:warning', txMeta, err) })) } /** resubmits the individual txMeta used in resubmitPendingTxs @param txMeta {Object} - txMeta object @param latestBlockNumber {string} - hex string for the latest block number @emits tx:retry @returns txHash {string} */ async _resubmitTx (txMeta, latestBlockNumber) { if (!txMeta.firstRetryBlockNumber) { //其firstRetryBlockNumber若为0,说明其之前还没有进行submit过 this.emit('tx:block-update', txMeta, latestBlockNumber)//就是呈递一下这样的event } const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber //就是txMeta.firstRetryBlockNumber为0,则第一次重试的blocknumber就是最新的blocknumber的值 const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16) //两个区块间的差 const retryCount = txMeta.retryCount || 0 //得到该交易至今retry的次数 // Exponential backoff to limit retries at publishing if (txBlockDistance <= Math.pow(2, retryCount) - 1) return //如果区块间的差值小于2^retryCount -1的话,就先不重试,以免重试频率过高 // Only auto-submit already-signed txs: if (!('rawTx' in txMeta)) return //有'rawTx'的交易才retry的 const rawTx = txMeta.rawTx const txHash = await this.publishTransaction(rawTx)//通过publish签名后的rawTx来进行重试,得到一个新的txhash // Increment successful tries: this.emit('tx:retry', txMeta) return txHash } /** Ask the network for the transaction to see if it has been include in a block @param txMeta {Object} - the txMeta object @emits tx:failed @emits tx:confirmed @emits tx:warning */ //通过其信息的查看来得到交易txMeta此时的状态,是failed、confirmed、warning async _checkPendingTx (txMeta) { const txHash = txMeta.hash const txId = txMeta.id // extra check in case there was an uncaught error during the // signature and submission process if (!txHash) {//txhash都没有,说明submit时出错了 const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') noTxHashErr.name = 'NoTxHashError' this.emit('tx:failed', txId, noTxHashErr) return } // If another tx with the same nonce is mined, set as failed. const taken = await this._checkIfNonceIsTaken(txMeta)//查看是否已又相同nonce的交易已经成功 if (taken) {//true则说明有相同的,交易失败 const nonceTakenErr = new Error('Another transaction with this nonce has been mined.') nonceTakenErr.name = 'NonceTakenErr' return this.emit('tx:failed', txId, nonceTakenErr) } // get latest transaction status try {//上面的判断都通过了,就查看该交易的信息 const txParams = await this.query.getTransactionByHash(txHash) if (!txParams) return //如果什么信息都没有,就返回空 if (txParams.blockNumber) {//如果txParams.blockNumber不为空,那就说明已经记录到了区块链上,已经confirmed this.emit('tx:confirmed', txId) } } catch (err) {//获取交易信息时出错 txMeta.warning = { error: err.message, message: 'There was a problem loading this transaction.', } this.emit('tx:warning', txMeta, err) } } /** checks to see if a confirmed txMeta has the same nonce @param txMeta {Object} - txMeta object @returns {boolean} */ async _checkIfNonceIsTaken (txMeta) {//查看这个交易txMeta的nonce是否与已经完成的交易的nonce相同 const address = txMeta.txParams.from const completed = this.getCompletedTransactions(address) const sameNonce = completed.filter((otherMeta) => {//得到满足nonce相等的交易的list,即sameNonce return otherMeta.txParams.nonce === txMeta.txParams.nonce }) return sameNonce.length > 0 //如果为true,则说明txMeta交易的nonce已有,该交易将失败 }}module.exports = PendingTransactionTracker
tx-gas-utils.js
const EthQuery = require('ethjs-query')const { hexToBn, BnMultiplyByFraction, bnToHex,} = require('../../lib/util')const { addHexPrefix } = require('ethereumjs-util')const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.这是一般交易固定传递的gas值/**tx-gas-utils are gas utility methods for Transaction managerits passed ethqueryand used to do things like calculate gas of a tx.@param {Object} provider - A network provider.*/class TxGasUtil { constructor (provider) { this.query = new EthQuery(provider) } /** @param txMeta {Object} - the txMeta object @returns {object} the txMeta object with the gas written to the txParams */ async analyzeGasUsage (txMeta) { const block = await this.query.getBlockByNumber('latest', false)//得到最新的区块的number let estimatedGasHex try { estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) //估计交易使用的gas } catch (err) { txMeta.simulationFails = { reason: err.message, } return txMeta } this.setTxGas(txMeta, block.gasLimit, estimatedGasHex) return txMeta } /** Estimates the tx's gas usage @param txMeta {Object} - the txMeta object @param blockGasLimitHex {string} - hex string of the block's gas limit @returns {string} the estimated gas limit as a hex string */ async estimateTxGas (txMeta, blockGasLimitHex) { const txParams = txMeta.txParams // check if gasLimit is already specified txMeta.gasLimitSpecified = Boolean(txParams.gas) //看该交易是否已经设置了其的gaslimit值 // if it is, use that value if (txMeta.gasLimitSpecified) { //如果已经有了,那就直接讲该值作为交易的估计值 return txParams.gas } // if recipient has no code, gas is 21k max: const recipient = txParams.to const hasRecipient = Boolean(recipient) let code if (recipient) code = await this.query.getCode(recipient) if (hasRecipient && (!code || code === '0x')) {有to且code为空或者只有0x0时,说明只是一个简单的value的send,只需要21000gas txParams.gas = SIMPLE_GAS_COST txMeta.simpleSend = true // Prevents buffer addition return SIMPLE_GAS_COST }//如果都不是上面的两种情况的活,就使用block gasLimit来估计交易gas // if not, fall back to block gasLimit const blockGasLimitBN = hexToBn(blockGasLimitHex) const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)//?????? txParams.gas = bnToHex(saferGasLimitBN) // run tx return await this.query.estimateGas(txParams) } /** Writes the gas on the txParams in the txMeta @param txMeta {Object} - the txMeta object to write to @param blockGasLimitHex {string} - the block gas limit hex @param estimatedGasHex {string} - the estimated gas hex */ setTxGas (txMeta, blockGasLimitHex, estimatedGasHex) { txMeta.estimatedGas = addHexPrefix(estimatedGasHex) const txParams = txMeta.txParams // if gasLimit was specified and doesnt OOG, // use original specified amount if (txMeta.gasLimitSpecified || txMeta.simpleSend) { txMeta.estimatedGas = txParams.gas //如果gas值已经声明好了,为用户自己设置的gaslimit,或者为simpleSend,那就将其设置为estimatedGas return } // if gasLimit not originally specified, // try adding an additional gas buffer to our estimation for safety const recommendedGasHex = this.addGasBuffer(txMeta.estimatedGas, blockGasLimitHex)//对之前得到的估计gas再进行一次判断,如果认为gas值过小,会相应扩大 txParams.gas = recommendedGasHex return } /** Adds a gas buffer with out exceeding the block gas limit @param initialGasLimitHex {string} - the initial gas limit to add the buffer too @param blockGasLimitHex {string} - the block gas limit @returns {string} the buffered gas limit as a hex string */ addGasBuffer (initialGasLimitHex, blockGasLimitHex) { const initialGasLimitBn = hexToBn(initialGasLimitHex) const blockGasLimitBn = hexToBn(blockGasLimitHex) const upperGasLimitBn = blockGasLimitBn.muln(0.9) const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) // if initialGasLimit is above blockGasLimit, dont modify it ,就是如果估计出来的gas-initialGasLimitBn已经大于blockGasLimit*0.9,我们就认为这个估计值是比较保险的,不会太小,就不修改了 if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn) // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit,如果initialGasLimitBn小于blockGasLimit*0.9 && initialGasLimitBn*1.5也小于blockGasLimit*0.9,那么就修改为initialGasLimitBn*1.5,这样会更保险 if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn) // otherwise use blockGasLimit return bnToHex(upperGasLimitBn) }}module.exports = TxGasUtil
index.js
对上面实现的代码的调用
const EventEmitter = require('events')const ObservableStore = require('obs-store')const ethUtil = require('ethereumjs-util')const Transaction = require('ethereumjs-tx')const EthQuery = require('ethjs-query')const TransactionStateManager = require('./tx-state-manager')const TxGasUtil = require('./tx-gas-utils')const PendingTransactionTracker = require('./pending-tx-tracker')const NonceTracker = require('./nonce-tracker')const txUtils = require('./lib/util')const cleanErrorStack = require('../../lib/cleanErrorStack')const log = require('loglevel')const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')const { TRANSACTION_TYPE_CANCEL, TRANSACTION_TYPE_RETRY, TRANSACTION_TYPE_STANDARD, TRANSACTION_STATUS_APPROVED,} = require('./enums')const { hexToBn, bnToHex } = require('../../lib/util')/** Transaction Controller is an aggregate of sub-controllers and trackers composing them in a way to be exposed to the metamask controller - txStateManager responsible for the state of a transaction and storing the transaction - pendingTxTracker watching blocks for transactions to be include and emitting confirmed events - txGasUtil gas calculations and safety buffering - nonceTracker calculating nonces @class @param {object} - opts @param {object} opts.initState - initial transaction list default is an empty array @param {Object} opts.networkStore - an observable store for network number @param {Object} opts.blockTracker - An instance of eth-blocktracker @param {Object} opts.provider - A network provider. @param {Function} opts.signTransaction - function the signs an ethereumjs-tx @param {Function} [opts.getGasPrice] - optional gas price calculator @param {Function} opts.signTransaction - ethTx signer that returns a rawTx @param {Number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state @param {Object} opts.preferencesStore*/class TransactionController extends EventEmitter { constructor (opts) { super() this.networkStore = opts.networkStore || new ObservableStore({}) this.preferencesStore = opts.preferencesStore || new ObservableStore({}) this.provider = opts.provider this.blockTracker = opts.blockTracker this.signEthTx = opts.signTransaction this.getGasPrice = opts.getGasPrice this.memStore = new ObservableStore({}) this.query = new EthQuery(this.provider) this.txGasUtil = new TxGasUtil(this.provider) this._mapMethods() this.txStateManager = new TransactionStateManager({ initState: opts.initState, txHistoryLimit: opts.txHistoryLimit, getNetwork: this.getNetwork.bind(this), }) this._onBootCleanUp() this.store = this.txStateManager.store this.nonceTracker = new NonceTracker({ provider: this.provider, blockTracker: this.blockTracker, getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), }) this.pendingTxTracker = new PendingTransactionTracker({ provider: this.provider, nonceTracker: this.nonceTracker, publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx), getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), }) this.txStateManager.store.subscribe(() => this.emit('update:badge')) this._setupListeners() // memstore is computed from a few different stores this._updateMemstore() this.txStateManager.store.subscribe(() => this._updateMemstore()) this.networkStore.subscribe(() => this._updateMemstore()) this.preferencesStore.subscribe(() => this._updateMemstore()) // request state update to finalize initialization this._updatePendingTxsAfterFirstBlock() } /** @returns {number} the chainId*/ getChainId () { const networkState = this.networkStore.getState() const getChainId = parseInt(networkState) if (Number.isNaN(getChainId)) { return 0 } else { return getChainId } }/** Adds a tx to the txlist @emits ${txMeta.id}:unapproved*/ addTx (txMeta) { this.txStateManager.addTx(txMeta) this.emit(`${txMeta.id}:unapproved`, txMeta) } /** Wipes the transactions for a given account @param {string} address - hex string of the from address for txs being removed */ wipeTransactions (address) { this.txStateManager.wipeTransactions(address) } /** add a new unapproved transaction to the pipeline 添加一个新的未经用户同意的交易 @returns {Promise} the hash of the transaction after being submitted to the network @param txParams {object} - txParams for the transaction @param opts {object} - with the key origin to put the origin on the txMeta */ async newUnapprovedTransaction (txParams, opts = {}) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const initialTxMeta = await this.addUnapprovedTransaction(txParams) initialTxMeta.origin = opts.origin this.txStateManager.updateTx(initialTxMeta, '#newUnapprovedTransaction - adding the origin') // listen for tx completion (success, fail) return new Promise((resolve, reject) => { this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => { switch (finishedTxMeta.status) { case 'submitted': return resolve(finishedTxMeta.hash) case 'rejected': return reject(cleanErrorStack(new Error('MetaMask Tx Signature: User denied transaction signature.'))) case 'failed': return reject(cleanErrorStack(new Error(finishedTxMeta.err.message))) default: return reject(cleanErrorStack(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) } }) }) } /** Validates and generates a txMeta with defaults and puts it in txStateManager store @returns {txMeta} */ async addUnapprovedTransaction (txParams) { // validate const normalizedTxParams = txUtils.normalizeTxParams(txParams) txUtils.validateTxParams(normalizedTxParams) // construct txMeta let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams, type: TRANSACTION_TYPE_STANDARD, }) this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) try { // check whether recipient account is blacklisted recipientBlacklistChecker.checkAccount(txMeta.metamaskNetworkId, normalizedTxParams.to) // add default tx params txMeta = await this.addTxGasDefaults(txMeta) } catch (error) { log.warn(error) this.txStateManager.setTxStatusFailed(txMeta.id, error) throw error } txMeta.loadingDefaults = false // save txMeta this.txStateManager.updateTx(txMeta) return txMeta }/** adds the tx gas defaults: gas && gasPrice @param txMeta {Object} - the txMeta object @returns {Promise