mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #4279 from MetaMask/network-remove-provider-engine
Enhancement: New BlockTracker and Json-Rpc-Engine based Provider
This commit is contained in:
commit
955ec2dca6
2
.babelrc
2
.babelrc
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"presets": [["env", { "debug": true }], "react", "stage-0"],
|
"presets": [["env"], "react", "stage-0"],
|
||||||
"plugins": ["transform-runtime", "transform-async-to-generator", "transform-class-properties"]
|
"plugins": ["transform-runtime", "transform-async-to-generator", "transform-class-properties"]
|
||||||
}
|
}
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,6 +9,7 @@ package
|
|||||||
# IDEs
|
# IDEs
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.sublime-project
|
||||||
|
|
||||||
# VIM
|
# VIM
|
||||||
*.swp
|
*.swp
|
||||||
@ -34,6 +35,7 @@ test/bundle.js
|
|||||||
test/test-bundle.js
|
test/test-bundle.js
|
||||||
|
|
||||||
test-artifacts
|
test-artifacts
|
||||||
|
test-builds
|
||||||
|
|
||||||
#ignore css output and sourcemaps
|
#ignore css output and sourcemaps
|
||||||
ui/app/css/output/
|
ui/app/css/output/
|
||||||
|
@ -19,7 +19,7 @@ const PortStream = require('./lib/port-stream.js')
|
|||||||
const createStreamSink = require('./lib/createStreamSink')
|
const createStreamSink = require('./lib/createStreamSink')
|
||||||
const NotificationManager = require('./lib/notification-manager.js')
|
const NotificationManager = require('./lib/notification-manager.js')
|
||||||
const MetamaskController = require('./metamask-controller')
|
const MetamaskController = require('./metamask-controller')
|
||||||
const firstTimeState = require('./first-time-state')
|
const rawFirstTimeState = require('./first-time-state')
|
||||||
const setupRaven = require('./lib/setupRaven')
|
const setupRaven = require('./lib/setupRaven')
|
||||||
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
|
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
|
||||||
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
|
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
|
||||||
@ -34,6 +34,9 @@ const {
|
|||||||
ENVIRONMENT_TYPE_FULLSCREEN,
|
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||||
} = require('./lib/enums')
|
} = require('./lib/enums')
|
||||||
|
|
||||||
|
// METAMASK_TEST_CONFIG is used in e2e tests to set the default network to localhost
|
||||||
|
const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_TEST_CONFIG)
|
||||||
|
|
||||||
const STORAGE_KEY = 'metamask-config'
|
const STORAGE_KEY = 'metamask-config'
|
||||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ class BalanceController {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.accountTracker.store.subscribe(update)
|
this.accountTracker.store.subscribe(update)
|
||||||
this.blockTracker.on('block', update)
|
this.blockTracker.on('latest', update)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
||||||
|
25
app/scripts/controllers/network/createInfuraClient.js
Normal file
25
app/scripts/controllers/network/createInfuraClient.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||||
|
const createBlockReEmitMiddleware = require('eth-json-rpc-middleware/block-reemit')
|
||||||
|
const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache')
|
||||||
|
const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache')
|
||||||
|
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
|
||||||
|
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
|
||||||
|
const createInfuraMiddleware = require('eth-json-rpc-infura')
|
||||||
|
const BlockTracker = require('eth-block-tracker')
|
||||||
|
|
||||||
|
module.exports = createInfuraClient
|
||||||
|
|
||||||
|
function createInfuraClient ({ network }) {
|
||||||
|
const infuraMiddleware = createInfuraMiddleware({ network })
|
||||||
|
const blockProvider = providerFromMiddleware(infuraMiddleware)
|
||||||
|
const blockTracker = new BlockTracker({ provider: blockProvider })
|
||||||
|
|
||||||
|
const networkMiddleware = mergeMiddleware([
|
||||||
|
createBlockCacheMiddleware({ blockTracker }),
|
||||||
|
createInflightMiddleware(),
|
||||||
|
createBlockReEmitMiddleware({ blockTracker, provider: blockProvider }),
|
||||||
|
createBlockTrackerInspectorMiddleware({ blockTracker }),
|
||||||
|
infuraMiddleware,
|
||||||
|
])
|
||||||
|
return { networkMiddleware, blockTracker }
|
||||||
|
}
|
25
app/scripts/controllers/network/createJsonRpcClient.js
Normal file
25
app/scripts/controllers/network/createJsonRpcClient.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||||
|
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
|
||||||
|
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref')
|
||||||
|
const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache')
|
||||||
|
const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache')
|
||||||
|
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
|
||||||
|
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
|
||||||
|
const BlockTracker = require('eth-block-tracker')
|
||||||
|
|
||||||
|
module.exports = createJsonRpcClient
|
||||||
|
|
||||||
|
function createJsonRpcClient ({ rpcUrl }) {
|
||||||
|
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
|
||||||
|
const blockProvider = providerFromMiddleware(fetchMiddleware)
|
||||||
|
const blockTracker = new BlockTracker({ provider: blockProvider })
|
||||||
|
|
||||||
|
const networkMiddleware = mergeMiddleware([
|
||||||
|
createBlockRefMiddleware({ blockTracker }),
|
||||||
|
createBlockCacheMiddleware({ blockTracker }),
|
||||||
|
createInflightMiddleware(),
|
||||||
|
createBlockTrackerInspectorMiddleware({ blockTracker }),
|
||||||
|
fetchMiddleware,
|
||||||
|
])
|
||||||
|
return { networkMiddleware, blockTracker }
|
||||||
|
}
|
21
app/scripts/controllers/network/createLocalhostClient.js
Normal file
21
app/scripts/controllers/network/createLocalhostClient.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||||
|
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
|
||||||
|
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref')
|
||||||
|
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
|
||||||
|
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
|
||||||
|
const BlockTracker = require('eth-block-tracker')
|
||||||
|
|
||||||
|
module.exports = createLocalhostClient
|
||||||
|
|
||||||
|
function createLocalhostClient () {
|
||||||
|
const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' })
|
||||||
|
const blockProvider = providerFromMiddleware(fetchMiddleware)
|
||||||
|
const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 })
|
||||||
|
|
||||||
|
const networkMiddleware = mergeMiddleware([
|
||||||
|
createBlockRefMiddleware({ blockTracker }),
|
||||||
|
createBlockTrackerInspectorMiddleware({ blockTracker }),
|
||||||
|
fetchMiddleware,
|
||||||
|
])
|
||||||
|
return { networkMiddleware, blockTracker }
|
||||||
|
}
|
43
app/scripts/controllers/network/createMetamaskMiddleware.js
Normal file
43
app/scripts/controllers/network/createMetamaskMiddleware.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||||
|
const createScaffoldMiddleware = require('json-rpc-engine/src/createScaffoldMiddleware')
|
||||||
|
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
|
||||||
|
const createWalletSubprovider = require('eth-json-rpc-middleware/wallet')
|
||||||
|
|
||||||
|
module.exports = createMetamaskMiddleware
|
||||||
|
|
||||||
|
function createMetamaskMiddleware ({
|
||||||
|
version,
|
||||||
|
getAccounts,
|
||||||
|
processTransaction,
|
||||||
|
processEthSignMessage,
|
||||||
|
processTypedMessage,
|
||||||
|
processPersonalMessage,
|
||||||
|
getPendingNonce,
|
||||||
|
}) {
|
||||||
|
const metamaskMiddleware = mergeMiddleware([
|
||||||
|
createScaffoldMiddleware({
|
||||||
|
// staticSubprovider
|
||||||
|
eth_syncing: false,
|
||||||
|
web3_clientVersion: `MetaMask/v${version}`,
|
||||||
|
}),
|
||||||
|
createWalletSubprovider({
|
||||||
|
getAccounts,
|
||||||
|
processTransaction,
|
||||||
|
processEthSignMessage,
|
||||||
|
processTypedMessage,
|
||||||
|
processPersonalMessage,
|
||||||
|
}),
|
||||||
|
createPendingNonceMiddleware({ getPendingNonce }),
|
||||||
|
])
|
||||||
|
return metamaskMiddleware
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPendingNonceMiddleware ({ getPendingNonce }) {
|
||||||
|
return createAsyncMiddleware(async (req, res, next) => {
|
||||||
|
if (req.method !== 'eth_getTransactionCount') return next()
|
||||||
|
const address = req.params[0]
|
||||||
|
const blockRef = req.params[1]
|
||||||
|
if (blockRef !== 'pending') return next()
|
||||||
|
req.result = await getPendingNonce(address)
|
||||||
|
})
|
||||||
|
}
|
@ -1,15 +1,17 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const EventEmitter = require('events')
|
const EventEmitter = require('events')
|
||||||
const createMetamaskProvider = require('web3-provider-engine/zero.js')
|
|
||||||
const SubproviderFromProvider = require('web3-provider-engine/subproviders/provider.js')
|
|
||||||
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
|
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const ComposedStore = require('obs-store/lib/composed')
|
const ComposedStore = require('obs-store/lib/composed')
|
||||||
const extend = require('xtend')
|
|
||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const createEventEmitterProxy = require('../../lib/events-proxy.js')
|
const JsonRpcEngine = require('json-rpc-engine')
|
||||||
|
const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const urlUtil = require('url')
|
const createMetamaskMiddleware = require('./createMetamaskMiddleware')
|
||||||
|
const createInfuraClient = require('./createInfuraClient')
|
||||||
|
const createJsonRpcClient = require('./createJsonRpcClient')
|
||||||
|
const createLocalhostClient = require('./createLocalhostClient')
|
||||||
|
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ROPSTEN,
|
ROPSTEN,
|
||||||
RINKEBY,
|
RINKEBY,
|
||||||
@ -17,7 +19,6 @@ const {
|
|||||||
MAINNET,
|
MAINNET,
|
||||||
LOCALHOST,
|
LOCALHOST,
|
||||||
} = require('./enums')
|
} = require('./enums')
|
||||||
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
|
||||||
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
|
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
|
||||||
|
|
||||||
const env = process.env.METAMASK_ENV
|
const env = process.env.METAMASK_ENV
|
||||||
@ -39,21 +40,27 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
this.providerStore = new ObservableStore(providerConfig)
|
this.providerStore = new ObservableStore(providerConfig)
|
||||||
this.networkStore = new ObservableStore('loading')
|
this.networkStore = new ObservableStore('loading')
|
||||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
||||||
// create event emitter proxy
|
|
||||||
this._proxy = createEventEmitterProxy()
|
|
||||||
|
|
||||||
this.on('networkDidChange', this.lookupNetwork)
|
this.on('networkDidChange', this.lookupNetwork)
|
||||||
|
// provider and block tracker
|
||||||
|
this._provider = null
|
||||||
|
this._blockTracker = null
|
||||||
|
// provider and block tracker proxies - because the network changes
|
||||||
|
this._providerProxy = null
|
||||||
|
this._blockTrackerProxy = null
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeProvider (_providerParams) {
|
initializeProvider (providerParams) {
|
||||||
this._baseProviderParams = _providerParams
|
this._baseProviderParams = providerParams
|
||||||
const { type, rpcTarget } = this.providerStore.getState()
|
const { type, rpcTarget } = this.providerStore.getState()
|
||||||
this._configureProvider({ type, rpcTarget })
|
this._configureProvider({ type, rpcTarget })
|
||||||
this._proxy.on('block', this._logBlock.bind(this))
|
|
||||||
this._proxy.on('error', this.verifyNetwork.bind(this))
|
|
||||||
this.ethQuery = new EthQuery(this._proxy)
|
|
||||||
this.lookupNetwork()
|
this.lookupNetwork()
|
||||||
return this._proxy
|
}
|
||||||
|
|
||||||
|
// return the proxies so the references will always be good
|
||||||
|
getProviderAndBlockTracker () {
|
||||||
|
const provider = this._providerProxy
|
||||||
|
const blockTracker = this._blockTrackerProxy
|
||||||
|
return { provider, blockTracker }
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyNetwork () {
|
verifyNetwork () {
|
||||||
@ -75,10 +82,11 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
|
|
||||||
lookupNetwork () {
|
lookupNetwork () {
|
||||||
// Prevent firing when provider is not defined.
|
// Prevent firing when provider is not defined.
|
||||||
if (!this.ethQuery || !this.ethQuery.sendAsync) {
|
if (!this._provider) {
|
||||||
return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery')
|
return log.warn('NetworkController - lookupNetwork aborted due to missing provider')
|
||||||
}
|
}
|
||||||
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
const ethQuery = new EthQuery(this._provider)
|
||||||
|
ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||||
if (err) return this.setNetworkState('loading')
|
if (err) return this.setNetworkState('loading')
|
||||||
log.info('web3.getNetwork returned ' + network)
|
log.info('web3.getNetwork returned ' + network)
|
||||||
this.setNetworkState(network)
|
this.setNetworkState(network)
|
||||||
@ -131,7 +139,7 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
this._configureInfuraProvider(opts)
|
this._configureInfuraProvider(opts)
|
||||||
// other type-based rpc endpoints
|
// other type-based rpc endpoints
|
||||||
} else if (type === LOCALHOST) {
|
} else if (type === LOCALHOST) {
|
||||||
this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL })
|
this._configureLocalhostProvider()
|
||||||
// url-based rpc endpoints
|
// url-based rpc endpoints
|
||||||
} else if (type === 'rpc') {
|
} else if (type === 'rpc') {
|
||||||
this._configureStandardProvider({ rpcUrl: rpcTarget })
|
this._configureStandardProvider({ rpcUrl: rpcTarget })
|
||||||
@ -141,49 +149,47 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_configureInfuraProvider ({ type }) {
|
_configureInfuraProvider ({ type }) {
|
||||||
log.info('_configureInfuraProvider', type)
|
log.info('NetworkController - configureInfuraProvider', type)
|
||||||
const infuraProvider = createInfuraProvider({ network: type })
|
const networkClient = createInfuraClient({ network: type })
|
||||||
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
|
this._setNetworkClient(networkClient)
|
||||||
const providerParams = extend(this._baseProviderParams, {
|
}
|
||||||
engineParams: {
|
|
||||||
pollingInterval: 8000,
|
_configureLocalhostProvider () {
|
||||||
blockTrackerProvider: infuraProvider,
|
log.info('NetworkController - configureLocalhostProvider')
|
||||||
},
|
const networkClient = createLocalhostClient()
|
||||||
dataSubprovider: infuraSubprovider,
|
this._setNetworkClient(networkClient)
|
||||||
})
|
|
||||||
const provider = createMetamaskProvider(providerParams)
|
|
||||||
this._setProvider(provider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureStandardProvider ({ rpcUrl }) {
|
_configureStandardProvider ({ rpcUrl }) {
|
||||||
// urlUtil handles malformed urls
|
log.info('NetworkController - configureStandardProvider', rpcUrl)
|
||||||
rpcUrl = urlUtil.parse(rpcUrl).format()
|
const networkClient = createJsonRpcClient({ rpcUrl })
|
||||||
const providerParams = extend(this._baseProviderParams, {
|
this._setNetworkClient(networkClient)
|
||||||
rpcUrl,
|
|
||||||
engineParams: {
|
|
||||||
pollingInterval: 8000,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const provider = createMetamaskProvider(providerParams)
|
|
||||||
this._setProvider(provider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setProvider (provider) {
|
_setNetworkClient ({ networkMiddleware, blockTracker }) {
|
||||||
// collect old block tracker events
|
const metamaskMiddleware = createMetamaskMiddleware(this._baseProviderParams)
|
||||||
const oldProvider = this._provider
|
const engine = new JsonRpcEngine()
|
||||||
let blockTrackerHandlers
|
engine.push(metamaskMiddleware)
|
||||||
if (oldProvider) {
|
engine.push(networkMiddleware)
|
||||||
// capture old block handlers
|
const provider = providerFromEngine(engine)
|
||||||
blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers
|
this._setProviderAndBlockTracker({ provider, blockTracker })
|
||||||
// tear down
|
}
|
||||||
oldProvider.removeAllListeners()
|
|
||||||
oldProvider.stop()
|
_setProviderAndBlockTracker ({ provider, blockTracker }) {
|
||||||
|
// update or intialize proxies
|
||||||
|
if (this._providerProxy) {
|
||||||
|
this._providerProxy.setTarget(provider)
|
||||||
|
} else {
|
||||||
|
this._providerProxy = createSwappableProxy(provider)
|
||||||
}
|
}
|
||||||
// override block tracler
|
if (this._blockTrackerProxy) {
|
||||||
provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers)
|
this._blockTrackerProxy.setTarget(blockTracker)
|
||||||
// set as new provider
|
} else {
|
||||||
|
this._blockTrackerProxy = createEventEmitterProxy(blockTracker)
|
||||||
|
}
|
||||||
|
// set new provider and blockTracker
|
||||||
this._provider = provider
|
this._provider = provider
|
||||||
this._proxy.setTarget(provider)
|
this._blockTracker = blockTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
_logBlock (block) {
|
_logBlock (block) {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const BN = require('ethereumjs-util').BN
|
|
||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
const pify = require('pify')
|
||||||
|
|
||||||
class RecentBlocksController {
|
class RecentBlocksController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller responsible for storing, updating and managing the recent history of blocks. Blocks are back filled
|
* Controller responsible for storing, updating and managing the recent history of blocks. Blocks are back filled
|
||||||
* upon the controller's construction and then the list is updated when the given block tracker gets a 'block' event
|
* upon the controller's construction and then the list is updated when the given block tracker gets a 'latest' event
|
||||||
* (indicating that there is a new block to process).
|
* (indicating that there is a new block to process).
|
||||||
*
|
*
|
||||||
* @typedef {Object} RecentBlocksController
|
* @typedef {Object} RecentBlocksController
|
||||||
@ -16,7 +16,7 @@ class RecentBlocksController {
|
|||||||
* @param {BlockTracker} opts.blockTracker Contains objects necessary for tracking blocks and querying the blockchain
|
* @param {BlockTracker} opts.blockTracker Contains objects necessary for tracking blocks and querying the blockchain
|
||||||
* @param {BlockTracker} opts.provider The provider used to create a new EthQuery instance.
|
* @param {BlockTracker} opts.provider The provider used to create a new EthQuery instance.
|
||||||
* @property {BlockTracker} blockTracker Points to the passed BlockTracker. On RecentBlocksController construction,
|
* @property {BlockTracker} blockTracker Points to the passed BlockTracker. On RecentBlocksController construction,
|
||||||
* listens for 'block' events so that new blocks can be processed and added to storage.
|
* listens for 'latest' events so that new blocks can be processed and added to storage.
|
||||||
* @property {EthQuery} ethQuery Points to the EthQuery instance created with the passed provider
|
* @property {EthQuery} ethQuery Points to the EthQuery instance created with the passed provider
|
||||||
* @property {number} historyLength The maximum length of blocks to track
|
* @property {number} historyLength The maximum length of blocks to track
|
||||||
* @property {object} store Stores the recentBlocks
|
* @property {object} store Stores the recentBlocks
|
||||||
@ -34,7 +34,13 @@ class RecentBlocksController {
|
|||||||
}, opts.initState)
|
}, opts.initState)
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
|
|
||||||
this.blockTracker.on('block', this.processBlock.bind(this))
|
this.blockTracker.on('latest', async (newBlockNumberHex) => {
|
||||||
|
try {
|
||||||
|
await this.processBlock(newBlockNumberHex)
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
this.backfill()
|
this.backfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +61,11 @@ class RecentBlocksController {
|
|||||||
* @param {object} newBlock The new block to modify and add to the recentBlocks array
|
* @param {object} newBlock The new block to modify and add to the recentBlocks array
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
processBlock (newBlock) {
|
async processBlock (newBlockNumberHex) {
|
||||||
|
const newBlockNumber = Number.parseInt(newBlockNumberHex, 16)
|
||||||
|
const newBlock = await this.getBlockByNumber(newBlockNumber, true)
|
||||||
|
if (!newBlock) return
|
||||||
|
|
||||||
const block = this.mapTransactionsToPrices(newBlock)
|
const block = this.mapTransactionsToPrices(newBlock)
|
||||||
|
|
||||||
const state = this.store.getState()
|
const state = this.store.getState()
|
||||||
@ -108,9 +118,9 @@ class RecentBlocksController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On this.blockTracker's first 'block' event after this RecentBlocksController's instantiation, the store.recentBlocks
|
* On this.blockTracker's first 'latest' event after this RecentBlocksController's instantiation, the store.recentBlocks
|
||||||
* array is populated with this.historyLength number of blocks. The block number of the this.blockTracker's first
|
* array is populated with this.historyLength number of blocks. The block number of the this.blockTracker's first
|
||||||
* 'block' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying
|
* 'latest' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying
|
||||||
* the blockchain. These blocks are backfilled so that the recentBlocks array is ordered from oldest to newest.
|
* the blockchain. These blocks are backfilled so that the recentBlocks array is ordered from oldest to newest.
|
||||||
*
|
*
|
||||||
* Each iteration over the block numbers is delayed by 100 milliseconds.
|
* Each iteration over the block numbers is delayed by 100 milliseconds.
|
||||||
@ -118,18 +128,17 @@ class RecentBlocksController {
|
|||||||
* @returns {Promise<void>} Promises undefined
|
* @returns {Promise<void>} Promises undefined
|
||||||
*/
|
*/
|
||||||
async backfill () {
|
async backfill () {
|
||||||
this.blockTracker.once('block', async (block) => {
|
this.blockTracker.once('latest', async (blockNumberHex) => {
|
||||||
const currentBlockNumber = Number.parseInt(block.number, 16)
|
const currentBlockNumber = Number.parseInt(blockNumberHex, 16)
|
||||||
const blocksToFetch = Math.min(currentBlockNumber, this.historyLength)
|
const blocksToFetch = Math.min(currentBlockNumber, this.historyLength)
|
||||||
const prevBlockNumber = currentBlockNumber - 1
|
const prevBlockNumber = currentBlockNumber - 1
|
||||||
const targetBlockNumbers = Array(blocksToFetch).fill().map((_, index) => prevBlockNumber - index)
|
const targetBlockNumbers = Array(blocksToFetch).fill().map((_, index) => prevBlockNumber - index)
|
||||||
await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => {
|
await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => {
|
||||||
try {
|
try {
|
||||||
const newBlock = await this.getBlockByNumber(targetBlockNumber)
|
const newBlock = await this.getBlockByNumber(targetBlockNumber, true)
|
||||||
|
if (!newBlock) return
|
||||||
|
|
||||||
if (newBlock) {
|
this.backfillBlock(newBlock)
|
||||||
this.backfillBlock(newBlock)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(e)
|
log.error(e)
|
||||||
}
|
}
|
||||||
@ -137,18 +146,6 @@ class RecentBlocksController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper for this.backfill. Provides an easy way to ensure a 100 millisecond delay using await
|
|
||||||
*
|
|
||||||
* @returns {Promise<void>} Promises undefined
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
async wait () {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, 100)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses EthQuery to get a block that has a given block number.
|
* Uses EthQuery to get a block that has a given block number.
|
||||||
*
|
*
|
||||||
@ -157,13 +154,8 @@ class RecentBlocksController {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async getBlockByNumber (number) {
|
async getBlockByNumber (number) {
|
||||||
const bn = new BN(number)
|
const blockNumberHex = '0x' + number.toString(16)
|
||||||
return new Promise((resolve, reject) => {
|
return await pify(this.ethQuery.getBlockByNumber).call(this.ethQuery, blockNumberHex, true)
|
||||||
this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => {
|
|
||||||
if (err) reject(err)
|
|
||||||
resolve(block)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ class TransactionController extends EventEmitter {
|
|||||||
this.store = this.txStateManager.store
|
this.store = this.txStateManager.store
|
||||||
this.nonceTracker = new NonceTracker({
|
this.nonceTracker = new NonceTracker({
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
|
blockTracker: this.blockTracker,
|
||||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
||||||
getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
||||||
})
|
})
|
||||||
@ -78,13 +79,17 @@ class TransactionController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
|
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
|
||||||
this._setupListners()
|
this._setupListeners()
|
||||||
// memstore is computed from a few different stores
|
// memstore is computed from a few different stores
|
||||||
this._updateMemstore()
|
this._updateMemstore()
|
||||||
this.txStateManager.store.subscribe(() => this._updateMemstore())
|
this.txStateManager.store.subscribe(() => this._updateMemstore())
|
||||||
this.networkStore.subscribe(() => this._updateMemstore())
|
this.networkStore.subscribe(() => this._updateMemstore())
|
||||||
this.preferencesStore.subscribe(() => this._updateMemstore())
|
this.preferencesStore.subscribe(() => this._updateMemstore())
|
||||||
|
|
||||||
|
// request state update to finalize initialization
|
||||||
|
this._updatePendingTxsAfterFirstBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {number} the chainId*/
|
/** @returns {number} the chainId*/
|
||||||
getChainId () {
|
getChainId () {
|
||||||
const networkState = this.networkStore.getState()
|
const networkState = this.networkStore.getState()
|
||||||
@ -311,6 +316,11 @@ class TransactionController extends EventEmitter {
|
|||||||
this.txStateManager.setTxStatusSubmitted(txId)
|
this.txStateManager.setTxStatusSubmitted(txId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
confirmTransaction (txId) {
|
||||||
|
this.txStateManager.setTxStatusConfirmed(txId)
|
||||||
|
this._markNonceDuplicatesDropped(txId)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convenience method for the ui thats sets the transaction to rejected
|
Convenience method for the ui thats sets the transaction to rejected
|
||||||
@param txId {number} - the tx's Id
|
@param txId {number} - the tx's Id
|
||||||
@ -354,6 +364,14 @@ class TransactionController extends EventEmitter {
|
|||||||
this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts)
|
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
|
If transaction controller was rebooted with transactions that are uncompleted
|
||||||
in steps of the transaction signing or user confirmation process it will either
|
in steps of the transaction signing or user confirmation process it will either
|
||||||
@ -386,14 +404,14 @@ class TransactionController extends EventEmitter {
|
|||||||
is called in constructor applies the listeners for pendingTxTracker txStateManager
|
is called in constructor applies the listeners for pendingTxTracker txStateManager
|
||||||
and blockTracker
|
and blockTracker
|
||||||
*/
|
*/
|
||||||
_setupListners () {
|
_setupListeners () {
|
||||||
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
|
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
|
||||||
|
this._setupBlockTrackerListener()
|
||||||
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
|
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
|
||||||
})
|
})
|
||||||
this.pendingTxTracker.on('tx:confirmed', (txId) => this.txStateManager.setTxStatusConfirmed(txId))
|
|
||||||
this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId))
|
|
||||||
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
|
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) => {
|
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
||||||
if (!txMeta.firstRetryBlockNumber) {
|
if (!txMeta.firstRetryBlockNumber) {
|
||||||
txMeta.firstRetryBlockNumber = latestBlockNumber
|
txMeta.firstRetryBlockNumber = latestBlockNumber
|
||||||
@ -405,13 +423,6 @@ class TransactionController extends EventEmitter {
|
|||||||
txMeta.retryCount++
|
txMeta.retryCount++
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
|
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
|
||||||
})
|
})
|
||||||
|
|
||||||
this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
|
|
||||||
// this is a little messy but until ethstore has been either
|
|
||||||
// removed or redone this is to guard against the race condition
|
|
||||||
this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
|
|
||||||
this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -435,6 +446,40 @@ class TransactionController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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
|
Updates the memStore in transaction controller
|
||||||
*/
|
*/
|
||||||
|
@ -12,8 +12,9 @@ const Mutex = require('await-semaphore').Mutex
|
|||||||
*/
|
*/
|
||||||
class NonceTracker {
|
class NonceTracker {
|
||||||
|
|
||||||
constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
|
constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) {
|
||||||
this.provider = provider
|
this.provider = provider
|
||||||
|
this.blockTracker = blockTracker
|
||||||
this.ethQuery = new EthQuery(provider)
|
this.ethQuery = new EthQuery(provider)
|
||||||
this.getPendingTransactions = getPendingTransactions
|
this.getPendingTransactions = getPendingTransactions
|
||||||
this.getConfirmedTransactions = getConfirmedTransactions
|
this.getConfirmedTransactions = getConfirmedTransactions
|
||||||
@ -34,7 +35,7 @@ class NonceTracker {
|
|||||||
* @typedef NonceDetails
|
* @typedef NonceDetails
|
||||||
* @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction.
|
* @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} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method.
|
||||||
* @property {number} highetSuggested - The maximum between the other two, the number returned.
|
* @property {number} highestSuggested - The maximum between the other two, the number returned.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,15 +81,6 @@ class NonceTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getCurrentBlock () {
|
|
||||||
const blockTracker = this._getBlockTracker()
|
|
||||||
const currentBlock = blockTracker.getCurrentBlock()
|
|
||||||
if (currentBlock) return currentBlock
|
|
||||||
return await new Promise((reject, resolve) => {
|
|
||||||
blockTracker.once('latest', resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async _globalMutexFree () {
|
async _globalMutexFree () {
|
||||||
const globalMutex = this._lookupMutex('global')
|
const globalMutex = this._lookupMutex('global')
|
||||||
const releaseLock = await globalMutex.acquire()
|
const releaseLock = await globalMutex.acquire()
|
||||||
@ -114,9 +106,8 @@ class NonceTracker {
|
|||||||
// calculate next nonce
|
// calculate next nonce
|
||||||
// we need to make sure our base count
|
// we need to make sure our base count
|
||||||
// and pending count are from the same block
|
// and pending count are from the same block
|
||||||
const currentBlock = await this._getCurrentBlock()
|
const blockNumber = await this.blockTracker.getLatestBlock()
|
||||||
const blockNumber = currentBlock.blockNumber
|
const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber)
|
||||||
const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber || 'latest')
|
|
||||||
const baseCount = baseCountBN.toNumber()
|
const baseCount = baseCountBN.toNumber()
|
||||||
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)
|
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)
|
||||||
const nonceDetails = { blockNumber, baseCount }
|
const nonceDetails = { blockNumber, baseCount }
|
||||||
@ -165,15 +156,6 @@ class NonceTracker {
|
|||||||
return { name: 'local', nonce: highest, details: { startPoint, highest } }
|
return { name: 'local', nonce: highest, details: { startPoint, highest } }
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is a hotfix for the fact that the blockTracker will
|
|
||||||
// change when the network changes
|
|
||||||
|
|
||||||
/**
|
|
||||||
@returns {Object} the current blockTracker
|
|
||||||
*/
|
|
||||||
_getBlockTracker () {
|
|
||||||
return this.provider._blockTracker
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = NonceTracker
|
module.exports = NonceTracker
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const EventEmitter = require('events')
|
const EventEmitter = require('events')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const EthQuery = require('ethjs-query')
|
const EthQuery = require('ethjs-query')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
Event emitter utility class for tracking the transactions as they<br>
|
Event emitter utility class for tracking the transactions as they<br>
|
||||||
@ -23,55 +24,26 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
super()
|
super()
|
||||||
this.query = new EthQuery(config.provider)
|
this.query = new EthQuery(config.provider)
|
||||||
this.nonceTracker = config.nonceTracker
|
this.nonceTracker = config.nonceTracker
|
||||||
// default is one day
|
|
||||||
this.getPendingTransactions = config.getPendingTransactions
|
this.getPendingTransactions = config.getPendingTransactions
|
||||||
this.getCompletedTransactions = config.getCompletedTransactions
|
this.getCompletedTransactions = config.getCompletedTransactions
|
||||||
this.publishTransaction = config.publishTransaction
|
this.publishTransaction = config.publishTransaction
|
||||||
this._checkPendingTxs()
|
this.confirmTransaction = config.confirmTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
checks if a signed tx is in a block and
|
checks the network for signed txs and releases the nonce global lock if it is
|
||||||
if it is included emits tx status as 'confirmed'
|
|
||||||
@param block {object}, a full block
|
|
||||||
@emits tx:confirmed
|
|
||||||
@emits tx:failed
|
|
||||||
*/
|
*/
|
||||||
checkForTxInBlock (block) {
|
async updatePendingTxs () {
|
||||||
const signedTxList = this.getPendingTransactions()
|
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
||||||
if (!signedTxList.length) return
|
const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
|
||||||
signedTxList.forEach((txMeta) => {
|
try {
|
||||||
const txHash = txMeta.hash
|
const pendingTxs = this.getPendingTransactions()
|
||||||
const txId = txMeta.id
|
await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)))
|
||||||
|
} catch (err) {
|
||||||
if (!txHash) {
|
log.error('PendingTransactionTracker - Error updating pending transactions')
|
||||||
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
|
log.error(err)
|
||||||
noTxHashErr.name = 'NoTxHashError'
|
|
||||||
this.emit('tx:failed', txId, noTxHashErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
block.transactions.forEach((tx) => {
|
|
||||||
if (tx.hash === txHash) this.emit('tx:confirmed', txId)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
asks the network for the transaction to see if a block number is included on it
|
|
||||||
if we have skipped/missed blocks
|
|
||||||
@param object - oldBlock newBlock
|
|
||||||
*/
|
|
||||||
queryPendingTxs ({ oldBlock, newBlock }) {
|
|
||||||
// check pending transactions on start
|
|
||||||
if (!oldBlock) {
|
|
||||||
this._checkPendingTxs()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// if we synced by more than one block, check for missed pending transactions
|
nonceGlobalLock.releaseLock()
|
||||||
const diff = Number.parseInt(newBlock.number, 16) - Number.parseInt(oldBlock.number, 16)
|
|
||||||
if (diff > 1) this._checkPendingTxs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,11 +51,11 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
@param block {object} - a block object
|
@param block {object} - a block object
|
||||||
@emits tx:warning
|
@emits tx:warning
|
||||||
*/
|
*/
|
||||||
resubmitPendingTxs (block) {
|
resubmitPendingTxs (blockNumber) {
|
||||||
const pending = this.getPendingTransactions()
|
const pending = this.getPendingTransactions()
|
||||||
// only try resubmitting if their are transactions to resubmit
|
// only try resubmitting if their are transactions to resubmit
|
||||||
if (!pending.length) return
|
if (!pending.length) return
|
||||||
pending.forEach((txMeta) => this._resubmitTx(txMeta, block.number).catch((err) => {
|
pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => {
|
||||||
/*
|
/*
|
||||||
Dont marked as failed if the error is a "known" transaction warning
|
Dont marked as failed if the error is a "known" transaction warning
|
||||||
"there is already a transaction with the same sender-nonce
|
"there is already a transaction with the same sender-nonce
|
||||||
@ -145,6 +117,7 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
this.emit('tx:retry', txMeta)
|
this.emit('tx:retry', txMeta)
|
||||||
return txHash
|
return txHash
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Ask the network for the transaction to see if it has been include in a block
|
Ask the network for the transaction to see if it has been include in a block
|
||||||
@param txMeta {Object} - the txMeta object
|
@param txMeta {Object} - the txMeta object
|
||||||
@ -174,9 +147,8 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get latest transaction status
|
// get latest transaction status
|
||||||
let txParams
|
|
||||||
try {
|
try {
|
||||||
txParams = await this.query.getTransactionByHash(txHash)
|
const txParams = await this.query.getTransactionByHash(txHash)
|
||||||
if (!txParams) return
|
if (!txParams) return
|
||||||
if (txParams.blockNumber) {
|
if (txParams.blockNumber) {
|
||||||
this.emit('tx:confirmed', txId)
|
this.emit('tx:confirmed', txId)
|
||||||
@ -190,27 +162,13 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
checks the network for signed txs and releases the nonce global lock if it is
|
|
||||||
*/
|
|
||||||
async _checkPendingTxs () {
|
|
||||||
const signedTxList = this.getPendingTransactions()
|
|
||||||
// in order to keep the nonceTracker accurate we block it while updating pending transactions
|
|
||||||
const { releaseLock } = await this.nonceTracker.getGlobalLock()
|
|
||||||
try {
|
|
||||||
await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
|
|
||||||
} catch (err) {
|
|
||||||
log.error('PendingTransactionWatcher - Error updating pending transactions')
|
|
||||||
log.error(err)
|
|
||||||
}
|
|
||||||
releaseLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
checks to see if a confirmed txMeta has the same nonce
|
checks to see if a confirmed txMeta has the same nonce
|
||||||
@param txMeta {Object} - txMeta object
|
@param txMeta {Object} - txMeta object
|
||||||
@returns {boolean}
|
@returns {boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
async _checkIfNonceIsTaken (txMeta) {
|
async _checkIfNonceIsTaken (txMeta) {
|
||||||
const address = txMeta.txParams.from
|
const address = txMeta.txParams.from
|
||||||
const completed = this.getCompletedTransactions(address)
|
const completed = this.getCompletedTransactions(address)
|
||||||
|
@ -25,7 +25,7 @@ class TxGasUtil {
|
|||||||
@returns {object} the txMeta object with the gas written to the txParams
|
@returns {object} the txMeta object with the gas written to the txParams
|
||||||
*/
|
*/
|
||||||
async analyzeGasUsage (txMeta) {
|
async analyzeGasUsage (txMeta) {
|
||||||
const block = await this.query.getBlockByNumber('latest', true)
|
const block = await this.query.getBlockByNumber('latest', false)
|
||||||
let estimatedGasHex
|
let estimatedGasHex
|
||||||
try {
|
try {
|
||||||
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
|
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
|
||||||
|
@ -7,14 +7,13 @@
|
|||||||
* on each new block.
|
* on each new block.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const async = require('async')
|
|
||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const EventEmitter = require('events').EventEmitter
|
const log = require('loglevel')
|
||||||
function noop () {}
|
const pify = require('pify')
|
||||||
|
|
||||||
|
|
||||||
class AccountTracker extends EventEmitter {
|
class AccountTracker {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This module is responsible for tracking any number of accounts and caching their current balances & transaction
|
* This module is responsible for tracking any number of accounts and caching their current balances & transaction
|
||||||
@ -35,8 +34,6 @@ class AccountTracker extends EventEmitter {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}) {
|
constructor (opts = {}) {
|
||||||
super()
|
|
||||||
|
|
||||||
const initState = {
|
const initState = {
|
||||||
accounts: {},
|
accounts: {},
|
||||||
currentBlockGasLimit: '',
|
currentBlockGasLimit: '',
|
||||||
@ -44,12 +41,12 @@ class AccountTracker extends EventEmitter {
|
|||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
|
|
||||||
this._provider = opts.provider
|
this._provider = opts.provider
|
||||||
this._query = new EthQuery(this._provider)
|
this._query = pify(new EthQuery(this._provider))
|
||||||
this._blockTracker = opts.blockTracker
|
this._blockTracker = opts.blockTracker
|
||||||
// subscribe to latest block
|
// subscribe to latest block
|
||||||
this._blockTracker.on('block', this._updateForBlock.bind(this))
|
this._blockTracker.on('latest', this._updateForBlock.bind(this))
|
||||||
// blockTracker.currentBlock may be null
|
// blockTracker.currentBlock may be null
|
||||||
this._currentBlockNumber = this._blockTracker.currentBlock
|
this._currentBlockNumber = this._blockTracker.getCurrentBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,49 +64,57 @@ class AccountTracker extends EventEmitter {
|
|||||||
const accounts = this.store.getState().accounts
|
const accounts = this.store.getState().accounts
|
||||||
const locals = Object.keys(accounts)
|
const locals = Object.keys(accounts)
|
||||||
|
|
||||||
const toAdd = []
|
const accountsToAdd = []
|
||||||
addresses.forEach((upstream) => {
|
addresses.forEach((upstream) => {
|
||||||
if (!locals.includes(upstream)) {
|
if (!locals.includes(upstream)) {
|
||||||
toAdd.push(upstream)
|
accountsToAdd.push(upstream)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const toRemove = []
|
const accountsToRemove = []
|
||||||
locals.forEach((local) => {
|
locals.forEach((local) => {
|
||||||
if (!addresses.includes(local)) {
|
if (!addresses.includes(local)) {
|
||||||
toRemove.push(local)
|
accountsToRemove.push(local)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
toAdd.forEach(upstream => this.addAccount(upstream))
|
this.addAccounts(accountsToAdd)
|
||||||
toRemove.forEach(local => this.removeAccount(local))
|
this.removeAccount(accountsToRemove)
|
||||||
this._updateAccounts()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new address to this AccountTracker's accounts object, which points to an empty object. This object will be
|
* Adds new addresses to track the balances of
|
||||||
* given a balance as long this._currentBlockNumber is defined.
|
* given a balance as long this._currentBlockNumber is defined.
|
||||||
*
|
*
|
||||||
* @param {string} address A hex address of a new account to store in this AccountTracker's accounts object
|
* @param {array} addresses An array of hex addresses of new accounts to track
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addAccount (address) {
|
addAccounts (addresses) {
|
||||||
const accounts = this.store.getState().accounts
|
const accounts = this.store.getState().accounts
|
||||||
accounts[address] = {}
|
// add initial state for addresses
|
||||||
|
addresses.forEach(address => {
|
||||||
|
accounts[address] = {}
|
||||||
|
})
|
||||||
|
// save accounts state
|
||||||
this.store.updateState({ accounts })
|
this.store.updateState({ accounts })
|
||||||
|
// fetch balances for the accounts if there is block number ready
|
||||||
if (!this._currentBlockNumber) return
|
if (!this._currentBlockNumber) return
|
||||||
this._updateAccount(address)
|
addresses.forEach(address => this._updateAccount(address))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an account from this AccountTracker's accounts object
|
* Removes accounts from being tracked
|
||||||
*
|
*
|
||||||
* @param {string} address A hex address of a the account to remove
|
* @param {array} an array of hex addresses to stop tracking
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
removeAccount (address) {
|
removeAccount (addresses) {
|
||||||
const accounts = this.store.getState().accounts
|
const accounts = this.store.getState().accounts
|
||||||
delete accounts[address]
|
// remove each state object
|
||||||
|
addresses.forEach(address => {
|
||||||
|
delete accounts[address]
|
||||||
|
})
|
||||||
|
// save accounts state
|
||||||
this.store.updateState({ accounts })
|
this.store.updateState({ accounts })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,71 +123,56 @@ class AccountTracker extends EventEmitter {
|
|||||||
* via EthQuery
|
* via EthQuery
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {object} block Data about the block that contains the data to update to.
|
* @param {number} blockNumber the block number to update to.
|
||||||
* @fires 'block' The updated state, if all account updates are successful
|
* @fires 'block' The updated state, if all account updates are successful
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateForBlock (block) {
|
async _updateForBlock (blockNumber) {
|
||||||
this._currentBlockNumber = block.number
|
this._currentBlockNumber = blockNumber
|
||||||
const currentBlockGasLimit = block.gasLimit
|
|
||||||
|
|
||||||
|
// block gasLimit polling shouldn't be in account-tracker shouldn't be here...
|
||||||
|
const currentBlock = await this._query.getBlockByNumber(blockNumber, false)
|
||||||
|
if (!currentBlock) return
|
||||||
|
const currentBlockGasLimit = currentBlock.gasLimit
|
||||||
this.store.updateState({ currentBlockGasLimit })
|
this.store.updateState({ currentBlockGasLimit })
|
||||||
|
|
||||||
async.parallel([
|
try {
|
||||||
this._updateAccounts.bind(this),
|
await this._updateAccounts()
|
||||||
], (err) => {
|
} catch (err) {
|
||||||
if (err) return console.error(err)
|
log.error(err)
|
||||||
this.emit('block', this.store.getState())
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls this._updateAccount for each account in this.store
|
* Calls this._updateAccount for each account in this.store
|
||||||
*
|
*
|
||||||
* @param {Function} cb A callback to pass to this._updateAccount, called after each account is successfully updated
|
* @returns {Promise} after all account balances updated
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateAccounts (cb = noop) {
|
async _updateAccounts () {
|
||||||
const accounts = this.store.getState().accounts
|
const accounts = this.store.getState().accounts
|
||||||
const addresses = Object.keys(accounts)
|
const addresses = Object.keys(accounts)
|
||||||
async.each(addresses, this._updateAccount.bind(this), cb)
|
await Promise.all(addresses.map(this._updateAccount.bind(this)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the current balance of an account. Gets an updated balance via this._getAccount.
|
* Updates the current balance of an account.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {string} address A hex address of a the account to be updated
|
* @param {string} address A hex address of a the account to be updated
|
||||||
* @param {Function} cb A callback to call once the account at address is successfully update
|
* @returns {Promise} after the account balance is updated
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateAccount (address, cb = noop) {
|
async _updateAccount (address) {
|
||||||
this._getAccount(address, (err, result) => {
|
// query balance
|
||||||
if (err) return cb(err)
|
const balance = await this._query.getBalance(address)
|
||||||
result.address = address
|
const result = { address, balance }
|
||||||
const accounts = this.store.getState().accounts
|
// update accounts state
|
||||||
// only populate if the entry is still present
|
const { accounts } = this.store.getState()
|
||||||
if (accounts[address]) {
|
// only populate if the entry is still present
|
||||||
accounts[address] = result
|
if (!accounts[address]) return
|
||||||
this.store.updateState({ accounts })
|
accounts[address] = result
|
||||||
}
|
this.store.updateState({ accounts })
|
||||||
cb(null, result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current balance of an account via EthQuery.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {string} address A hex address of a the account to query
|
|
||||||
* @param {Function} cb A callback to call once the account at address is successfully update
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_getAccount (address, cb = noop) {
|
|
||||||
const query = this._query
|
|
||||||
async.parallel({
|
|
||||||
balance: query.getBalance.bind(query, address),
|
|
||||||
}, cb)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* Returns an EventEmitter that proxies events from the given event emitter
|
|
||||||
* @param {any} eventEmitter
|
|
||||||
* @param {object} listeners - The listeners to proxy to
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
module.exports = function createEventEmitterProxy (eventEmitter, listeners) {
|
|
||||||
let target = eventEmitter
|
|
||||||
const eventHandlers = listeners || {}
|
|
||||||
const proxy = /** @type {any} */ (new Proxy({}, {
|
|
||||||
get: (_, name) => {
|
|
||||||
// intercept listeners
|
|
||||||
if (name === 'on') return addListener
|
|
||||||
if (name === 'setTarget') return setTarget
|
|
||||||
if (name === 'proxyEventHandlers') return eventHandlers
|
|
||||||
return (/** @type {any} */ (target))[name]
|
|
||||||
},
|
|
||||||
set: (_, name, value) => {
|
|
||||||
target[name] = value
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
function setTarget (/** @type {EventEmitter} */ eventEmitter) {
|
|
||||||
target = eventEmitter
|
|
||||||
// migrate listeners
|
|
||||||
Object.keys(eventHandlers).forEach((name) => {
|
|
||||||
/** @type {Array<Function>} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Attaches a function to be called whenever the specified event is emitted
|
|
||||||
* @param {string} name
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
function addListener (name, handler) {
|
|
||||||
if (!eventHandlers[name]) eventHandlers[name] = []
|
|
||||||
eventHandlers[name].push(handler)
|
|
||||||
target.on(name, handler)
|
|
||||||
}
|
|
||||||
if (listeners) proxy.setTarget(eventEmitter)
|
|
||||||
return proxy
|
|
||||||
}
|
|
@ -69,10 +69,39 @@ module.exports = class MessageManager extends EventEmitter {
|
|||||||
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
|
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
|
||||||
*
|
*
|
||||||
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
|
* @returns {promise} after signature has been
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
addUnapprovedMessageAsync (msgParams, req) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const msgId = this.addUnapprovedMessage(msgParams, req)
|
||||||
|
// await finished
|
||||||
|
this.once(`${msgId}:finished`, (data) => {
|
||||||
|
switch (data.status) {
|
||||||
|
case 'signed':
|
||||||
|
return resolve(data.rawSig)
|
||||||
|
case 'rejected':
|
||||||
|
return reject(new Error('MetaMask Message Signature: User denied message signature.'))
|
||||||
|
default:
|
||||||
|
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Message with an 'unapproved' status using the passed msgParams. this.addMsg is called to add the
|
||||||
|
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
|
||||||
|
*
|
||||||
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object where the origin may be specificied
|
||||||
* @returns {number} The id of the newly created message.
|
* @returns {number} The id of the newly created message.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addUnapprovedMessage (msgParams) {
|
addUnapprovedMessage (msgParams, req) {
|
||||||
|
// add origin from request
|
||||||
|
if (req) msgParams.origin = req.origin
|
||||||
msgParams.data = normalizeMsgData(msgParams.data)
|
msgParams.data = normalizeMsgData(msgParams.data)
|
||||||
// create txData obj with parameters and meta data
|
// create txData obj with parameters and meta data
|
||||||
var time = (new Date()).getTime()
|
var time = (new Date()).getTime()
|
||||||
|
@ -73,11 +73,43 @@ module.exports = class PersonalMessageManager extends EventEmitter {
|
|||||||
* this.memStore.
|
* this.memStore.
|
||||||
*
|
*
|
||||||
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
|
* @returns {promise} When the message has been signed or rejected
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
addUnapprovedMessageAsync (msgParams, req) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!msgParams.from) {
|
||||||
|
reject(new Error('MetaMask Message Signature: from field is required.'))
|
||||||
|
}
|
||||||
|
const msgId = this.addUnapprovedMessage(msgParams, req)
|
||||||
|
this.once(`${msgId}:finished`, (data) => {
|
||||||
|
switch (data.status) {
|
||||||
|
case 'signed':
|
||||||
|
return resolve(data.rawSig)
|
||||||
|
case 'rejected':
|
||||||
|
return reject(new Error('MetaMask Message Signature: User denied message signature.'))
|
||||||
|
default:
|
||||||
|
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new PersonalMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||||
|
* the new PersonalMessage to this.messages, and to save the unapproved PersonalMessages from that list to
|
||||||
|
* this.memStore.
|
||||||
|
*
|
||||||
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
* @returns {number} The id of the newly created PersonalMessage.
|
* @returns {number} The id of the newly created PersonalMessage.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addUnapprovedMessage (msgParams) {
|
addUnapprovedMessage (msgParams, req) {
|
||||||
log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
||||||
|
// add origin from request
|
||||||
|
if (req) msgParams.origin = req.origin
|
||||||
msgParams.data = this.normalizeMsgData(msgParams.data)
|
msgParams.data = this.normalizeMsgData(msgParams.data)
|
||||||
// create txData obj with parameters and meta data
|
// create txData obj with parameters and meta data
|
||||||
var time = (new Date()).getTime()
|
var time = (new Date()).getTime()
|
||||||
@ -257,4 +289,3 @@ module.exports = class PersonalMessageManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,11 +70,11 @@ function simplifyErrorMessages (report) {
|
|||||||
|
|
||||||
function rewriteErrorMessages (report, rewriteFn) {
|
function rewriteErrorMessages (report, rewriteFn) {
|
||||||
// rewrite top level message
|
// rewrite top level message
|
||||||
if (report.message) report.message = rewriteFn(report.message)
|
if (typeof report.message === 'string') report.message = rewriteFn(report.message)
|
||||||
// rewrite each exception message
|
// rewrite each exception message
|
||||||
if (report.exception && report.exception.values) {
|
if (report.exception && report.exception.values) {
|
||||||
report.exception.values.forEach(item => {
|
report.exception.values.forEach(item => {
|
||||||
item.value = rewriteFn(item.value)
|
if (typeof item.value === 'string') item.value = rewriteFn(item.value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,40 @@ module.exports = class TypedMessageManager extends EventEmitter {
|
|||||||
* this.memStore. Before any of this is done, msgParams are validated
|
* this.memStore. Before any of this is done, msgParams are validated
|
||||||
*
|
*
|
||||||
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
|
* @returns {promise} When the message has been signed or rejected
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
addUnapprovedMessageAsync (msgParams, req) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const msgId = this.addUnapprovedMessage(msgParams, req)
|
||||||
|
this.once(`${msgId}:finished`, (data) => {
|
||||||
|
switch (data.status) {
|
||||||
|
case 'signed':
|
||||||
|
return resolve(data.rawSig)
|
||||||
|
case 'rejected':
|
||||||
|
return reject(new Error('MetaMask Message Signature: User denied message signature.'))
|
||||||
|
default:
|
||||||
|
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new TypedMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||||
|
* the new TypedMessage to this.messages, and to save the unapproved TypedMessages from that list to
|
||||||
|
* this.memStore. Before any of this is done, msgParams are validated
|
||||||
|
*
|
||||||
|
* @param {Object} msgParams The params for the eth_sign call to be made after the message is approved.
|
||||||
|
* @param {Object} req (optional) The original request object possibly containing the origin
|
||||||
* @returns {number} The id of the newly created TypedMessage.
|
* @returns {number} The id of the newly created TypedMessage.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addUnapprovedMessage (msgParams) {
|
addUnapprovedMessage (msgParams, req) {
|
||||||
this.validateParams(msgParams)
|
this.validateParams(msgParams)
|
||||||
|
// add origin from request
|
||||||
|
if (req) msgParams.origin = req.origin
|
||||||
|
|
||||||
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
||||||
// create txData obj with parameters and meta data
|
// create txData obj with parameters and meta data
|
||||||
|
@ -127,7 +127,21 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) {
|
|||||||
return targetBN.mul(numBN).div(denomBN)
|
return targetBN.mul(numBN).div(denomBN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyListeners (listeners, emitter) {
|
||||||
|
Object.keys(listeners).forEach((key) => {
|
||||||
|
emitter.on(key, listeners[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeListeners (listeners, emitter) {
|
||||||
|
Object.keys(listeners).forEach((key) => {
|
||||||
|
emitter.removeListener(key, listeners[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
removeListeners,
|
||||||
|
applyListeners,
|
||||||
getPlatform,
|
getPlatform,
|
||||||
getStack,
|
getStack,
|
||||||
getEnvironmentType,
|
getEnvironmentType,
|
||||||
|
@ -46,7 +46,6 @@ const BN = require('ethereumjs-util').BN
|
|||||||
const GWEI_BN = new BN('1000000000')
|
const GWEI_BN = new BN('1000000000')
|
||||||
const percentile = require('percentile')
|
const percentile = require('percentile')
|
||||||
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
|
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
|
||||||
const cleanErrorStack = require('./lib/cleanErrorStack')
|
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const TrezorKeyring = require('eth-trezor-keyring')
|
const TrezorKeyring = require('eth-trezor-keyring')
|
||||||
|
|
||||||
@ -107,8 +106,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
this.blacklistController.scheduleUpdates()
|
this.blacklistController.scheduleUpdates()
|
||||||
|
|
||||||
// rpc provider
|
// rpc provider
|
||||||
this.provider = this.initializeProvider()
|
this.initializeProvider()
|
||||||
this.blockTracker = this.provider._blockTracker
|
this.provider = this.networkController.getProviderAndBlockTracker().provider
|
||||||
|
this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker
|
||||||
|
|
||||||
// token exchange rate tracker
|
// token exchange rate tracker
|
||||||
this.tokenRatesController = new TokenRatesController({
|
this.tokenRatesController = new TokenRatesController({
|
||||||
@ -252,28 +252,22 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
static: {
|
static: {
|
||||||
eth_syncing: false,
|
eth_syncing: false,
|
||||||
web3_clientVersion: `MetaMask/v${version}`,
|
web3_clientVersion: `MetaMask/v${version}`,
|
||||||
eth_sendTransaction: (payload, next, end) => {
|
|
||||||
const origin = payload.origin
|
|
||||||
const txParams = payload.params[0]
|
|
||||||
nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// account mgmt
|
// account mgmt
|
||||||
getAccounts: (cb) => {
|
getAccounts: async () => {
|
||||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||||
const result = []
|
|
||||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||||
|
|
||||||
// only show address if account is unlocked
|
// only show address if account is unlocked
|
||||||
if (isUnlocked && selectedAddress) {
|
if (isUnlocked && selectedAddress) {
|
||||||
result.push(selectedAddress)
|
return [selectedAddress]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
cb(null, result)
|
|
||||||
},
|
},
|
||||||
// tx signing
|
// tx signing
|
||||||
// old style msg signing
|
processTransaction: this.newUnapprovedTransaction.bind(this),
|
||||||
processMessage: this.newUnsignedMessage.bind(this),
|
// msg signing
|
||||||
// personal_sign msg signing
|
processEthSignMessage: this.newUnsignedMessage.bind(this),
|
||||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||||
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
|
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
|
||||||
}
|
}
|
||||||
@ -809,6 +803,18 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Identity Management (signature operations)
|
// Identity Management (signature operations)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a Dapp suggests a new tx to be signed.
|
||||||
|
* this wrapper needs to exist so we can provide a reference to
|
||||||
|
* "newUnapprovedTransaction" before "txController" is instantiated
|
||||||
|
*
|
||||||
|
* @param {Object} msgParams - The params passed to eth_sign.
|
||||||
|
* @param {Object} req - (optional) the original request, containing the origin
|
||||||
|
*/
|
||||||
|
async newUnapprovedTransaction (txParams, req) {
|
||||||
|
return await this.txController.newUnapprovedTransaction(txParams, req)
|
||||||
|
}
|
||||||
|
|
||||||
// eth_sign methods:
|
// eth_sign methods:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -820,20 +826,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {Object} msgParams - The params passed to eth_sign.
|
* @param {Object} msgParams - The params passed to eth_sign.
|
||||||
* @param {Function} cb = The callback function called with the signature.
|
* @param {Function} cb = The callback function called with the signature.
|
||||||
*/
|
*/
|
||||||
newUnsignedMessage (msgParams, cb) {
|
newUnsignedMessage (msgParams, req) {
|
||||||
const msgId = this.messageManager.addUnapprovedMessage(msgParams)
|
const promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req)
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
this.opts.showUnconfirmedMessage()
|
this.opts.showUnconfirmedMessage()
|
||||||
this.messageManager.once(`${msgId}:finished`, (data) => {
|
return promise
|
||||||
switch (data.status) {
|
|
||||||
case 'signed':
|
|
||||||
return cb(null, data.rawSig)
|
|
||||||
case 'rejected':
|
|
||||||
return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.')))
|
|
||||||
default:
|
|
||||||
return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -887,24 +884,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {Function} cb - The callback function called with the signature.
|
* @param {Function} cb - The callback function called with the signature.
|
||||||
* Passed back to the requesting Dapp.
|
* Passed back to the requesting Dapp.
|
||||||
*/
|
*/
|
||||||
newUnsignedPersonalMessage (msgParams, cb) {
|
async newUnsignedPersonalMessage (msgParams, req) {
|
||||||
if (!msgParams.from) {
|
const promise = this.personalMessageManager.addUnapprovedMessageAsync(msgParams, req)
|
||||||
return cb(cleanErrorStack(new Error('MetaMask Message Signature: from field is required.')))
|
|
||||||
}
|
|
||||||
|
|
||||||
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
|
|
||||||
this.sendUpdate()
|
this.sendUpdate()
|
||||||
this.opts.showUnconfirmedMessage()
|
this.opts.showUnconfirmedMessage()
|
||||||
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
|
return promise
|
||||||
switch (data.status) {
|
|
||||||
case 'signed':
|
|
||||||
return cb(null, data.rawSig)
|
|
||||||
case 'rejected':
|
|
||||||
return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.')))
|
|
||||||
default:
|
|
||||||
return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -953,26 +937,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
* @param {Object} msgParams - The params passed to eth_signTypedData.
|
* @param {Object} msgParams - The params passed to eth_signTypedData.
|
||||||
* @param {Function} cb - The callback function, called with the signature.
|
* @param {Function} cb - The callback function, called with the signature.
|
||||||
*/
|
*/
|
||||||
newUnsignedTypedMessage (msgParams, cb) {
|
newUnsignedTypedMessage (msgParams, req) {
|
||||||
let msgId
|
const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req)
|
||||||
try {
|
this.sendUpdate()
|
||||||
msgId = this.typedMessageManager.addUnapprovedMessage(msgParams)
|
this.opts.showUnconfirmedMessage()
|
||||||
this.sendUpdate()
|
return promise
|
||||||
this.opts.showUnconfirmedMessage()
|
|
||||||
} catch (e) {
|
|
||||||
return cb(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.typedMessageManager.once(`${msgId}:finished`, (data) => {
|
|
||||||
switch (data.status) {
|
|
||||||
case 'signed':
|
|
||||||
return cb(null, data.rawSig)
|
|
||||||
case 'rejected':
|
|
||||||
return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.')))
|
|
||||||
default:
|
|
||||||
return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1237,7 +1206,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// create filter polyfill middleware
|
// create filter polyfill middleware
|
||||||
const filterMiddleware = createFilterMiddleware({
|
const filterMiddleware = createFilterMiddleware({
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
blockTracker: this.provider._blockTracker,
|
blockTracker: this.blockTracker,
|
||||||
})
|
})
|
||||||
|
|
||||||
engine.push(createOriginMiddleware({ origin }))
|
engine.push(createOriginMiddleware({ origin }))
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
### Developing on Dependencies
|
### Developing on Dependencies
|
||||||
|
|
||||||
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies:
|
To enjoy the live-reloading that `gulp dev` offers while working on the dependencies:
|
||||||
|
|
||||||
1. Clone the dependency locally.
|
1. Clone the dependency locally.
|
||||||
2. `npm install` in its folder.
|
2. `npm install` in its folder.
|
||||||
3. Run `npm link` in its folder.
|
3. Run `npm link` in its folder.
|
||||||
4. Run `npm link $DEP_NAME` in this project folder.
|
4. Run `npm link $DEP_NAME` in this project folder.
|
||||||
5. Next time you `npm start` it will watch the dependency for changes as well!
|
5. Next time you `npm start` it will watch the dependency for changes as well!
|
||||||
|
|
||||||
|
@ -89,8 +89,6 @@ MetaMask has two kinds of [duplex stream APIs](https://github.com/substack/strea
|
|||||||
|
|
||||||
If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background).
|
If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background).
|
||||||
|
|
||||||
To make this as easy as possible, we use one of our favorite internal tools, [web3-provider-engine](https://www.npmjs.com/package/web3-provider-engine) to construct a custom web3 provider object whose source of truth is a stream that we connect to remotely.
|
|
||||||
|
|
||||||
To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available.
|
To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available.
|
||||||
|
|
||||||
In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic!
|
In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic!
|
||||||
|
@ -116,12 +116,25 @@ Notice.prototype.render = function () {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Notice.prototype.setInitialDisclaimerState = function () {
|
||||||
|
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
|
||||||
|
this.setState({disclaimerDisabled: false})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Notice.prototype.componentDidMount = function () {
|
Notice.prototype.componentDidMount = function () {
|
||||||
// eslint-disable-next-line react/no-find-dom-node
|
// eslint-disable-next-line react/no-find-dom-node
|
||||||
var node = findDOMNode(this)
|
var node = findDOMNode(this)
|
||||||
linker.setupListener(node)
|
linker.setupListener(node)
|
||||||
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
|
this.setInitialDisclaimerState()
|
||||||
this.setState({disclaimerDisabled: false})
|
}
|
||||||
|
|
||||||
|
Notice.prototype.componentDidUpdate = function (prevProps) {
|
||||||
|
const { notice: { id } = {} } = this.props
|
||||||
|
const { notice: { id: prevNoticeId } = {} } = prevProps
|
||||||
|
|
||||||
|
if (id !== prevNoticeId) {
|
||||||
|
this.setInitialDisclaimerState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2817
package-lock.json
generated
2817
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@ -36,7 +36,7 @@
|
|||||||
"test:mascara:build:locales": "mkdirp dist/chrome && cp -R app/_locales dist/chrome/_locales",
|
"test:mascara:build:locales": "mkdirp dist/chrome && cp -R app/_locales dist/chrome/_locales",
|
||||||
"test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js",
|
"test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js",
|
||||||
"test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js",
|
"test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js",
|
||||||
"ganache:start": "ganache-cli -m 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'",
|
"ganache:start": "ganache-cli --noVMErrorsOnRPCResponse -m 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'",
|
||||||
"sentry:publish": "node ./development/sentry-publish.js",
|
"sentry:publish": "node ./development/sentry-publish.js",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
@ -105,10 +105,13 @@
|
|||||||
"ensnare": "^1.0.0",
|
"ensnare": "^1.0.0",
|
||||||
"eslint-plugin-react": "^7.4.0",
|
"eslint-plugin-react": "^7.4.0",
|
||||||
"eth-bin-to-ops": "^1.0.1",
|
"eth-bin-to-ops": "^1.0.1",
|
||||||
|
"eth-block-tracker": "^4.0.1",
|
||||||
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master",
|
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master",
|
||||||
|
"eth-json-rpc-middleware": "^2.4.0",
|
||||||
|
"eth-keyring-controller": "^3.1.4",
|
||||||
"eth-ens-namehash": "^2.0.8",
|
"eth-ens-namehash": "^2.0.8",
|
||||||
"eth-hd-keyring": "^1.2.2",
|
"eth-hd-keyring": "^1.2.2",
|
||||||
"eth-json-rpc-filters": "^1.2.6",
|
"eth-json-rpc-filters": "^2.1.1",
|
||||||
"eth-json-rpc-infura": "^3.0.0",
|
"eth-json-rpc-infura": "^3.0.0",
|
||||||
"eth-method-registry": "^1.0.0",
|
"eth-method-registry": "^1.0.0",
|
||||||
"eth-phishing-detect": "^1.1.4",
|
"eth-phishing-detect": "^1.1.4",
|
||||||
@ -145,7 +148,7 @@
|
|||||||
"iframe-stream": "^3.0.0",
|
"iframe-stream": "^3.0.0",
|
||||||
"inject-css": "^0.1.1",
|
"inject-css": "^0.1.1",
|
||||||
"jazzicon": "^1.2.0",
|
"jazzicon": "^1.2.0",
|
||||||
"json-rpc-engine": "^3.6.1",
|
"json-rpc-engine": "^3.7.3",
|
||||||
"json-rpc-middleware-stream": "^1.0.1",
|
"json-rpc-middleware-stream": "^1.0.1",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.memoize": "^4.1.2",
|
"lodash.memoize": "^4.1.2",
|
||||||
@ -202,11 +205,11 @@
|
|||||||
"shallow-copy": "0.0.1",
|
"shallow-copy": "0.0.1",
|
||||||
"sw-controller": "^1.0.3",
|
"sw-controller": "^1.0.3",
|
||||||
"sw-stream": "^2.0.2",
|
"sw-stream": "^2.0.2",
|
||||||
|
"swappable-obj-proxy": "^1.0.2",
|
||||||
"textarea-caret": "^3.0.1",
|
"textarea-caret": "^3.0.1",
|
||||||
"valid-url": "^1.0.9",
|
"valid-url": "^1.0.9",
|
||||||
"vreme": "^3.0.2",
|
"vreme": "^3.0.2",
|
||||||
"web3": "^0.20.1",
|
"web3": "^0.20.1",
|
||||||
"web3-provider-engine": "^14.0.5",
|
|
||||||
"web3-stream-provider": "^3.0.1",
|
"web3-stream-provider": "^3.0.1",
|
||||||
"webrtc-adapter": "^6.3.0",
|
"webrtc-adapter": "^6.3.0",
|
||||||
"xtend": "^4.0.1"
|
"xtend": "^4.0.1"
|
||||||
@ -249,6 +252,7 @@
|
|||||||
"eth-json-rpc-middleware": "^1.6.0",
|
"eth-json-rpc-middleware": "^1.6.0",
|
||||||
"eth-keyring-controller": "^3.3.1",
|
"eth-keyring-controller": "^3.3.1",
|
||||||
"file-loader": "^1.1.11",
|
"file-loader": "^1.1.11",
|
||||||
|
"fs-extra": "^6.0.1",
|
||||||
"fs-promise": "^2.0.3",
|
"fs-promise": "^2.0.3",
|
||||||
"ganache-cli": "^6.1.0",
|
"ganache-cli": "^6.1.0",
|
||||||
"ganache-core": "^2.1.5",
|
"ganache-core": "^2.1.5",
|
||||||
@ -293,6 +297,7 @@
|
|||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"png-file-stream": "^1.0.0",
|
"png-file-stream": "^1.0.0",
|
||||||
|
"prepend-file": "^1.3.1",
|
||||||
"prompt": "^1.0.0",
|
"prompt": "^1.0.0",
|
||||||
"proxyquire": "2.0.1",
|
"proxyquire": "2.0.1",
|
||||||
"qs": "^6.2.0",
|
"qs": "^6.2.0",
|
||||||
|
@ -2,8 +2,8 @@ const fs = require('fs')
|
|||||||
const mkdirp = require('mkdirp')
|
const mkdirp = require('mkdirp')
|
||||||
const pify = require('pify')
|
const pify = require('pify')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const {until} = require('selenium-webdriver')
|
|
||||||
const { delay } = require('../func')
|
const { delay } = require('../func')
|
||||||
|
const { until } = require('selenium-webdriver')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
assertElementNotPresent,
|
assertElementNotPresent,
|
||||||
|
@ -525,6 +525,15 @@ describe('MetaMask', function () {
|
|||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('confirms a deploy contract transaction in the popup', async () => {
|
||||||
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
|
const popup = windowHandles[2]
|
||||||
|
await driver.switchTo().window(popup)
|
||||||
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
|
await confirmButton.click()
|
||||||
|
await delay(regularDelayMs)
|
||||||
|
})
|
||||||
|
|
||||||
it('calls and confirms a contract method where ETH is sent', async () => {
|
it('calls and confirms a contract method where ETH is sent', async () => {
|
||||||
await driver.switchTo().window(dapp)
|
await driver.switchTo().window(dapp)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
@ -626,20 +635,21 @@ describe('MetaMask', function () {
|
|||||||
|
|
||||||
describe('Add a custom token from a dapp', () => {
|
describe('Add a custom token from a dapp', () => {
|
||||||
it('creates a new token', async () => {
|
it('creates a new token', async () => {
|
||||||
const windowHandles = await driver.getAllWindowHandles()
|
let windowHandles = await driver.getAllWindowHandles()
|
||||||
const extension = windowHandles[0]
|
const extension = windowHandles[0]
|
||||||
const dapp = windowHandles[1]
|
const dapp = windowHandles[1]
|
||||||
await delay(regularDelayMs * 2)
|
await delay(regularDelayMs * 2)
|
||||||
|
|
||||||
await driver.switchTo().window(dapp)
|
await driver.switchTo().window(dapp)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs * 2)
|
||||||
|
|
||||||
const createToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Create Token')]`))
|
const createToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Create Token')]`))
|
||||||
await createToken.click()
|
await createToken.click()
|
||||||
await delay(regularDelayMs)
|
await delay(largeDelayMs)
|
||||||
|
|
||||||
await driver.switchTo().window(extension)
|
windowHandles = await driver.getAllWindowHandles()
|
||||||
await loadExtension(driver, extensionId)
|
const popup = windowHandles[2]
|
||||||
|
await driver.switchTo().window(popup)
|
||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
|
|
||||||
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
|
||||||
@ -1014,4 +1024,4 @@ describe('MetaMask', function () {
|
|||||||
await delay(regularDelayMs)
|
await delay(regularDelayMs)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -1,14 +1,19 @@
|
|||||||
require('chromedriver')
|
require('chromedriver')
|
||||||
require('geckodriver')
|
require('geckodriver')
|
||||||
const fs = require('fs')
|
const fs = require('fs-extra')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const pify = require('pify')
|
||||||
|
const prependFile = pify(require('prepend-file'))
|
||||||
const webdriver = require('selenium-webdriver')
|
const webdriver = require('selenium-webdriver')
|
||||||
const Command = require('selenium-webdriver/lib/command').Command
|
const Command = require('selenium-webdriver/lib/command').Command
|
||||||
const By = webdriver.By
|
const By = webdriver.By
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
delay,
|
delay,
|
||||||
|
createModifiedTestBuild,
|
||||||
|
setupBrowserAndExtension,
|
||||||
|
verboseReportOnFailure,
|
||||||
buildChromeWebDriver,
|
buildChromeWebDriver,
|
||||||
buildFirefoxWebdriver,
|
buildFirefoxWebdriver,
|
||||||
installWebExt,
|
installWebExt,
|
||||||
@ -20,6 +25,37 @@ function delay (time) {
|
|||||||
return new Promise(resolve => setTimeout(resolve, time))
|
return new Promise(resolve => setTimeout(resolve, time))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createModifiedTestBuild ({ browser, srcPath }) {
|
||||||
|
// copy build to test-builds directory
|
||||||
|
const extPath = path.resolve(`test-builds/${browser}`)
|
||||||
|
await fs.ensureDir(extPath)
|
||||||
|
await fs.copy(srcPath, extPath)
|
||||||
|
// inject METAMASK_TEST_CONFIG setting default test network
|
||||||
|
const config = { NetworkController: { provider: { type: 'localhost' } } }
|
||||||
|
await prependFile(`${extPath}/background.js`, `window.METAMASK_TEST_CONFIG=${JSON.stringify(config)};\n`)
|
||||||
|
return { extPath }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupBrowserAndExtension ({ browser, extPath }) {
|
||||||
|
let driver, extensionId, extensionUri
|
||||||
|
|
||||||
|
if (browser === 'chrome') {
|
||||||
|
driver = buildChromeWebDriver(extPath)
|
||||||
|
extensionId = await getExtensionIdChrome(driver)
|
||||||
|
extensionUri = `chrome-extension://${extensionId}/home.html`
|
||||||
|
} else if (browser === 'firefox') {
|
||||||
|
driver = buildFirefoxWebdriver()
|
||||||
|
await installWebExt(driver, extPath)
|
||||||
|
await delay(700)
|
||||||
|
extensionId = await getExtensionIdFirefox(driver)
|
||||||
|
extensionUri = `moz-extension://${extensionId}/home.html`
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown Browser "${browser}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { driver, extensionId, extensionUri }
|
||||||
|
}
|
||||||
|
|
||||||
function buildChromeWebDriver (extPath) {
|
function buildChromeWebDriver (extPath) {
|
||||||
const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile'))
|
const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile'))
|
||||||
return new webdriver.Builder()
|
return new webdriver.Builder()
|
||||||
@ -61,3 +97,13 @@ async function installWebExt (driver, extension) {
|
|||||||
|
|
||||||
return await driver.schedule(cmd, 'installWebExt(' + extension + ')')
|
return await driver.schedule(cmd, 'installWebExt(' + extension + ')')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function verboseReportOnFailure ({ browser, driver, title }) {
|
||||||
|
const artifactDir = `./test-artifacts/${browser}/${title}`
|
||||||
|
const filepathBase = `${artifactDir}/test-failure`
|
||||||
|
await fs.ensureDir(artifactDir)
|
||||||
|
const screenshot = await driver.takeScreenshot()
|
||||||
|
await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
||||||
|
const htmlSource = await driver.getPageSource()
|
||||||
|
await fs.writeFile(`${filepathBase}-dom.html`, htmlSource)
|
||||||
|
}
|
||||||
|
@ -1,49 +1,41 @@
|
|||||||
const fs = require('fs')
|
|
||||||
const mkdirp = require('mkdirp')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const pify = require('pify')
|
const { By, Key, until } = require('selenium-webdriver')
|
||||||
const webdriver = require('selenium-webdriver')
|
const { delay, createModifiedTestBuild, setupBrowserAndExtension, verboseReportOnFailure } = require('./func')
|
||||||
const { By, Key, until } = webdriver
|
|
||||||
const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func')
|
|
||||||
|
|
||||||
describe('Metamask popup page', function () {
|
describe('Metamask popup page', function () {
|
||||||
let driver, accountAddress, tokenAddress, extensionId
|
const browser = process.env.SELENIUM_BROWSER
|
||||||
|
let driver, accountAddress, tokenAddress, extensionUri
|
||||||
|
|
||||||
this.timeout(0)
|
this.timeout(0)
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
const srcPath = path.resolve(`dist/${browser}`)
|
||||||
const extPath = path.resolve('dist/chrome')
|
const { extPath } = await createModifiedTestBuild({ browser, srcPath })
|
||||||
driver = buildChromeWebDriver(extPath)
|
const installResult = await setupBrowserAndExtension({ browser, extPath })
|
||||||
extensionId = await getExtensionIdChrome(driver)
|
driver = installResult.driver
|
||||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
extensionUri = installResult.extensionUri
|
||||||
|
|
||||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
await driver.get(extensionUri)
|
||||||
const extPath = path.resolve('dist/firefox')
|
await delay(300)
|
||||||
driver = buildFirefoxWebdriver()
|
|
||||||
await installWebExt(driver, extPath)
|
|
||||||
await delay(700)
|
|
||||||
extensionId = await getExtensionIdFirefox(driver)
|
|
||||||
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async function () {
|
afterEach(async function () {
|
||||||
// logs command not supported in firefox
|
// logs command not supported in firefox
|
||||||
// https://github.com/SeleniumHQ/selenium/issues/2910
|
// https://github.com/SeleniumHQ/selenium/issues/2910
|
||||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
if (browser === 'chrome') {
|
||||||
// check for console errors
|
// check for console errors
|
||||||
const errors = await checkBrowserForConsoleErrors()
|
const errors = await checkBrowserForConsoleErrors()
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
const errorReports = errors.map(err => err.message)
|
const errorReports = errors.map(err => err.message)
|
||||||
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
|
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
|
||||||
this.test.error(new Error(errorMessage))
|
console.error(new Error(errorMessage))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// gather extra data if test failed
|
// gather extra data if test failed
|
||||||
if (this.currentTest.state === 'failed') {
|
if (this.currentTest.state === 'failed') {
|
||||||
await verboseReportOnFailure(this.currentTest)
|
await verboseReportOnFailure({ browser, driver, title: this.currentTest.title })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -54,7 +46,6 @@ describe('Metamask popup page', function () {
|
|||||||
describe('Setup', function () {
|
describe('Setup', function () {
|
||||||
|
|
||||||
it('switches to Chrome extensions list', async function () {
|
it('switches to Chrome extensions list', async function () {
|
||||||
await delay(300)
|
|
||||||
const windowHandles = await driver.getAllWindowHandles()
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
await driver.switchTo().window(windowHandles[0])
|
await driver.switchTo().window(windowHandles[0])
|
||||||
})
|
})
|
||||||
@ -98,6 +89,7 @@ describe('Metamask popup page', function () {
|
|||||||
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => {
|
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => {
|
||||||
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button'))
|
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button'))
|
||||||
await button.click()
|
await button.click()
|
||||||
|
await delay(300)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows privacy notice', async () => {
|
it('shows privacy notice', async () => {
|
||||||
@ -108,7 +100,6 @@ describe('Metamask popup page', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('shows phishing notice', async () => {
|
it('shows phishing notice', async () => {
|
||||||
await delay(300)
|
|
||||||
const noticeHeader = await driver.findElement(By.css('.terms-header')).getText()
|
const noticeHeader = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
assert.equal(noticeHeader, 'PHISHING WARNING', 'shows phishing warning')
|
assert.equal(noticeHeader, 'PHISHING WARNING', 'shows phishing warning')
|
||||||
const element = await driver.findElement(By.css('.markdown'))
|
const element = await driver.findElement(By.css('.markdown'))
|
||||||
@ -295,11 +286,7 @@ describe('Metamask popup page', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('navigates back to MetaMask popup in the tab', async function () {
|
it('navigates back to MetaMask popup in the tab', async function () {
|
||||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
await driver.get(extensionUri)
|
||||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
|
||||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
|
||||||
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
|
||||||
}
|
|
||||||
await delay(700)
|
await delay(700)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -362,21 +349,4 @@ describe('Metamask popup page', function () {
|
|||||||
return matchedErrorObjects
|
return matchedErrorObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verboseReportOnFailure (test) {
|
|
||||||
let artifactDir
|
|
||||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
|
||||||
artifactDir = `./test-artifacts/chrome/${test.title}`
|
|
||||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
|
||||||
artifactDir = `./test-artifacts/firefox/${test.title}`
|
|
||||||
}
|
|
||||||
const filepathBase = `${artifactDir}/test-failure`
|
|
||||||
await pify(mkdirp)(artifactDir)
|
|
||||||
// capture screenshot
|
|
||||||
const screenshot = await driver.takeScreenshot()
|
|
||||||
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
|
||||||
// capture dom source
|
|
||||||
const htmlSource = await driver.getPageSource()
|
|
||||||
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
|
const Ganache = require('ganache-core')
|
||||||
|
const nock = require('nock')
|
||||||
import Enzyme from 'enzyme'
|
import Enzyme from 'enzyme'
|
||||||
import Adapter from 'enzyme-adapter-react-15'
|
import Adapter from 'enzyme-adapter-react-15'
|
||||||
|
|
||||||
|
nock.disableNetConnect()
|
||||||
|
nock.enableNetConnect('localhost')
|
||||||
|
|
||||||
Enzyme.configure({ adapter: new Adapter() })
|
Enzyme.configure({ adapter: new Adapter() })
|
||||||
// disallow promises from swallowing errors
|
// disallow promises from swallowing errors
|
||||||
enableFailureOnUnhandledPromiseRejection()
|
enableFailureOnUnhandledPromiseRejection()
|
||||||
|
|
||||||
|
// ganache server
|
||||||
|
const server = Ganache.server()
|
||||||
|
server.listen(8545, () => {
|
||||||
|
console.log('Ganache Testrpc is running on "http://localhost:8545"')
|
||||||
|
})
|
||||||
|
|
||||||
// logging util
|
// logging util
|
||||||
var log = require('loglevel')
|
var log = require('loglevel')
|
||||||
log.setDefaultLevel(5)
|
log.setDefaultLevel(5)
|
||||||
@ -14,6 +25,9 @@ global.log = log
|
|||||||
// polyfills
|
// polyfills
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// fetch
|
||||||
|
global.fetch = require('isomorphic-fetch')
|
||||||
|
|
||||||
// dom
|
// dom
|
||||||
require('jsdom-global')()
|
require('jsdom-global')()
|
||||||
|
|
||||||
|
16
test/lib/createTxMeta.js
Normal file
16
test/lib/createTxMeta.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
||||||
|
|
||||||
|
module.exports = createTxMeta
|
||||||
|
|
||||||
|
function createTxMeta (partialMeta) {
|
||||||
|
const txMeta = Object.assign({
|
||||||
|
status: 'unapproved',
|
||||||
|
txParams: {},
|
||||||
|
}, partialMeta)
|
||||||
|
// initialize history
|
||||||
|
txMeta.history = []
|
||||||
|
// capture initial snapshot of txMeta for history
|
||||||
|
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
|
||||||
|
txMeta.history.push(snapshot)
|
||||||
|
return txMeta
|
||||||
|
}
|
@ -1,6 +1,3 @@
|
|||||||
// polyfill fetch
|
|
||||||
global.fetch = global.fetch || require('isomorphic-fetch')
|
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const nock = require('nock')
|
const nock = require('nock')
|
||||||
const CurrencyController = require('../../../../app/scripts/controllers/currency')
|
const CurrencyController = require('../../../../app/scripts/controllers/currency')
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
const nock = require('nock')
|
||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const DetectTokensController = require('../../../../app/scripts/controllers/detect-tokens')
|
const DetectTokensController = require('../../../../app/scripts/controllers/detect-tokens')
|
||||||
@ -6,15 +7,34 @@ const NetworkController = require('../../../../app/scripts/controllers/network/n
|
|||||||
const PreferencesController = require('../../../../app/scripts/controllers/preferences')
|
const PreferencesController = require('../../../../app/scripts/controllers/preferences')
|
||||||
|
|
||||||
describe('DetectTokensController', () => {
|
describe('DetectTokensController', () => {
|
||||||
const sandbox = sinon.createSandbox()
|
const sandbox = sinon.createSandbox()
|
||||||
let clock, keyringMemStore, network, preferences
|
let clock, keyringMemStore, network, preferences, controller
|
||||||
beforeEach(async () => {
|
|
||||||
keyringMemStore = new ObservableStore({ isUnlocked: false})
|
const noop = () => {}
|
||||||
network = new NetworkController({ provider: { type: 'mainnet' }})
|
|
||||||
preferences = new PreferencesController({ network })
|
const networkControllerProviderConfig = {
|
||||||
})
|
getAccounts: noop,
|
||||||
after(() => {
|
}
|
||||||
sandbox.restore()
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
|
||||||
|
|
||||||
|
nock('https://api.infura.io')
|
||||||
|
.get(/.*/)
|
||||||
|
.reply(200)
|
||||||
|
|
||||||
|
keyringMemStore = new ObservableStore({ isUnlocked: false})
|
||||||
|
network = new NetworkController()
|
||||||
|
preferences = new PreferencesController({ network })
|
||||||
|
controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||||
|
|
||||||
|
network.initializeProvider(networkControllerProviderConfig)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
sandbox.restore()
|
||||||
|
nock.cleanAll()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should poll on correct interval', async () => {
|
it('should poll on correct interval', async () => {
|
||||||
@ -26,7 +46,10 @@ describe('DetectTokensController', () => {
|
|||||||
|
|
||||||
it('should be called on every polling period', async () => {
|
it('should be called on every polling period', async () => {
|
||||||
clock = sandbox.useFakeTimers()
|
clock = sandbox.useFakeTimers()
|
||||||
|
const network = new NetworkController()
|
||||||
|
network.initializeProvider(networkControllerProviderConfig)
|
||||||
network.setProviderType('mainnet')
|
network.setProviderType('mainnet')
|
||||||
|
const preferences = new PreferencesController({ network })
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = true
|
controller.isUnlocked = true
|
||||||
@ -44,8 +67,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not check tokens while in test network', async () => {
|
it('should not check tokens while in test network', async () => {
|
||||||
network.setProviderType('rinkeby')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = true
|
controller.isUnlocked = true
|
||||||
|
|
||||||
@ -58,7 +79,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should only check and add tokens while in main network', async () => {
|
it('should only check and add tokens while in main network', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = true
|
controller.isUnlocked = true
|
||||||
@ -75,7 +95,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not detect same token while in main network', async () => {
|
it('should not detect same token while in main network', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
preferences.addToken('0x0d262e5dc4a06a0f1c90ce79c7a60c09dfc884e4', 'J8T', 8)
|
preferences.addToken('0x0d262e5dc4a06a0f1c90ce79c7a60c09dfc884e4', 'J8T', 8)
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
@ -93,8 +112,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should trigger detect new tokens when change address', async () => {
|
it('should trigger detect new tokens when change address', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = true
|
controller.isUnlocked = true
|
||||||
var stub = sandbox.stub(controller, 'detectNewTokens')
|
var stub = sandbox.stub(controller, 'detectNewTokens')
|
||||||
@ -103,8 +120,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should trigger detect new tokens when submit password', async () => {
|
it('should trigger detect new tokens when submit password', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.selectedAddress = '0x0'
|
controller.selectedAddress = '0x0'
|
||||||
var stub = sandbox.stub(controller, 'detectNewTokens')
|
var stub = sandbox.stub(controller, 'detectNewTokens')
|
||||||
@ -113,8 +128,6 @@ describe('DetectTokensController', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not trigger detect new tokens when not open or not unlocked', async () => {
|
it('should not trigger detect new tokens when not open or not unlocked', async () => {
|
||||||
network.setProviderType('mainnet')
|
|
||||||
const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore })
|
|
||||||
controller.isOpen = true
|
controller.isOpen = true
|
||||||
controller.isUnlocked = false
|
controller.isUnlocked = false
|
||||||
var stub = sandbox.stub(controller, 'detectTokenBalance')
|
var stub = sandbox.stub(controller, 'detectTokenBalance')
|
||||||
@ -125,4 +138,4 @@ describe('DetectTokensController', () => {
|
|||||||
clock.tick(180000)
|
clock.tick(180000)
|
||||||
sandbox.assert.notCalled(stub)
|
sandbox.assert.notCalled(stub)
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -3,9 +3,10 @@ const sinon = require('sinon')
|
|||||||
const clone = require('clone')
|
const clone = require('clone')
|
||||||
const nock = require('nock')
|
const nock = require('nock')
|
||||||
const createThoughStream = require('through2').obj
|
const createThoughStream = require('through2').obj
|
||||||
const MetaMaskController = require('../../../../app/scripts/metamask-controller')
|
|
||||||
const blacklistJSON = require('eth-phishing-detect/src/config')
|
const blacklistJSON = require('eth-phishing-detect/src/config')
|
||||||
const firstTimeState = require('../../../../app/scripts/first-time-state')
|
const MetaMaskController = require('../../../../app/scripts/metamask-controller')
|
||||||
|
const firstTimeState = require('../../../unit/localhostState')
|
||||||
|
const createTxMeta = require('../../../lib/createTxMeta')
|
||||||
|
|
||||||
const currentNetworkId = 42
|
const currentNetworkId = 42
|
||||||
const DEFAULT_LABEL = 'Account 1'
|
const DEFAULT_LABEL = 'Account 1'
|
||||||
@ -13,6 +14,7 @@ const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm re
|
|||||||
const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
|
const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
|
||||||
const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle'
|
const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle'
|
||||||
const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
||||||
|
const CUSTOM_RPC_URL = 'http://localhost:8545'
|
||||||
|
|
||||||
describe('MetaMaskController', function () {
|
describe('MetaMaskController', function () {
|
||||||
let metamaskController
|
let metamaskController
|
||||||
@ -346,29 +348,19 @@ describe('MetaMaskController', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('#setCustomRpc', function () {
|
describe('#setCustomRpc', function () {
|
||||||
const customRPC = 'https://custom.rpc/'
|
|
||||||
let rpcTarget
|
let rpcTarget
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
rpcTarget = metamaskController.setCustomRpc(CUSTOM_RPC_URL)
|
||||||
nock('https://custom.rpc')
|
|
||||||
.post('/')
|
|
||||||
.reply(200)
|
|
||||||
|
|
||||||
rpcTarget = metamaskController.setCustomRpc(customRPC)
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
nock.cleanAll()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns custom RPC that when called', async function () {
|
it('returns custom RPC that when called', async function () {
|
||||||
assert.equal(await rpcTarget, customRPC)
|
assert.equal(await rpcTarget, CUSTOM_RPC_URL)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('changes the network controller rpc', function () {
|
it('changes the network controller rpc', function () {
|
||||||
const networkControllerState = metamaskController.networkController.store.getState()
|
const networkControllerState = metamaskController.networkController.store.getState()
|
||||||
assert.equal(networkControllerState.provider.rpcTarget, customRPC)
|
assert.equal(networkControllerState.provider.rpcTarget, CUSTOM_RPC_URL)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -473,9 +465,10 @@ describe('MetaMaskController', function () {
|
|||||||
getNetworkstub.returns(42)
|
getNetworkstub.returns(42)
|
||||||
|
|
||||||
metamaskController.txController.txStateManager._saveTxList([
|
metamaskController.txController.txStateManager._saveTxList([
|
||||||
{ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} },
|
createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }),
|
||||||
{ id: 2, status: 'rejected', metamaskNetworkId: 32, txParams: {} },
|
createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }),
|
||||||
{ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} },
|
createTxMeta({ id: 2, status: 'rejected', metamaskNetworkId: 32 }),
|
||||||
|
createTxMeta({ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -552,14 +545,14 @@ describe('MetaMaskController', function () {
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#newUnsignedMessage', function () {
|
describe('#newUnsignedMessage', () => {
|
||||||
|
|
||||||
let msgParams, metamaskMsgs, messages, msgId
|
let msgParams, metamaskMsgs, messages, msgId
|
||||||
|
|
||||||
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
|
||||||
const data = '0x43727970746f6b697474696573'
|
const data = '0x43727970746f6b697474696573'
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async () => {
|
||||||
|
|
||||||
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
|
||||||
|
|
||||||
@ -568,7 +561,10 @@ describe('MetaMaskController', function () {
|
|||||||
'data': data,
|
'data': data,
|
||||||
}
|
}
|
||||||
|
|
||||||
metamaskController.newUnsignedMessage(msgParams, noop)
|
const promise = metamaskController.newUnsignedMessage(msgParams)
|
||||||
|
// handle the promise so it doesn't throw an unhandledRejection
|
||||||
|
promise.then(noop).catch(noop)
|
||||||
|
|
||||||
metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs()
|
metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs()
|
||||||
messages = metamaskController.messageManager.messages
|
messages = metamaskController.messageManager.messages
|
||||||
msgId = Object.keys(metamaskMsgs)[0]
|
msgId = Object.keys(metamaskMsgs)[0]
|
||||||
@ -608,13 +604,16 @@ describe('MetaMaskController', function () {
|
|||||||
|
|
||||||
describe('#newUnsignedPersonalMessage', function () {
|
describe('#newUnsignedPersonalMessage', function () {
|
||||||
|
|
||||||
it('errors with no from in msgParams', function () {
|
it('errors with no from in msgParams', async () => {
|
||||||
const msgParams = {
|
const msgParams = {
|
||||||
'data': data,
|
'data': data,
|
||||||
}
|
}
|
||||||
metamaskController.newUnsignedPersonalMessage(msgParams, function (error) {
|
try {
|
||||||
|
await metamaskController.newUnsignedPersonalMessage(msgParams)
|
||||||
|
assert.fail('should have thrown')
|
||||||
|
} catch (error) {
|
||||||
assert.equal(error.message, 'MetaMask Message Signature: from field is required.')
|
assert.equal(error.message, 'MetaMask Message Signature: from field is required.')
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let msgParams, metamaskPersonalMsgs, personalMessages, msgId
|
let msgParams, metamaskPersonalMsgs, personalMessages, msgId
|
||||||
@ -631,7 +630,10 @@ describe('MetaMaskController', function () {
|
|||||||
'data': data,
|
'data': data,
|
||||||
}
|
}
|
||||||
|
|
||||||
metamaskController.newUnsignedPersonalMessage(msgParams, noop)
|
const promise = metamaskController.newUnsignedPersonalMessage(msgParams)
|
||||||
|
// handle the promise so it doesn't throw an unhandledRejection
|
||||||
|
promise.then(noop).catch(noop)
|
||||||
|
|
||||||
metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs()
|
metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs()
|
||||||
personalMessages = metamaskController.personalMessageManager.messages
|
personalMessages = metamaskController.personalMessageManager.messages
|
||||||
msgId = Object.keys(metamaskPersonalMsgs)[0]
|
msgId = Object.keys(metamaskPersonalMsgs)[0]
|
||||||
@ -670,22 +672,27 @@ describe('MetaMaskController', function () {
|
|||||||
describe('#setupUntrustedCommunication', function () {
|
describe('#setupUntrustedCommunication', function () {
|
||||||
let streamTest
|
let streamTest
|
||||||
|
|
||||||
const phishingUrl = 'decentral.market'
|
const phishingUrl = 'myethereumwalletntw.com'
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
streamTest.end()
|
streamTest.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets up phishing stream for untrusted communication ', async function () {
|
it('sets up phishing stream for untrusted communication ', async () => {
|
||||||
await metamaskController.blacklistController.updatePhishingList()
|
await metamaskController.blacklistController.updatePhishingList()
|
||||||
|
console.log(blacklistJSON.blacklist.includes(phishingUrl))
|
||||||
|
|
||||||
|
const { promise, resolve } = deferredPromise()
|
||||||
|
|
||||||
streamTest = createThoughStream((chunk, enc, cb) => {
|
streamTest = createThoughStream((chunk, enc, cb) => {
|
||||||
assert.equal(chunk.name, 'phishing')
|
if (chunk.name !== 'phishing') return cb()
|
||||||
assert.equal(chunk.data.hostname, phishingUrl)
|
assert.equal(chunk.data.hostname, phishingUrl)
|
||||||
cb()
|
resolve()
|
||||||
})
|
cb()
|
||||||
// console.log(streamTest)
|
})
|
||||||
metamaskController.setupUntrustedCommunication(streamTest, phishingUrl)
|
metamaskController.setupUntrustedCommunication(streamTest, phishingUrl)
|
||||||
|
|
||||||
|
await promise
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -732,3 +739,9 @@ describe('MetaMaskController', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function deferredPromise () {
|
||||||
|
let resolve
|
||||||
|
const promise = new Promise(_resolve => { resolve = _resolve })
|
||||||
|
return { promise, resolve }
|
||||||
|
}
|
||||||
|
@ -32,9 +32,10 @@ describe('# Network Controller', function () {
|
|||||||
describe('#provider', function () {
|
describe('#provider', function () {
|
||||||
it('provider should be updatable without reassignment', function () {
|
it('provider should be updatable without reassignment', function () {
|
||||||
networkController.initializeProvider(networkControllerProviderConfig)
|
networkController.initializeProvider(networkControllerProviderConfig)
|
||||||
const proxy = networkController._proxy
|
const providerProxy = networkController.getProviderAndBlockTracker().provider
|
||||||
proxy.setTarget({ test: true, on: () => {} })
|
assert.equal(providerProxy.test, undefined)
|
||||||
assert.ok(proxy.test)
|
providerProxy.setTarget({ test: true })
|
||||||
|
assert.equal(providerProxy.test, true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('#getNetworkState', function () {
|
describe('#getNetworkState', function () {
|
||||||
|
@ -224,14 +224,15 @@ function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') {
|
|||||||
providerResultStub.result = providerStub
|
providerResultStub.result = providerStub
|
||||||
const provider = {
|
const provider = {
|
||||||
sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
|
sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
|
||||||
_blockTracker: {
|
}
|
||||||
getCurrentBlock: () => '0x11b568',
|
const blockTracker = {
|
||||||
},
|
getCurrentBlock: () => '0x11b568',
|
||||||
|
getLatestBlock: async () => '0x11b568',
|
||||||
}
|
}
|
||||||
return new NonceTracker({
|
return new NonceTracker({
|
||||||
provider,
|
provider,
|
||||||
|
blockTracker,
|
||||||
getPendingTransactions,
|
getPendingTransactions,
|
||||||
getConfirmedTransactions,
|
getConfirmedTransactions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ describe('PendingTransactionTracker', function () {
|
|||||||
let pendingTxTracker, txMeta, txMetaNoHash, providerResultStub,
|
let pendingTxTracker, txMeta, txMetaNoHash, providerResultStub,
|
||||||
provider, txMeta3, txList, knownErrors
|
provider, txMeta3, txList, knownErrors
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
txMeta = {
|
txMeta = {
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -40,7 +41,10 @@ describe('PendingTransactionTracker', function () {
|
|||||||
getPendingTransactions: () => { return [] },
|
getPendingTransactions: () => { return [] },
|
||||||
getCompletedTransactions: () => { return [] },
|
getCompletedTransactions: () => { return [] },
|
||||||
publishTransaction: () => {},
|
publishTransaction: () => {},
|
||||||
|
confirmTransaction: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
pendingTxTracker._getBlock = (blockNumber) => { return {number: blockNumber, transactions: []} }
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('_checkPendingTx state management', function () {
|
describe('_checkPendingTx state management', function () {
|
||||||
@ -92,58 +96,6 @@ describe('PendingTransactionTracker', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#checkForTxInBlock', function () {
|
|
||||||
it('should return if no pending transactions', function () {
|
|
||||||
// throw a type error if it trys to do anything on the block
|
|
||||||
// thus failing the test
|
|
||||||
const block = Proxy.revocable({}, {}).revoke()
|
|
||||||
pendingTxTracker.checkForTxInBlock(block)
|
|
||||||
})
|
|
||||||
it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
|
|
||||||
const block = Proxy.revocable({}, {}).revoke()
|
|
||||||
pendingTxTracker.getPendingTransactions = () => [txMetaNoHash]
|
|
||||||
pendingTxTracker.once('tx:failed', (txId, err) => {
|
|
||||||
assert(txId, txMetaNoHash.id, 'should pass txId')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
pendingTxTracker.checkForTxInBlock(block)
|
|
||||||
})
|
|
||||||
it('should emit \'txConfirmed\' if the tx is in the block', function (done) {
|
|
||||||
const block = { transactions: [txMeta]}
|
|
||||||
pendingTxTracker.getPendingTransactions = () => [txMeta]
|
|
||||||
pendingTxTracker.once('tx:confirmed', (txId) => {
|
|
||||||
assert(txId, txMeta.id, 'should pass txId')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
pendingTxTracker.once('tx:failed', (_, err) => { done(err) })
|
|
||||||
pendingTxTracker.checkForTxInBlock(block)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('#queryPendingTxs', function () {
|
|
||||||
it('should call #_checkPendingTxs if their is no oldBlock', function (done) {
|
|
||||||
let oldBlock
|
|
||||||
const newBlock = { number: '0x01' }
|
|
||||||
pendingTxTracker._checkPendingTxs = done
|
|
||||||
pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
|
|
||||||
})
|
|
||||||
it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) {
|
|
||||||
const oldBlock = { number: '0x01' }
|
|
||||||
const newBlock = { number: '0x03' }
|
|
||||||
pendingTxTracker._checkPendingTxs = done
|
|
||||||
pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
|
|
||||||
})
|
|
||||||
it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) {
|
|
||||||
const oldBlock = { number: '0x1' }
|
|
||||||
const newBlock = { number: '0x2' }
|
|
||||||
pendingTxTracker._checkPendingTxs = () => {
|
|
||||||
const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less')
|
|
||||||
done(err)
|
|
||||||
}
|
|
||||||
pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#_checkPendingTx', function () {
|
describe('#_checkPendingTx', function () {
|
||||||
it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
|
it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
|
||||||
pendingTxTracker.once('tx:failed', (txId, err) => {
|
pendingTxTracker.once('tx:failed', (txId, err) => {
|
||||||
@ -157,16 +109,6 @@ describe('PendingTransactionTracker', function () {
|
|||||||
providerResultStub.eth_getTransactionByHash = null
|
providerResultStub.eth_getTransactionByHash = null
|
||||||
pendingTxTracker._checkPendingTx(txMeta)
|
pendingTxTracker._checkPendingTx(txMeta)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should emit \'txConfirmed\'', function (done) {
|
|
||||||
providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'}
|
|
||||||
pendingTxTracker.once('tx:confirmed', (txId) => {
|
|
||||||
assert(txId, txMeta.id, 'should pass txId')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
pendingTxTracker.once('tx:failed', (_, err) => { done(err) })
|
|
||||||
pendingTxTracker._checkPendingTx(txMeta)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#_checkPendingTxs', function () {
|
describe('#_checkPendingTxs', function () {
|
||||||
@ -180,19 +122,19 @@ describe('PendingTransactionTracker', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should warp all txMeta\'s in #_checkPendingTx', function (done) {
|
it('should warp all txMeta\'s in #updatePendingTxs', function (done) {
|
||||||
pendingTxTracker.getPendingTransactions = () => txList
|
pendingTxTracker.getPendingTransactions = () => txList
|
||||||
pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) }
|
pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) }
|
||||||
Promise.all(txList.map((tx) => tx.processed))
|
Promise.all(txList.map((tx) => tx.processed))
|
||||||
.then((txCompletedList) => done())
|
.then((txCompletedList) => done())
|
||||||
.catch(done)
|
.catch(done)
|
||||||
|
|
||||||
pendingTxTracker._checkPendingTxs()
|
pendingTxTracker.updatePendingTxs()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#resubmitPendingTxs', function () {
|
describe('#resubmitPendingTxs', function () {
|
||||||
const blockStub = { number: '0x0' }
|
const blockNumberStub = '0x0'
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
const txMeta2 = txMeta3 = txMeta
|
const txMeta2 = txMeta3 = txMeta
|
||||||
txList = [txMeta, txMeta2, txMeta3].map((tx) => {
|
txList = [txMeta, txMeta2, txMeta3].map((tx) => {
|
||||||
@ -210,7 +152,7 @@ describe('PendingTransactionTracker', function () {
|
|||||||
Promise.all(txList.map((tx) => tx.processed))
|
Promise.all(txList.map((tx) => tx.processed))
|
||||||
.then((txCompletedList) => done())
|
.then((txCompletedList) => done())
|
||||||
.catch(done)
|
.catch(done)
|
||||||
pendingTxTracker.resubmitPendingTxs(blockStub)
|
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
|
||||||
})
|
})
|
||||||
it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) {
|
it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) {
|
||||||
knownErrors = [
|
knownErrors = [
|
||||||
@ -237,7 +179,7 @@ describe('PendingTransactionTracker', function () {
|
|||||||
.then((txCompletedList) => done())
|
.then((txCompletedList) => done())
|
||||||
.catch(done)
|
.catch(done)
|
||||||
|
|
||||||
pendingTxTracker.resubmitPendingTxs(blockStub)
|
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
|
||||||
})
|
})
|
||||||
it('should emit \'tx:warning\' if it encountered a real error', function (done) {
|
it('should emit \'tx:warning\' if it encountered a real error', function (done) {
|
||||||
pendingTxTracker.once('tx:warning', (txMeta, err) => {
|
pendingTxTracker.once('tx:warning', (txMeta, err) => {
|
||||||
@ -255,7 +197,7 @@ describe('PendingTransactionTracker', function () {
|
|||||||
.then((txCompletedList) => done())
|
.then((txCompletedList) => done())
|
||||||
.catch(done)
|
.catch(done)
|
||||||
|
|
||||||
pendingTxTracker.resubmitPendingTxs(blockStub)
|
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('#_resubmitTx', function () {
|
describe('#_resubmitTx', function () {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
const EventEmitter = require('events')
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const EthTx = require('ethereumjs-tx')
|
const EthTx = require('ethereumjs-tx')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
@ -22,12 +23,14 @@ describe('Transaction Controller', function () {
|
|||||||
}
|
}
|
||||||
provider = createTestProviderTools({ scaffold: providerResultStub }).provider
|
provider = createTestProviderTools({ scaffold: providerResultStub }).provider
|
||||||
fromAccount = getTestAccounts()[0]
|
fromAccount = getTestAccounts()[0]
|
||||||
|
const blockTrackerStub = new EventEmitter()
|
||||||
|
blockTrackerStub.getCurrentBlock = noop
|
||||||
|
blockTrackerStub.getLatestBlock = noop
|
||||||
txController = new TransactionController({
|
txController = new TransactionController({
|
||||||
provider,
|
provider,
|
||||||
networkStore: new ObservableStore(currentNetworkId),
|
networkStore: new ObservableStore(currentNetworkId),
|
||||||
txHistoryLimit: 10,
|
txHistoryLimit: 10,
|
||||||
blockTracker: { getCurrentBlock: noop, on: noop, once: noop },
|
blockTracker: blockTrackerStub,
|
||||||
signTransaction: (ethTx) => new Promise((resolve) => {
|
signTransaction: (ethTx) => new Promise((resolve) => {
|
||||||
ethTx.sign(fromAccount.key)
|
ethTx.sign(fromAccount.key)
|
||||||
resolve()
|
resolve()
|
||||||
@ -49,9 +52,9 @@ describe('Transaction Controller', function () {
|
|||||||
describe('#getUnapprovedTxCount', function () {
|
describe('#getUnapprovedTxCount', function () {
|
||||||
it('should return the number of unapproved txs', function () {
|
it('should return the number of unapproved txs', function () {
|
||||||
txController.txStateManager._saveTxList([
|
txController.txStateManager._saveTxList([
|
||||||
{ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
{ id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
{ id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
])
|
])
|
||||||
const unapprovedTxCount = txController.getUnapprovedTxCount()
|
const unapprovedTxCount = txController.getUnapprovedTxCount()
|
||||||
assert.equal(unapprovedTxCount, 3, 'should be 3')
|
assert.equal(unapprovedTxCount, 3, 'should be 3')
|
||||||
@ -61,9 +64,9 @@ describe('Transaction Controller', function () {
|
|||||||
describe('#getPendingTxCount', function () {
|
describe('#getPendingTxCount', function () {
|
||||||
it('should return the number of pending txs', function () {
|
it('should return the number of pending txs', function () {
|
||||||
txController.txStateManager._saveTxList([
|
txController.txStateManager._saveTxList([
|
||||||
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
{ id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
{ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
|
{ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] },
|
||||||
])
|
])
|
||||||
const pendingTxCount = txController.getPendingTxCount()
|
const pendingTxCount = txController.getPendingTxCount()
|
||||||
assert.equal(pendingTxCount, 3, 'should be 3')
|
assert.equal(pendingTxCount, 3, 'should be 3')
|
||||||
@ -79,15 +82,15 @@ describe('Transaction Controller', function () {
|
|||||||
'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
}
|
}
|
||||||
txController.txStateManager._saveTxList([
|
txController.txStateManager._saveTxList([
|
||||||
{id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
{id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams},
|
{id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -201,24 +204,22 @@ describe('Transaction Controller', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('#addTxGasDefaults', function () {
|
describe('#addTxGasDefaults', function () {
|
||||||
it('should add the tx defaults if their are none', function (done) {
|
it('should add the tx defaults if their are none', async () => {
|
||||||
const txMeta = {
|
const txMeta = {
|
||||||
'txParams': {
|
txParams: {
|
||||||
'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
to: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
|
||||||
},
|
},
|
||||||
|
history: [],
|
||||||
}
|
}
|
||||||
providerResultStub.eth_gasPrice = '4a817c800'
|
providerResultStub.eth_gasPrice = '4a817c800'
|
||||||
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }
|
providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }
|
||||||
providerResultStub.eth_estimateGas = '5209'
|
providerResultStub.eth_estimateGas = '5209'
|
||||||
txController.addTxGasDefaults(txMeta)
|
|
||||||
.then((txMetaWithDefaults) => {
|
const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta)
|
||||||
assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value')
|
assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value')
|
||||||
assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price')
|
assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price')
|
||||||
assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field')
|
assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field')
|
||||||
done()
|
|
||||||
})
|
|
||||||
.catch(done)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -381,8 +382,9 @@ describe('Transaction Controller', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should publish a tx, updates the rawTx when provided a one', async function () {
|
it('should publish a tx, updates the rawTx when provided a one', async function () {
|
||||||
|
const rawTx = '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c'
|
||||||
txController.txStateManager.addTx(txMeta)
|
txController.txStateManager.addTx(txMeta)
|
||||||
await txController.publishTransaction(txMeta.id)
|
await txController.publishTransaction(txMeta.id, rawTx)
|
||||||
const publishedTx = txController.txStateManager.getTx(1)
|
const publishedTx = txController.txStateManager.getTx(1)
|
||||||
assert.equal(publishedTx.hash, hash)
|
assert.equal(publishedTx.hash, hash)
|
||||||
assert.equal(publishedTx.status, 'submitted')
|
assert.equal(publishedTx.status, 'submitted')
|
||||||
@ -398,7 +400,7 @@ describe('Transaction Controller', function () {
|
|||||||
data: '0x0',
|
data: '0x0',
|
||||||
}
|
}
|
||||||
txController.txStateManager._saveTxList([
|
txController.txStateManager._saveTxList([
|
||||||
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams },
|
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
])
|
])
|
||||||
txController.retryTransaction(1)
|
txController.retryTransaction(1)
|
||||||
.then((txMeta) => {
|
.then((txMeta) => {
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
// polyfill fetch
|
|
||||||
global.fetch = global.fetch || require('isomorphic-fetch')
|
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const configManagerGen = require('../lib/mock-config-manager')
|
const configManagerGen = require('../lib/mock-config-manager')
|
||||||
|
|
||||||
|
21
test/unit/localhostState.js
Normal file
21
test/unit/localhostState.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} FirstTimeState
|
||||||
|
* @property {Object} config Initial configuration parameters
|
||||||
|
* @property {Object} NetworkController Network controller state
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {FirstTimeState}
|
||||||
|
*/
|
||||||
|
const initialState = {
|
||||||
|
config: {},
|
||||||
|
NetworkController: {
|
||||||
|
provider: {
|
||||||
|
type: 'rpc',
|
||||||
|
rpcTarget: 'http://localhost:8545',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = initialState
|
Loading…
Reference in New Issue
Block a user