Use `chainId` for incoming transactions controller (#9583)

The incoming transactions controller now uses the `chainId` for the
current network instead of the `networkId`. This ensures that custom
RPC endpoints for the built-in supported networks do correctly receive
incoming transactions.

As part of this change, the incoming transactions controller will also
cease keeping track of the "last block fetched" for networks that are
not supported. This piece of state never really represented the last
block fetched, as _no_ blocks were fetched for any such networks. It
been removed.
feature/default_network_editable
Mark Stacey 4 years ago committed by GitHub
parent c6064072c7
commit 55bff07bbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 85
      app/scripts/controllers/incoming-transactions.js
  2. 18
      app/scripts/controllers/network/enums.js
  3. 298
      test/unit/app/controllers/incoming-transactions-test.js

@ -6,12 +6,18 @@ import { bnToHex } from '../lib/util'
import fetchWithTimeout from '../lib/fetch-with-timeout' import fetchWithTimeout from '../lib/fetch-with-timeout'
import { import {
ROPSTEN, CHAIN_ID_TO_NETWORK_ID_MAP,
RINKEBY, CHAIN_ID_TO_TYPE_MAP,
KOVAN,
GOERLI, GOERLI,
GOERLI_CHAIN_ID,
KOVAN,
KOVAN_CHAIN_ID,
MAINNET, MAINNET,
NETWORK_TYPE_TO_ID_MAP, MAINNET_CHAIN_ID,
RINKEBY,
RINKEBY_CHAIN_ID,
ROPSTEN,
ROPSTEN_CHAIN_ID,
} from './network/enums' } from './network/enums'
const fetch = fetchWithTimeout({ const fetch = fetchWithTimeout({
@ -25,6 +31,14 @@ const fetch = fetchWithTimeout({
* Note that only the built-in Infura networks are supported (i.e. anything in `INFURA_PROVIDER_TYPES`). We will not * Note that only the built-in Infura networks are supported (i.e. anything in `INFURA_PROVIDER_TYPES`). We will not
* attempt to retrieve incoming transactions on any custom RPC endpoints. * attempt to retrieve incoming transactions on any custom RPC endpoints.
*/ */
const etherscanSupportedNetworks = [
GOERLI_CHAIN_ID,
KOVAN_CHAIN_ID,
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
]
export default class IncomingTransactionsController { export default class IncomingTransactionsController {
constructor (opts = {}) { constructor (opts = {}) {
@ -36,7 +50,6 @@ export default class IncomingTransactionsController {
this.blockTracker = blockTracker this.blockTracker = blockTracker
this.networkController = networkController this.networkController = networkController
this.preferencesController = preferencesController this.preferencesController = preferencesController
this.getCurrentNetwork = () => networkController.getProviderConfig().type
this._onLatestBlock = async (newBlockNumberHex) => { this._onLatestBlock = async (newBlockNumberHex) => {
const selectedAddress = this.preferencesController.getSelectedAddress() const selectedAddress = this.preferencesController.getSelectedAddress()
@ -50,11 +63,11 @@ export default class IncomingTransactionsController {
const initState = { const initState = {
incomingTransactions: {}, incomingTransactions: {},
incomingTxLastFetchedBlocksByNetwork: { incomingTxLastFetchedBlocksByNetwork: {
[ROPSTEN]: null,
[RINKEBY]: null,
[KOVAN]: null,
[GOERLI]: null, [GOERLI]: null,
[KOVAN]: null,
[MAINNET]: null, [MAINNET]: null,
[RINKEBY]: null,
[ROPSTEN]: null,
}, ...opts.initState, }, ...opts.initState,
} }
this.store = new ObservableStore(initState) this.store = new ObservableStore(initState)
@ -88,11 +101,10 @@ export default class IncomingTransactionsController {
}) })
})) }))
this.networkController.on('networkDidChange', async (newType) => { this.networkController.on('networkDidChange', async () => {
const address = this.preferencesController.getSelectedAddress() const address = this.preferencesController.getSelectedAddress()
await this._update({ await this._update({
address, address,
networkType: newType,
}) })
}) })
} }
@ -113,29 +125,32 @@ export default class IncomingTransactionsController {
this.blockTracker.removeListener('latest', this._onLatestBlock) this.blockTracker.removeListener('latest', this._onLatestBlock)
} }
async _update ({ address, newBlockNumberDec, networkType } = {}) { async _update ({ address, newBlockNumberDec } = {}) {
const chainId = this.networkController.getCurrentChainId()
if (!etherscanSupportedNetworks.includes(chainId)) {
return
}
try { try {
const dataForUpdate = await this._getDataForUpdate({ address, newBlockNumberDec, networkType }) const dataForUpdate = await this._getDataForUpdate({ address, chainId, newBlockNumberDec })
await this._updateStateWithNewTxData(dataForUpdate) this._updateStateWithNewTxData(dataForUpdate)
} catch (err) { } catch (err) {
log.error(err) log.error(err)
} }
} }
async _getDataForUpdate ({ address, newBlockNumberDec, networkType } = {}) { async _getDataForUpdate ({ address, chainId, newBlockNumberDec } = {}) {
const { const {
incomingTransactions: currentIncomingTxs, incomingTransactions: currentIncomingTxs,
incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork, incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork,
} = this.store.getState() } = this.store.getState()
const network = networkType || this.getCurrentNetwork() const lastFetchBlockByCurrentNetwork = currentBlocksByNetwork[CHAIN_ID_TO_TYPE_MAP[chainId]]
const lastFetchBlockByCurrentNetwork = currentBlocksByNetwork[network]
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec
if (blockToFetchFrom === undefined) { if (blockToFetchFrom === undefined) {
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16) blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16)
} }
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(address, blockToFetchFrom, network) const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(address, blockToFetchFrom, chainId)
return { return {
latestIncomingTxBlockNumber, latestIncomingTxBlockNumber,
@ -143,17 +158,17 @@ export default class IncomingTransactionsController {
currentIncomingTxs, currentIncomingTxs,
currentBlocksByNetwork, currentBlocksByNetwork,
fetchedBlockNumber: blockToFetchFrom, fetchedBlockNumber: blockToFetchFrom,
network, chainId,
} }
} }
async _updateStateWithNewTxData ({ _updateStateWithNewTxData ({
latestIncomingTxBlockNumber, latestIncomingTxBlockNumber,
newTxs, newTxs,
currentIncomingTxs, currentIncomingTxs,
currentBlocksByNetwork, currentBlocksByNetwork,
fetchedBlockNumber, fetchedBlockNumber,
network, chainId,
}) { }) {
const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber
? parseInt(latestIncomingTxBlockNumber, 10) + 1 ? parseInt(latestIncomingTxBlockNumber, 10) + 1
@ -168,28 +183,22 @@ export default class IncomingTransactionsController {
this.store.updateState({ this.store.updateState({
incomingTxLastFetchedBlocksByNetwork: { incomingTxLastFetchedBlocksByNetwork: {
...currentBlocksByNetwork, ...currentBlocksByNetwork,
[network]: newLatestBlockHashByNetwork, [CHAIN_ID_TO_TYPE_MAP[chainId]]: newLatestBlockHashByNetwork,
}, },
incomingTransactions: newIncomingTransactions, incomingTransactions: newIncomingTransactions,
}) })
} }
async _fetchAll (address, fromBlock, networkType) { async _fetchAll (address, fromBlock, chainId) {
const fetchedTxResponse = await this._fetchTxs(address, fromBlock, networkType) const fetchedTxResponse = await this._fetchTxs(address, fromBlock, chainId)
return this._processTxFetchResponse(fetchedTxResponse) return this._processTxFetchResponse(fetchedTxResponse)
} }
async _fetchTxs (address, fromBlock, networkType) { async _fetchTxs (address, fromBlock, chainId) {
let etherscanSubdomain = 'api' const etherscanSubdomain = chainId === MAINNET_CHAIN_ID
const currentNetworkID = NETWORK_TYPE_TO_ID_MAP[networkType]?.networkId ? 'api'
: `api-${CHAIN_ID_TO_TYPE_MAP[chainId]}`
if (!currentNetworkID) {
return {}
}
if (networkType !== MAINNET) {
etherscanSubdomain = `api-${networkType}`
}
const apiUrl = `https://${etherscanSubdomain}.etherscan.io` const apiUrl = `https://${etherscanSubdomain}.etherscan.io`
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1` let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`
@ -202,17 +211,17 @@ export default class IncomingTransactionsController {
return { return {
...parsedResponse, ...parsedResponse,
address, address,
currentNetworkID, chainId,
} }
} }
_processTxFetchResponse ({ status, result = [], address, currentNetworkID }) { _processTxFetchResponse ({ status, result = [], address, chainId }) {
if (status === '1' && Array.isArray(result) && result.length > 0) { if (status === '1' && Array.isArray(result) && result.length > 0) {
const remoteTxList = {} const remoteTxList = {}
const remoteTxs = [] const remoteTxs = []
result.forEach((tx) => { result.forEach((tx) => {
if (!remoteTxList[tx.hash]) { if (!remoteTxList[tx.hash]) {
remoteTxs.push(this._normalizeTxFromEtherscan(tx, currentNetworkID)) remoteTxs.push(this._normalizeTxFromEtherscan(tx, chainId))
remoteTxList[tx.hash] = 1 remoteTxList[tx.hash] = 1
} }
}) })
@ -241,13 +250,13 @@ export default class IncomingTransactionsController {
} }
} }
_normalizeTxFromEtherscan (txMeta, currentNetworkID) { _normalizeTxFromEtherscan (txMeta, chainId) {
const time = parseInt(txMeta.timeStamp, 10) * 1000 const time = parseInt(txMeta.timeStamp, 10) * 1000
const status = txMeta.isError === '0' ? 'confirmed' : 'failed' const status = txMeta.isError === '0' ? 'confirmed' : 'failed'
return { return {
blockNumber: txMeta.blockNumber, blockNumber: txMeta.blockNumber,
id: createId(), id: createId(),
metamaskNetworkId: currentNetworkID, metamaskNetworkId: CHAIN_ID_TO_NETWORK_ID_MAP[chainId],
status, status,
time, time,
txParams: { txParams: {

@ -57,3 +57,21 @@ export const NETWORK_TO_NAME_MAP = {
[GOERLI_CHAIN_ID]: GOERLI_DISPLAY_NAME, [GOERLI_CHAIN_ID]: GOERLI_DISPLAY_NAME,
[MAINNET_CHAIN_ID]: MAINNET_DISPLAY_NAME, [MAINNET_CHAIN_ID]: MAINNET_DISPLAY_NAME,
} }
export const CHAIN_ID_TO_TYPE_MAP = Object.entries(NETWORK_TYPE_TO_ID_MAP)
.reduce(
(chainIdToTypeMap, [networkType, { chainId }]) => {
chainIdToTypeMap[chainId] = networkType
return chainIdToTypeMap
},
{},
)
export const CHAIN_ID_TO_NETWORK_ID_MAP = Object.values(NETWORK_TYPE_TO_ID_MAP)
.reduce(
(chainIdToNetworkIdMap, { chainId, networkId }) => {
chainIdToNetworkIdMap[chainId] = networkId
return chainIdToNetworkIdMap
},
{},
)

@ -9,15 +9,18 @@ import {
GOERLI, GOERLI,
KOVAN, KOVAN,
MAINNET, MAINNET,
MAINNET_CHAIN_ID,
RINKEBY, RINKEBY,
ROPSTEN, ROPSTEN,
ROPSTEN_CHAIN_ID,
ROPSTEN_NETWORK_ID,
} from '../../../../app/scripts/controllers/network/enums' } from '../../../../app/scripts/controllers/network/enums'
const IncomingTransactionsController = proxyquire('../../../../app/scripts/controllers/incoming-transactions', { const IncomingTransactionsController = proxyquire('../../../../app/scripts/controllers/incoming-transactions', {
'../lib/random-id': { default: () => 54321 }, '../lib/random-id': { default: () => 54321 },
}).default }).default
const FAKE_NETWORK = 'FAKE_NETWORK' const FAKE_CHAIN_ID = '0x1338'
const MOCK_SELECTED_ADDRESS = '0x0101' const MOCK_SELECTED_ADDRESS = '0x0101'
function getEmptyInitState () { function getEmptyInitState () {
@ -48,27 +51,9 @@ function getNonEmptyInitState () {
} }
} }
function getNonEmptyInitStateWithFakeNetworkState () { function getMockNetworkController (chainId = FAKE_CHAIN_ID) {
return { return {
incomingTransactions: { getCurrentChainId: () => chainId,
'0x123456': { id: 777 },
},
incomingTxLastFetchedBlocksByNetwork: {
[ROPSTEN]: 1,
[RINKEBY]: 2,
[KOVAN]: 3,
[GOERLI]: 5,
[MAINNET]: 4,
[FAKE_NETWORK]: 1111,
},
}
}
function getMockNetworkController (networkType = FAKE_NETWORK) {
return {
getProviderConfig: () => {
return { type: networkType }
},
on: sinon.spy(), on: sinon.spy(),
} }
} }
@ -164,7 +149,6 @@ describe('IncomingTransactionsController', function () {
assert.equal(incomingTransactionsController._update.callCount, 1) assert.equal(incomingTransactionsController._update.callCount, 1)
assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], { assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], {
address: '0x0101', address: '0x0101',
networkType: 'testNetworkType',
}) })
incomingTransactionsController._update.resetHistory() incomingTransactionsController._update.resetHistory()
@ -200,7 +184,7 @@ describe('IncomingTransactionsController', function () {
it('should update upon latest block when started and on supported network', async function () { it('should update upon latest block when started and on supported network', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(ROPSTEN), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -258,34 +242,44 @@ describe('IncomingTransactionsController', function () {
) )
}) })
it('should update last block fetched when started and not on supported network', async function () { it('should not update upon latest block when started and not on supported network', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
// reply with a valid request for any supported network, so that this test has every opportunity to fail
for (const network of [GOERLI, KOVAN, MAINNET, RINKEBY, ROPSTEN]) {
nock(`https://api${network === MAINNET ? '' : `-${network.toLowerCase()}`}.etherscan.io`)
.get(/api.+/u)
.reply(
200,
JSON.stringify({
status: '1',
result: [getFakeEtherscanTransaction()],
}),
)
}
const updateStateStub = sinon.stub(incomingTransactionsController.store, 'updateState') const updateStateStub = sinon.stub(incomingTransactionsController.store, 'updateState')
const updateStateCalled = waitUntilCalled(updateStateStub, incomingTransactionsController.store) const updateStateCalled = waitUntilCalled(updateStateStub, incomingTransactionsController.store)
const putStateStub = sinon.stub(incomingTransactionsController.store, 'putState')
const putStateCalled = waitUntilCalled(putStateStub, incomingTransactionsController.store)
incomingTransactionsController.start() incomingTransactionsController.start()
await updateStateCalled try {
await Promise.race([
const state = incomingTransactionsController.store.getState() updateStateCalled,
assert.deepStrictEqual( putStateCalled,
state, new Promise((_, reject) => {
{ setTimeout(() => reject(new Error('TIMEOUT')), 1000)
incomingTransactions: { }),
...getNonEmptyInitState().incomingTransactions, ])
}, assert.fail('Update state should not have been called')
incomingTxLastFetchedBlocksByNetwork: { } catch (error) {
...getNonEmptyInitState().incomingTxLastFetchedBlocksByNetwork, assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown')
[FAKE_NETWORK]: 11, }
},
},
'Should update last block fetched',
)
}) })
it('should not update upon latest block when started and incoming transactions disabled', async function () { it('should not update upon latest block when started and incoming transactions disabled', async function () {
@ -331,7 +325,7 @@ describe('IncomingTransactionsController', function () {
it('should not update upon latest block when not started', async function () { it('should not update upon latest block when not started', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(ROPSTEN), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -369,7 +363,7 @@ describe('IncomingTransactionsController', function () {
it('should not update upon latest block when stopped', async function () { it('should not update upon latest block when stopped', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(ROPSTEN), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -409,7 +403,7 @@ describe('IncomingTransactionsController', function () {
it('should update when the selected address changes and on supported network', async function () { it('should update when the selected address changes and on supported network', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(ROPSTEN), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -473,7 +467,7 @@ describe('IncomingTransactionsController', function () {
) )
}) })
it('should update last block fetched when selected address changes and not on supported network', async function () { it('should not update when the selected address changes and not on supported network', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: { ...getMockBlockTracker() }, blockTracker: { ...getMockBlockTracker() },
networkController: getMockNetworkController(), networkController: getMockNetworkController(),
@ -495,6 +489,8 @@ describe('IncomingTransactionsController', function () {
} }
const updateStateStub = sinon.stub(incomingTransactionsController.store, 'updateState') const updateStateStub = sinon.stub(incomingTransactionsController.store, 'updateState')
const updateStateCalled = waitUntilCalled(updateStateStub, incomingTransactionsController.store) const updateStateCalled = waitUntilCalled(updateStateStub, incomingTransactionsController.store)
const putStateStub = sinon.stub(incomingTransactionsController.store, 'putState')
const putStateCalled = waitUntilCalled(putStateStub, incomingTransactionsController.store)
const subscription = incomingTransactionsController.preferencesController.store.subscribe.getCall(1).args[0] const subscription = incomingTransactionsController.preferencesController.store.subscribe.getCall(1).args[0]
// The incoming transactions controller will always skip the first event // The incoming transactions controller will always skip the first event
@ -503,28 +499,24 @@ describe('IncomingTransactionsController', function () {
await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS }) await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS })
await subscription({ selectedAddress: NEW_MOCK_SELECTED_ADDRESS }) await subscription({ selectedAddress: NEW_MOCK_SELECTED_ADDRESS })
await updateStateCalled try {
await Promise.race([
const state = incomingTransactionsController.store.getState() updateStateCalled,
assert.deepStrictEqual( putStateCalled,
state, new Promise((_, reject) => {
{ setTimeout(() => reject(new Error('TIMEOUT')), 1000)
incomingTransactions: { }),
...getNonEmptyInitState().incomingTransactions, ])
}, assert.fail('Update state should not have been called')
incomingTxLastFetchedBlocksByNetwork: { } catch (error) {
...getNonEmptyInitState().incomingTxLastFetchedBlocksByNetwork, assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown')
[FAKE_NETWORK]: 11, }
},
},
'Should update last block fetched',
)
}) })
it('should update when switching to a supported network', async function () { it('should update when switching to a supported network', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(ROPSTEN), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -542,7 +534,7 @@ describe('IncomingTransactionsController', function () {
const updateStateCalled = waitUntilCalled(updateStateStub, incomingTransactionsController.store) const updateStateCalled = waitUntilCalled(updateStateStub, incomingTransactionsController.store)
const subscription = incomingTransactionsController.networkController.on.getCall(0).args[1] const subscription = incomingTransactionsController.networkController.on.getCall(0).args[1]
incomingTransactionsController.networkController = getMockNetworkController(ROPSTEN) incomingTransactionsController.networkController = getMockNetworkController(ROPSTEN_CHAIN_ID)
await subscription(ROPSTEN) await subscription(ROPSTEN)
await updateStateCalled await updateStateCalled
@ -584,10 +576,11 @@ describe('IncomingTransactionsController', function () {
) )
}) })
it('should update last block fetched when switching to an unsupported network', async function () { it('should not update when switching to an unsupported network', async function () {
const networkController = getMockNetworkController(ROPSTEN_CHAIN_ID)
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController,
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -605,26 +598,26 @@ describe('IncomingTransactionsController', function () {
} }
const updateStateStub = sinon.stub(incomingTransactionsController.store, 'updateState') const updateStateStub = sinon.stub(incomingTransactionsController.store, 'updateState')
const updateStateCalled = waitUntilCalled(updateStateStub, incomingTransactionsController.store) const updateStateCalled = waitUntilCalled(updateStateStub, incomingTransactionsController.store)
const putStateStub = sinon.stub(incomingTransactionsController.store, 'putState')
const putStateCalled = waitUntilCalled(putStateStub, incomingTransactionsController.store)
const subscription = incomingTransactionsController.networkController.on.getCall(0).args[1] const subscription = incomingTransactionsController.networkController.on.getCall(0).args[1]
await subscription('SECOND_FAKE_NETWORK')
await updateStateCalled networkController.getCurrentChainId = () => FAKE_CHAIN_ID
await subscription()
const state = incomingTransactionsController.store.getState() try {
assert.deepStrictEqual( await Promise.race([
state, updateStateCalled,
{ putStateCalled,
incomingTransactions: { new Promise((_, reject) => {
...getNonEmptyInitState().incomingTransactions, setTimeout(() => reject(new Error('TIMEOUT')), 1000)
}, }),
incomingTxLastFetchedBlocksByNetwork: { ])
...getNonEmptyInitState().incomingTxLastFetchedBlocksByNetwork, assert.fail('Update state should not have been called')
SECOND_FAKE_NETWORK: 11, } catch (error) {
}, assert(error.message === 'TIMEOUT', 'TIMEOUT error should be thrown')
}, }
'Should update last block fetched',
)
}) })
}) })
@ -632,66 +625,53 @@ describe('IncomingTransactionsController', function () {
it('should call fetchAll with the correct params when passed a new block number and the current network has no stored block', async function () { it('should call fetchAll with the correct params when passed a new block number and the current network has no stored block', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getEmptyInitState(),
}) })
incomingTransactionsController._fetchAll = sinon.stub().returns({}) incomingTransactionsController._fetchAll = sinon.stub().returns({})
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 }) await incomingTransactionsController._getDataForUpdate({
address: 'fakeAddress',
assert(incomingTransactionsController._fetchAll.calledOnce) chainId: ROPSTEN_CHAIN_ID,
newBlockNumberDec: 999,
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
'fakeAddress', 999, 'FAKE_NETWORK',
])
})
it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async function () {
const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(),
preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitStateWithFakeNetworkState(),
}) })
incomingTransactionsController._fetchAll = sinon.stub().returns({})
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 })
assert(incomingTransactionsController._fetchAll.calledOnce) assert(incomingTransactionsController._fetchAll.calledOnce)
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [ assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
'fakeAddress', 1111, 'FAKE_NETWORK', 'fakeAddress', 999, ROPSTEN_CHAIN_ID,
]) ])
}) })
it('should call fetchAll with the correct params when passed a new network type but no block info exists', async function () { it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitStateWithFakeNetworkState(), initState: getNonEmptyInitState(),
}) })
incomingTransactionsController._fetchAll = sinon.stub().returns({}) incomingTransactionsController._fetchAll = sinon.stub().returns({})
await incomingTransactionsController._getDataForUpdate({ await incomingTransactionsController._getDataForUpdate({
address: 'fakeAddress', address: 'fakeAddress',
networkType: 'NEW_FAKE_NETWORK', chainId: ROPSTEN_CHAIN_ID,
newBlockNumberDec: 999,
}) })
assert(incomingTransactionsController._fetchAll.calledOnce) assert(incomingTransactionsController._fetchAll.calledOnce)
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [ assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
'fakeAddress', 10, 'NEW_FAKE_NETWORK', 'fakeAddress', 4, ROPSTEN_CHAIN_ID,
]) ])
}) })
it('should return the expected data', async function () { it('should return the expected data', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitStateWithFakeNetworkState(), initState: getNonEmptyInitState(),
}) })
incomingTransactionsController._fetchAll = sinon.stub().returns({ incomingTransactionsController._fetchAll = sinon.stub().returns({
latestIncomingTxBlockNumber: 444, latestIncomingTxBlockNumber: 444,
@ -700,7 +680,7 @@ describe('IncomingTransactionsController', function () {
const result = await incomingTransactionsController._getDataForUpdate({ const result = await incomingTransactionsController._getDataForUpdate({
address: 'fakeAddress', address: 'fakeAddress',
networkType: 'FAKE_NETWORK', chainId: ROPSTEN_CHAIN_ID,
}) })
assert.deepEqual(result, { assert.deepEqual(result, {
@ -710,15 +690,14 @@ describe('IncomingTransactionsController', function () {
'0x123456': { id: 777 }, '0x123456': { id: 777 },
}, },
currentBlocksByNetwork: { currentBlocksByNetwork: {
[ROPSTEN]: 1, [GOERLI]: 1,
[RINKEBY]: 2, [KOVAN]: 2,
[KOVAN]: 3, [MAINNET]: 3,
[GOERLI]: 5, [RINKEBY]: 5,
[MAINNET]: 4, [ROPSTEN]: 4,
FAKE_NETWORK: 1111,
}, },
fetchedBlockNumber: 1111, fetchedBlockNumber: 4,
network: 'FAKE_NETWORK', chainId: ROPSTEN_CHAIN_ID,
}) })
}) })
}) })
@ -730,15 +709,14 @@ describe('IncomingTransactionsController', function () {
'0x123456': { id: 777, hash: '0x123456' }, '0x123456': { id: 777, hash: '0x123456' },
}, },
currentBlocksByNetwork: { currentBlocksByNetwork: {
[ROPSTEN]: 1, [GOERLI]: 1,
[RINKEBY]: 2, [KOVAN]: 2,
[KOVAN]: 3, [MAINNET]: 3,
[GOERLI]: 5, [RINKEBY]: 5,
[MAINNET]: 4, [ROPSTEN]: 4,
FAKE_NETWORK: 1111,
}, },
fetchedBlockNumber: 1111, fetchedBlockNumber: 1111,
network: 'FAKE_NETWORK', chainId: ROPSTEN_CHAIN_ID,
} }
const MOCK_INPUT_WITH_LASTEST = { const MOCK_INPUT_WITH_LASTEST = {
@ -749,7 +727,7 @@ describe('IncomingTransactionsController', function () {
it('should update state with correct blockhash and transactions when passed a truthy latestIncomingTxBlockNumber', async function () { it('should update state with correct blockhash and transactions when passed a truthy latestIncomingTxBlockNumber', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -762,7 +740,7 @@ describe('IncomingTransactionsController', function () {
assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], { assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], {
incomingTxLastFetchedBlocksByNetwork: { incomingTxLastFetchedBlocksByNetwork: {
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork, ...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork,
'FAKE_NETWORK': 445, [ROPSTEN]: 445,
}, },
incomingTransactions: { incomingTransactions: {
'0x123456': { id: 777, hash: '0x123456' }, '0x123456': { id: 777, hash: '0x123456' },
@ -774,7 +752,7 @@ describe('IncomingTransactionsController', function () {
it('should update state with correct blockhash and transactions when passed a falsy latestIncomingTxBlockNumber', async function () { it('should update state with correct blockhash and transactions when passed a falsy latestIncomingTxBlockNumber', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -787,7 +765,7 @@ describe('IncomingTransactionsController', function () {
assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], { assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], {
incomingTxLastFetchedBlocksByNetwork: { incomingTxLastFetchedBlocksByNetwork: {
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork, ...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork,
'FAKE_NETWORK': 1112, [ROPSTEN]: 1112,
}, },
incomingTransactions: { incomingTransactions: {
'0x123456': { id: 777, hash: '0x123456' }, '0x123456': { id: 777, hash: '0x123456' },
@ -815,12 +793,12 @@ describe('IncomingTransactionsController', function () {
it('should call fetch with the expected url when passed an address, block number and supported network', async function () { it('should call fetch with the expected url when passed an address, block number and supported network', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN) await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN_CHAIN_ID)
assert(mockFetch.calledOnce) assert(mockFetch.calledOnce)
assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`) assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`)
@ -829,12 +807,12 @@ describe('IncomingTransactionsController', function () {
it('should call fetch with the expected url when passed an address, block number and MAINNET', async function () { it('should call fetch with the expected url when passed an address, block number and MAINNET', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(MAINNET_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', MAINNET) await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', MAINNET_CHAIN_ID)
assert(mockFetch.calledOnce) assert(mockFetch.calledOnce)
assert.equal(mockFetch.getCall(0).args[0], `https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`) assert.equal(mockFetch.getCall(0).args[0], `https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`)
@ -843,46 +821,32 @@ describe('IncomingTransactionsController', function () {
it('should call fetch with the expected url when passed an address and supported network, but a falsy block number', async function () { it('should call fetch with the expected url when passed an address and supported network, but a falsy block number', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
await incomingTransactionsController._fetchTxs('0xfakeaddress', null, ROPSTEN) await incomingTransactionsController._fetchTxs('0xfakeaddress', null, ROPSTEN_CHAIN_ID)
assert(mockFetch.calledOnce) assert(mockFetch.calledOnce)
assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`) assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`)
}) })
it('should not fetch and return an empty object when passed an unsported network', async function () {
const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(),
preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(),
})
const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', null, 'UNSUPPORTED_NETWORK')
assert(mockFetch.notCalled)
assert.deepEqual(result, {})
})
it('should return the results from the fetch call, plus the address and currentNetworkID, when passed an address, block number and supported network', async function () { it('should return the results from the fetch call, plus the address and currentNetworkID, when passed an address, block number and supported network', async function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN) const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN_CHAIN_ID)
assert(mockFetch.calledOnce) assert(mockFetch.calledOnce)
assert.deepEqual(result, { assert.deepEqual(result, {
someKey: 'someValue', someKey: 'someValue',
address: '0xfakeaddress', address: '0xfakeaddress',
currentNetworkID: '3', chainId: ROPSTEN_CHAIN_ID,
}) })
}) })
}) })
@ -891,7 +855,7 @@ describe('IncomingTransactionsController', function () {
it('should return a null block number and empty tx array if status is 0', function () { it('should return a null block number and empty tx array if status is 0', function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -911,7 +875,7 @@ describe('IncomingTransactionsController', function () {
it('should return a null block number and empty tx array if the passed result array is empty', function () { it('should return a null block number and empty tx array if the passed result array is empty', function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -931,21 +895,21 @@ describe('IncomingTransactionsController', function () {
it('should return the expected block number and tx list when passed data from a successful fetch', function () { it('should return the expected block number and tx list when passed data from a successful fetch', function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
incomingTransactionsController._normalizeTxFromEtherscan = (tx, currentNetworkID) => ({ incomingTransactionsController._normalizeTxFromEtherscan = (tx) => ({
...tx, ...tx,
currentNetworkID, currentNetworkID: ROPSTEN_NETWORK_ID,
normalized: true, normalized: true,
}) })
const result = incomingTransactionsController._processTxFetchResponse({ const result = incomingTransactionsController._processTxFetchResponse({
status: '1', status: '1',
address: '0xfakeaddress', address: '0xfakeaddress',
currentNetworkID: 'FAKE_NETWORK', chainId: ROPSTEN_CHAIN_ID,
result: [ result: [
{ {
hash: '0xabc123', hash: '0xabc123',
@ -1009,7 +973,7 @@ describe('IncomingTransactionsController', function () {
blockNumber: 5000, blockNumber: 5000,
time: 9, time: 9,
normalized: true, normalized: true,
currentNetworkID: 'FAKE_NETWORK', currentNetworkID: ROPSTEN_NETWORK_ID,
}, },
{ {
hash: '0xabc123', hash: '0xabc123',
@ -1019,7 +983,7 @@ describe('IncomingTransactionsController', function () {
blockNumber: 5000, blockNumber: 5000,
time: 10, time: 10,
normalized: true, normalized: true,
currentNetworkID: 'FAKE_NETWORK', currentNetworkID: ROPSTEN_NETWORK_ID,
}, },
{ {
hash: '0xabc12345', hash: '0xabc12345',
@ -1029,7 +993,7 @@ describe('IncomingTransactionsController', function () {
blockNumber: 5001, blockNumber: 5001,
time: 11, time: 11,
normalized: true, normalized: true,
currentNetworkID: 'FAKE_NETWORK', currentNetworkID: ROPSTEN_NETWORK_ID,
}, },
{ {
hash: '0xabc123456', hash: '0xabc123456',
@ -1039,7 +1003,7 @@ describe('IncomingTransactionsController', function () {
blockNumber: 5001, blockNumber: 5001,
time: 12, time: 12,
normalized: true, normalized: true,
currentNetworkID: 'FAKE_NETWORK', currentNetworkID: ROPSTEN_NETWORK_ID,
}, },
], ],
}) })
@ -1050,7 +1014,7 @@ describe('IncomingTransactionsController', function () {
it('should return the expected data when the tx is in error', function () { it('should return the expected data when the tx is in error', function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -1066,12 +1030,12 @@ describe('IncomingTransactionsController', function () {
to: '0xe', to: '0xe',
value: '15', value: '15',
hash: '0xg', hash: '0xg',
}, 'FAKE_NETWORK') }, ROPSTEN_CHAIN_ID)
assert.deepEqual(result, { assert.deepEqual(result, {
blockNumber: 333, blockNumber: 333,
id: 54321, id: 54321,
metamaskNetworkId: 'FAKE_NETWORK', metamaskNetworkId: ROPSTEN_NETWORK_ID,
status: 'failed', status: 'failed',
time: 4444000, time: 4444000,
txParams: { txParams: {
@ -1090,7 +1054,7 @@ describe('IncomingTransactionsController', function () {
it('should return the expected data when the tx is not in error', function () { it('should return the expected data when the tx is not in error', function () {
const incomingTransactionsController = new IncomingTransactionsController({ const incomingTransactionsController = new IncomingTransactionsController({
blockTracker: getMockBlockTracker(), blockTracker: getMockBlockTracker(),
networkController: getMockNetworkController(), networkController: getMockNetworkController(ROPSTEN_CHAIN_ID),
preferencesController: getMockPreferencesController(), preferencesController: getMockPreferencesController(),
initState: getNonEmptyInitState(), initState: getNonEmptyInitState(),
}) })
@ -1106,12 +1070,12 @@ describe('IncomingTransactionsController', function () {
to: '0xe', to: '0xe',
value: '15', value: '15',
hash: '0xg', hash: '0xg',
}, 'FAKE_NETWORK') }, ROPSTEN_CHAIN_ID)
assert.deepEqual(result, { assert.deepEqual(result, {
blockNumber: 333, blockNumber: 333,
id: 54321, id: 54321,
metamaskNetworkId: 'FAKE_NETWORK', metamaskNetworkId: ROPSTEN_NETWORK_ID,
status: 'confirmed', status: 'confirmed',
time: 4444000, time: 4444000,
txParams: { txParams: {

Loading…
Cancel
Save