network - create provider and block-tracker via json-rpc-engine

feature/default_network_editable
kumavis 7 years ago
parent 708422432c
commit 088d7930e0
  1. 34
      app/scripts/controllers/network/createInfuraClient.js
  2. 35
      app/scripts/controllers/network/createJsonRpcClient.js
  3. 33
      app/scripts/controllers/network/createLocalhostClient.js
  4. 43
      app/scripts/controllers/network/createMetamaskMiddleware.js
  5. 116
      app/scripts/controllers/network/network.js
  6. 1
      app/scripts/controllers/transactions/index.js
  7. 18
      app/scripts/controllers/transactions/nonce-tracker.js
  8. 7
      app/scripts/metamask-controller.js
  9. 4
      package.json
  10. 8
      test/unit/nonce-tracker-test.js

@ -0,0 +1,34 @@
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref')
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-cache')
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([
createBlockRefMiddleware({ blockTracker }),
createBlockCacheMiddleware({ blockTracker }),
createInflightMiddleware(),
createBlockTrackerInspectorMiddleware({ blockTracker }),
infuraMiddleware,
])
return { networkMiddleware, blockTracker }
}
// inspect if response contains a block ref higher than our latest block
const futureBlockRefRequests = ['eth_getTransactionByHash', 'eth_getTransactionReceipt']
function createBlockTrackerInspectorMiddleware ({ blockTracker }) {
return createAsyncMiddleware(async (req, res, next) => {
if (!futureBlockRefRequests.includes(req.method)) return next()
await next()
const blockNumber = Number.parseInt(res.result.blockNumber, 16)
const currentBlockNumber = Number.parseInt(blockTracker.getCurrentBlock(), 16)
if (blockNumber > currentBlockNumber) await blockTracker.checkForLatestBlock()
})
}

@ -0,0 +1,35 @@
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref')
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-cache')
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 }
}
// inspect if response contains a block ref higher than our latest block
const futureBlockRefRequests = ['eth_getTransactionByHash', 'eth_getTransactionReceipt']
function createBlockTrackerInspectorMiddleware ({ blockTracker }) {
return createAsyncMiddleware(async (req, res, next) => {
if (!futureBlockRefRequests.includes(req.method)) return next()
await next()
const blockNumber = Number.parseInt(res.result.blockNumber, 16)
const currentBlockNumber = Number.parseInt(blockTracker.getCurrentBlock(), 16)
if (blockNumber > currentBlockNumber) await blockTracker.checkForLatestBlock()
})
}

@ -0,0 +1,33 @@
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref')
const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-cache')
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 }
}
// inspect if response contains a block ref higher than our latest block
const futureBlockRefRequests = ['eth_getTransactionByHash', 'eth_getTransactionReceipt']
function createBlockTrackerInspectorMiddleware ({ blockTracker }) {
return createAsyncMiddleware(async (req, res, next) => {
if (!futureBlockRefRequests.includes(req.method)) return next()
await next()
const blockNumber = Number.parseInt(res.result.blockNumber, 16)
const currentBlockNumber = Number.parseInt(blockTracker.getCurrentBlock(), 16)
if (blockNumber > currentBlockNumber) await blockTracker.checkForLatestBlock()
})
}

@ -0,0 +1,43 @@
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
const createScaffoldMiddleware = require('json-rpc-engine/src/scaffold')
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,14 +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 extend = require('xtend')
const EthQuery = require('eth-query') const EthQuery = require('eth-query')
const createEventEmitterProxy = require('../../lib/events-proxy.js')
const log = require('loglevel') const log = require('loglevel')
const createMetamaskMiddleware = require('./createMetamaskMiddleware')
const createInfuraClient = require('./createInfuraClient')
const createJsonRpcClient = require('./createJsonRpcClient')
const createLocalhostClient = require('./createLocalhostClient')
// const createEventEmitterProxy = require('../../lib/events-proxy.js')
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
const { const {
ROPSTEN, ROPSTEN,
RINKEBY, RINKEBY,
@ -38,21 +41,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._blockTracker
return { provider, blockTracker }
} }
verifyNetwork () { verifyNetwork () {
@ -74,10 +83,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)
@ -123,7 +133,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 })
@ -133,47 +143,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 }) {
const providerParams = extend(this._baseProviderParams, { log.info('NetworkController - configureStandardProvider', rpcUrl)
rpcUrl, const networkClient = createJsonRpcClient({ rpcUrl })
engineParams: { this._setNetworkClient(networkClient)
pollingInterval: 8000, }
},
}) _setNetworkClient ({ networkMiddleware, blockTracker }) {
const provider = createMetamaskProvider(providerParams) const metamaskMiddleware = createMetamaskMiddleware(this._baseProviderParams)
this._setProvider(provider) const engine = new JsonRpcEngine()
} engine.push(metamaskMiddleware)
engine.push(networkMiddleware)
_setProvider (provider) { const provider = providerFromEngine(engine)
// collect old block tracker events this._setProviderAndBlockTracker({ provider, blockTracker })
const oldProvider = this._provider }
let blockTrackerHandlers
if (oldProvider) { _setProviderAndBlockTracker ({ provider, blockTracker }) {
// capture old block handlers // update or intialize proxies
blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers if (this._providerProxy) {
// tear down this._providerProxy.setTarget(provider)
oldProvider.removeAllListeners() } else {
oldProvider.stop() this._providerProxy = createSwappableProxy(provider)
}
if (this._blockTrackerProxy) {
this._blockTrackerProxy.setTarget(blockTracker)
} else {
this._blockTrackerProxy = createEventEmitterProxy(blockTracker)
} }
// override block tracler // set new provider and blockTracker
provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers)
// set as new provider
this._provider = provider this._provider = provider
this._proxy.setTarget(provider) this._blockTracker = blockTracker
} }
_logBlock (block) { _logBlock (block) {

@ -63,6 +63,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),
}) })

@ -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
@ -75,11 +76,10 @@ class NonceTracker {
} }
async _getCurrentBlock () { async _getCurrentBlock () {
const blockTracker = this._getBlockTracker() const currentBlock = this.blockTracker.getCurrentBlock()
const currentBlock = blockTracker.getCurrentBlock()
if (currentBlock) return currentBlock if (currentBlock) return currentBlock
return await new Promise((reject, resolve) => { return await new Promise((reject, resolve) => {
blockTracker.once('latest', resolve) this.blockTracker.once('latest', resolve)
}) })
} }
@ -171,16 +171,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

@ -103,8 +103,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({
@ -1033,7 +1034,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 }))

@ -91,6 +91,7 @@
"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": "github:metamask/eth-block-tracker#acbcfda348c309ece0fb96fad78d4861d08f5a91",
"eth-contract-metadata": "^1.1.5", "eth-contract-metadata": "^1.1.5",
"eth-hd-keyring": "^1.2.1", "eth-hd-keyring": "^1.2.1",
"eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-filters": "^1.2.6",
@ -129,7 +130,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.0",
"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",
@ -185,6 +186,7 @@
"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.0",
"textarea-caret": "^3.0.1", "textarea-caret": "^3.0.1",
"through2": "^2.0.3", "through2": "^2.0.3",
"valid-url": "^1.0.9", "valid-url": "^1.0.9",

@ -226,14 +226,14 @@ 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',
} }
return new NonceTracker({ return new NonceTracker({
provider, provider,
blockTracker,
getPendingTransactions, getPendingTransactions,
getConfirmedTransactions, getConfirmedTransactions,
}) })
} }

Loading…
Cancel
Save