• 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
  • 相关阅读:
    [GeeksForGeeks] Maximum Length Chain of Pairs
    [Coding Made Simple] Buy/Sell stock with at most K transactions to maximize profit
    [LeetCode 10] Regular Expression Matching
    056_统计/etc/passwd 中 root 出现的次数
    055_使用脚本循环创建三位数字的文本文件(111-999 的所有文件)
    054_自动修改计划任务配置文件
    053_修改 Linux 系统的最大打开文件数量
    052_获取本机 MAC 地址
    051_循环关闭局域网中所有主机
    050_显示进度条(回旋镖版)
  • 原文地址:https://www.cnblogs.com/wanghui-garcia/p/9687001.html
Copyright © 2020-2023  润新知