metamask源码学习-controller-transaction

()metamask-extension/app/scripts/controllers/transactions

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 <br>statuses: 交易的状态 <br> - `'unapproved'` the user has not responded <br> - `'rejected'` the user has responded no! <br> - `'approved'` the user has approved the tx <br> - `'signed'` the tx is signed <br> - `'submitted'` the tx is sent to a server <br> - `'confirmed'` the tx has been included in a block. <br> - `'failed'` the tx failed for some reason, included on tx data. <br> - `'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:<br> let <code>thingsToLookFor = {<br> to: '0x0..',<br> from: '0x0..',<br> status: 'signed',<br> err: undefined,<br> }<br></code> @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;
}

 

metamask-extension/app/scripts/controllers/transactions/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<Object>} with the key releaseLock (the gloabl mutex)
  */
  async getGlobalLock () {
    const globalMutex = this._lookupMutex('global')//查看是否存在lockId = 'global'的互斥锁globalMutex
    // await global mutex free
    const releaseLock = await globalMutex.acquire()//等待互斥锁被释放并得到互斥锁,阻止别的线程运行
    return { releaseLock }
  }

  /**
   * @typedef NonceDetails
   * @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction.
   * @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method.
   * @property {number} highestSuggested - The maximum between the other two, the number returned.
   */

  /**
  this will return an object with the `nextNonce` `nonceDetails` of type NonceDetails, and the releaseLock
  Note: releaseLock must be called after adding a signed tx to pending transactions (or discarding).
  @param address {string} the hex string for the address whose nonce we are calculating
  @returns {Promise<NonceDetails>}
  */
  async getNonceLock (address) {
    // await global mutex free,查看全局互斥是否free,并获得该互斥再释放,使全局互斥锁状态为free
    await this._globalMutexFree()
    // await lock free, then take lock,只有全局互斥锁free了,其他的互斥锁才能用,这里得到某个address的互斥锁,将资源上锁
    const releaseLock = await this._takeMutex(address)
    try {
      // evaluate multiple nextNonce strategies
      const nonceDetails = {}
      const networkNonceResult = await this._getNetworkNextNonce(address)//得到最新的blockNumber信息和address从创世区块到blockNumber所进行的所有交易数
      const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)//得到下一个要被confirm的交易的nonce值
      const nextNetworkNonce = networkNonceResult.nonce //networkNonceResult.nonce 为address的所有交易数量,因为nonce从0开始,所以这个值也就是下一个交易的nonce值
      const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)//从上面两个数字中取得最大值来作为下一个交易的nonce值

      const pendingTxs = this.getPendingTransactions(address)//得到address账户状态为pending的交易,为list
      const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0 //查看pending状态的交易中有没有之前得到的最高nonce的交易,有则更改nonce,加1,得到最终的nonce

      nonceDetails.params = {
        highestLocallyConfirmed,//得到下一个要被confirm的交易的nonce值
        highestSuggested, //上下两个值中去最大值得到的下个交易的nonce值
        nextNetworkNonce, //address从创世区块到blockNumber所进行的所有交易数,即下一个交易的nonce值
      }
      nonceDetails.local = localNonceResult //再查看pending交易后得到的nonce值
      nonceDetails.network = networkNonceResult //得到最新的blockNumber信息和address从创世区块到blockNumber所进行的所有交易数

      const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce) //从localNonceResult.nonc(再查看pending交易后得到的nonce值)networkNonceResult.nonce (为address的所有交易数量)中得到最大值,即最后的nonce值
      assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)//判断为整数

      // return nonce and release cb
      return { nextNonce, nonceDetails, releaseLock }
    } catch (err) {
      // release lock if we encounter an error
      releaseLock()
      throw err
    }
  }

  async _globalMutexFree () {//获得global互斥锁并将其释放,保证其free状态
    const globalMutex = this._lookupMutex('global')
    const releaseLock = await globalMutex.acquire()
    releaseLock()
  }

  async _takeMutex (lockId) {
    const mutex = this._lookupMutex(lockId)//得到某个id的互斥锁
    const releaseLock = await mutex.acquire()//并获得互斥,将其上锁,将共享资源控制住
    return releaseLock
  }

  _lookupMutex (lockId) {//查看某个id的lock是否存在,如果不存在则创建一个互斥锁,并返回
    let mutex = this.lockMap[lockId]
    if (!mutex) {
      mutex = new Mutex()
      this.lockMap[lockId] = mutex
    }
    return mutex
  }

  async _getNetworkNextNonce (address) {
    // calculate next nonce
    // we need to make sure our base count
    // and pending count are from the same block
    const blockNumber = await this.blockTracker.getLatestBlock()//得到最新的block number
    const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber) //得到address从第一个区块到blockNumber区块上的所有交易数量
    const baseCount = baseCountBN.toNumber()
    assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)//判断baseCount是否为数字类型
    const nonceDetails = { blockNumber, baseCount } 
    return { name: 'network', nonce: baseCount, details: nonceDetails }
  }

  _getHighestLocallyConfirmed (address) {
    const confirmedTransactions = this.getConfirmedTransactions(address)//得到address账户的所有状态为Confirmed的交易
    const highest = this._getHighestNonce(confirmedTransactions)//得到这些状态为Confirmed的交易中最大的nonce值
    return Number.isInteger(highest) ? highest + 1 : 0 //如果该值为整数,则加一,得到下一个nonce值,否则则为0
  }

  _getHighestNonce (txList) {
    const nonces = txList.map((txMeta) => {//递归读取所有交易的nonce以list形式存放,即nonces
      const nonce = txMeta.txParams.nonce
      assert(typeof nonce, 'string', 'nonces should be hex strings')//nonce必须为hex strings格式
      return parseInt(nonce, 16)//将nonce由hex strings转为hex int
    })
    const highestNonce = Math.max.apply(null, nonces),然后从中取出最大的nonce
    return highestNonce
  }

  /**
    @typedef {object} highestContinuousFrom
    @property {string} - name the name for how the nonce was calculated based on the data used
    @property {number} - nonce the next suggested nonce
    @property {object} - details the provided starting nonce that was used (for debugging)
  */
  /**
    @param txList {array} - list of txMeta's
    @param startPoint {number} - the highest known locally confirmed nonce
    @returns {highestContinuousFrom}
  */
  _getHighestContinuousFrom (txList, startPoint) {
    const nonces = txList.map((txMeta) => {//得到所有交易的nonce值并以list形式存放
      const nonce = txMeta.txParams.nonce
      assert(typeof nonce, 'string', 'nonces should be hex strings')
      return parseInt(nonce, 16)
    })

    let highest = startPoint
    while (nonces.includes(highest)) {//如果这些交易中已经有我们以为的最高的nonce值,那我们需要将nonce++,更改最高nonce值,因为pending状态的交易是下一步就要记在区块上的交易
      highest++
    }

    return { name: 'local', nonce: highest, details: { startPoint, highest } }
  }

}

module.exports = NonceTracker

 

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<br>
  go from a pending state to a confirmed (mined in a block) state<br>
<br>
  As well as continues broadcast while in the pending state
<br>
@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 manager
its passed ethquery
and 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
    <br>- txStateManager
      responsible for the state of a transaction and
      storing the transaction
    <br>- pendingTxTracker
      watching blocks for transactions to be include
      and emitting confirmed events
    <br>- txGasUtil
      gas calculations and safety buffering
    <br>- 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<string>} 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<object>} resolves with txMeta
*/
  async addTxGasDefaults (txMeta) {
    const txParams = txMeta.txParams
    // ensure value
    txParams.value = txParams.value ? ethUtil.addHexPrefix(txParams.value) : '0x0'
    txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
    let gasPrice = txParams.gasPrice
    if (!gasPrice) {
      gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice()
    }
    txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
    // set gasLimit
    return await this.txGasUtil.analyzeGasUsage(txMeta)
  }

  /**
    Creates a new txMeta with the same txParams as the original
    to allow the user to resign the transaction with a higher gas values
    @param  originalTxId {number} - the id of the txMeta that
    you want to attempt to retry
    @return {txMeta}
  */

  async retryTransaction (originalTxId) {//重试一次交易
    const originalTxMeta = this.txStateManager.getTx(originalTxId)
    const lastGasPrice = originalTxMeta.txParams.gasPrice
    const txMeta = this.txStateManager.generateTxMeta({
      txParams: originalTxMeta.txParams,
      lastGasPrice,
      loadingDefaults: false,
      type: TRANSACTION_TYPE_RETRY,
    })
    this.addTx(txMeta)
    this.emit('newUnapprovedTx', txMeta)
    return txMeta
  }

  /**
   * Creates a new approved transaction to attempt to cancel a previously submitted transaction. The
   * new transaction contains the same nonce as the previous, is a basic ETH transfer of 0x value to
   * the sender's address, and has a higher gasPrice than that of the previous transaction.
   * @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
   * @param {string=} customGasPrice - the hex value to use for the cancel transaction
   * @returns {txMeta}
   */
  async createCancelTransaction (originalTxId, customGasPrice) {//创建一个nonce与以前的交易相同的新的交易一次来取消之前的那个交易,gasPrice要更高
    const originalTxMeta = this.txStateManager.getTx(originalTxId)
    const { txParams } = originalTxMeta
    const { gasPrice: lastGasPrice, from, nonce } = txParams
    const newGasPrice = customGasPrice || bnToHex(hexToBn(lastGasPrice).mul(1.1))
    const newTxMeta = this.txStateManager.generateTxMeta({
      txParams: {
        from,
        to: from,
        nonce,
        gas: '0x5208',
        value: '0x0',
        gasPrice: newGasPrice,
      },
      lastGasPrice,
      loadingDefaults: false,
      status: TRANSACTION_STATUS_APPROVED,
      type: TRANSACTION_TYPE_CANCEL,
    })

    this.addTx(newTxMeta)
    await this.approveTransaction(newTxMeta.id)
    return newTxMeta
  }

  /**
  updates the txMeta in the txStateManager
  @param txMeta {Object} - the updated txMeta
  */
  async updateTransaction (txMeta) {
    this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
  }

  /**
  updates and approves the transaction
  @param txMeta {Object}
  */
  async updateAndApproveTransaction (txMeta) {
    this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
    await this.approveTransaction(txMeta.id)
  }

  /**
  sets the tx status to approved
  auto fills the nonce
  signs the transaction
  publishes the transaction
  if any of these steps fails the tx status will be set to failed
    @param txId {number} - the tx's Id
  */
  async approveTransaction (txId) {
    let nonceLock
    try {
      // approve
      this.txStateManager.setTxStatusApproved(txId)
      // get next nonce
      const txMeta = this.txStateManager.getTx(txId)
      const fromAddress = txMeta.txParams.from
      // wait for a nonce
      nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
      // add nonce to txParams
      // if txMeta has lastGasPrice then it is a retry at same nonce with higher
      // gas price transaction and their for the nonce should not be calculated
      const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce
      txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
      // add nonce debugging information to txMeta
      txMeta.nonceDetails = nonceLock.nonceDetails
      this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
      // sign transaction
      const rawTx = await this.signTransaction(txId)
      await this.publishTransaction(txId, rawTx)
      // must set transaction to submitted/failed before releasing lock
      nonceLock.releaseLock()
    } catch (err) {
      // this is try-catch wrapped so that we can guarantee that the nonceLock is released
      try {
        this.txStateManager.setTxStatusFailed(txId, err)
      } catch (err) {
        log.error(err)
      }
      // must set transaction to submitted/failed before releasing lock
      if (nonceLock) nonceLock.releaseLock()
      // continue with error chain
      throw err
    }
  }
  /**
    adds the chain id and signs the transaction and set the status to signed
    @param txId {number} - the tx's Id
    @returns - rawTx {string}
  */
  async signTransaction (txId) {
    const txMeta = this.txStateManager.getTx(txId)
    // add network/chain id
    const chainId = this.getChainId()
    const txParams = Object.assign({}, txMeta.txParams, { chainId })
    // sign tx
    const fromAddress = txParams.from
    const ethTx = new Transaction(txParams)
    await this.signEthTx(ethTx, fromAddress)
    // set state to signed
    this.txStateManager.setTxStatusSigned(txMeta.id)
    const rawTx = ethUtil.bufferToHex(ethTx.serialize())
    return rawTx
  }

  /**
    publishes the raw tx and sets the txMeta to submitted
    @param txId {number} - the tx's Id
    @param rawTx {string} - the hex string of the serialized signed transaction
    @returns {Promise<void>}
  */
  async publishTransaction (txId, rawTx) {
    const txMeta = this.txStateManager.getTx(txId)
    txMeta.rawTx = rawTx
    this.txStateManager.updateTx(txMeta, 'transactions#publishTransaction')
    const txHash = await this.query.sendRawTransaction(rawTx)
    this.setTxHash(txId, txHash)
    this.txStateManager.setTxStatusSubmitted(txId)
  }

  confirmTransaction (txId) {
    this.txStateManager.setTxStatusConfirmed(txId)
    this._markNonceDuplicatesDropped(txId)
  }

  /**
    Convenience method for the ui thats sets the transaction to rejected
    @param txId {number} - the tx's Id
    @returns {Promise<void>}
  */
  async cancelTransaction (txId) {
    this.txStateManager.setTxStatusRejected(txId)
  }

  /**
    Sets the txHas on the txMeta
    @param txId {number} - the tx's Id
    @param txHash {string} - the hash for the txMeta
  */
  setTxHash (txId, txHash) {
    // Add the tx hash to the persisted meta-tx object
    const txMeta = this.txStateManager.getTx(txId)
    txMeta.hash = txHash
    this.txStateManager.updateTx(txMeta, 'transactions#setTxHash')
  }

//
//           PRIVATE METHODS
//
  /** maps methods for convenience*/
  _mapMethods () {
    /** @returns the state in transaction controller */
    this.getState = () => this.memStore.getState()
    /** @returns the network number stored in networkStore */
    this.getNetwork = () => this.networkStore.getState()
    /** @returns the user selected address */
    this.getSelectedAddress = () => this.preferencesStore.getState().selectedAddress
    /** Returns an array of transactions whos status is unapproved */
    this.getUnapprovedTxCount = () => Object.keys(this.txStateManager.getUnapprovedTxList()).length
    /**
      @returns a number that represents how many transactions have the status submitted
      @param account {String} - hex prefixed account
    */
    this.getPendingTxCount = (account) => this.txStateManager.getPendingTransactions(account).length
    /** see txStateManager */
    this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts)
  }

  // called once on startup
  async _updatePendingTxsAfterFirstBlock () {
    // wait for first block so we know we're ready
    await this.blockTracker.getLatestBlock()
    // get status update for all pending transactions (for the current network)
    await this.pendingTxTracker.updatePendingTxs()
  }

  /**
    If transaction controller was rebooted with transactions that are uncompleted
    in steps of the transaction signing or user confirmation process it will either
    transition txMetas to a failed state or try to redo those tasks.
  */

  _onBootCleanUp () {
    this.txStateManager.getFilteredTxList({
      status: 'unapproved',
      loadingDefaults: true,
    }).forEach((tx) => {
      this.addTxGasDefaults(tx)
      .then((txMeta) => {
        txMeta.loadingDefaults = false
        this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
      }).catch((error) => {
        this.txStateManager.setTxStatusFailed(tx.id, error)
      })
    })

    this.txStateManager.getFilteredTxList({
      status: TRANSACTION_STATUS_APPROVED,
    }).forEach((txMeta) => {
      const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
      this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
    })
  }

  /**
    is called in constructor applies the listeners for pendingTxTracker txStateManager
    and blockTracker
  */
  _setupListeners () {
    this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
    this._setupBlockTrackerListener()
    this.pendingTxTracker.on('tx:warning', (txMeta) => {
      this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
    })
    this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
    this.pendingTxTracker.on('tx:confirmed', (txId) => this.confirmTransaction(txId))
    this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
      if (!txMeta.firstRetryBlockNumber) {
        txMeta.firstRetryBlockNumber = latestBlockNumber
        this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
      }
    })
    this.pendingTxTracker.on('tx:retry', (txMeta) => {
      if (!('retryCount' in txMeta)) txMeta.retryCount = 0
      txMeta.retryCount++
      this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
    })
  }

  /**
    Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions
    in the list have the same nonce
    @param txId {Number} - the txId of the transaction that has been confirmed in a block
  */
  _markNonceDuplicatesDropped (txId) {
    // get the confirmed transactions nonce and from address
    const txMeta = this.txStateManager.getTx(txId)
    const { nonce, from } = txMeta.txParams
    const sameNonceTxs = this.txStateManager.getFilteredTxList({nonce, from})
    if (!sameNonceTxs.length) return
    // mark all same nonce transactions as dropped and give i a replacedBy hash
    sameNonceTxs.forEach((otherTxMeta) => {
      if (otherTxMeta.id === txId) return
      otherTxMeta.replacedBy = txMeta.hash
      this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce')
      this.txStateManager.setTxStatusDropped(otherTxMeta.id)
    })
  }

  _setupBlockTrackerListener () {
    let listenersAreActive = false
    const latestBlockHandler = this._onLatestBlock.bind(this)
    const blockTracker = this.blockTracker
    const txStateManager = this.txStateManager

    txStateManager.on('tx:status-update', updateSubscription)
    updateSubscription()

    function updateSubscription () {
      const pendingTxs = txStateManager.getPendingTransactions()
      if (!listenersAreActive && pendingTxs.length > 0) {
        blockTracker.on('latest', latestBlockHandler)
        listenersAreActive = true
      } else if (listenersAreActive && !pendingTxs.length) {
        blockTracker.removeListener('latest', latestBlockHandler)
        listenersAreActive = false
      }
    }
  }

  async _onLatestBlock (blockNumber) {
    try {
      await this.pendingTxTracker.updatePendingTxs()
    } catch (err) {
      log.error(err)
    }
    try {
      await this.pendingTxTracker.resubmitPendingTxs(blockNumber)
    } catch (err) {
      log.error(err)
    }
  }

  /**
    Updates the memStore in transaction controller
  */
  _updateMemstore () {
    const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
    const selectedAddressTxList = this.txStateManager.getFilteredTxList({
      from: this.getSelectedAddress(),
      metamaskNetworkId: this.getNetwork(),
    })
    this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
  }
}

module.exports = TransactionController

 

转载于:https://www.cnblogs.com/wanghui-garcia/p/9687001.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值