Merge pull request #3585 from lazaridiscom/laz/i3568

[WIP] document/rearrange metamask-controller.js, re #3568
feature/default_network_editable
kumavis 7 years ago committed by GitHub
commit 112a9443ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .eslintrc
  2. 599
      app/scripts/metamask-controller.js

@ -148,7 +148,7 @@
"space-in-parens": [1, "never"], "space-in-parens": [1, "never"],
"space-infix-ops": 2, "space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }], "space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","], "exceptions": ["=", "-"] } ],
"strict": 0, "strict": 0,
"template-curly-spacing": [2, "never"], "template-curly-spacing": [2, "never"],
"use-isnan": 2, "use-isnan": 2,

@ -1,3 +1,9 @@
/**
* @file The central metamask controller. Aggregates other controllers and exports an api.
* @copyright Copyright (c) 2018 MetaMask
* @license MIT
*/
const EventEmitter = require('events') const EventEmitter = require('events')
const extend = require('xtend') const extend = require('xtend')
const pump = require('pump') const pump = require('pump')
@ -41,7 +47,11 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
module.exports = class MetamaskController extends EventEmitter { module.exports = class MetamaskController extends EventEmitter {
constructor (opts) { /**
* @constructor
* @param {Object} opts
*/
constructor (opts) {
super() super()
this.defaultMaxListeners = 20 this.defaultMaxListeners = 20
@ -223,10 +233,9 @@ module.exports = class MetamaskController extends EventEmitter {
this.infuraController.store.subscribe(sendUpdate) this.infuraController.store.subscribe(sendUpdate)
} }
// /**
// Constructor helpers * Constructor helper: initialize a provider.
// */
initializeProvider () { initializeProvider () {
const providerOpts = { const providerOpts = {
static: { static: {
@ -257,6 +266,9 @@ module.exports = class MetamaskController extends EventEmitter {
return providerProxy return providerProxy
} }
/**
* Constructor helper: initialize a public config store.
*/
initPublicConfigStore () { initPublicConfigStore () {
// get init state // get init state
const publicConfigStore = new ObservableStore() const publicConfigStore = new ObservableStore()
@ -278,10 +290,15 @@ module.exports = class MetamaskController extends EventEmitter {
return publicConfigStore return publicConfigStore
} }
// //=============================================================================
// State Management // EXPOSED TO THE UI SUBSYSTEM
// //=============================================================================
/**
* The metamask-state of the various controllers, made available to the UI
*
* @returns {Object} status
*/
getState () { getState () {
const wallet = this.configManager.getWallet() const wallet = this.configManager.getWallet()
const vault = this.keyringController.store.getState().vault const vault = this.keyringController.store.getState().vault
@ -316,10 +333,11 @@ module.exports = class MetamaskController extends EventEmitter {
) )
} }
// /**
// Remote Features * Returns an api-object which is consumed by the UI
// *
* @returns {Object}
*/
getApi () { getApi () {
const keyringController = this.keyringController const keyringController = this.keyringController
const preferencesController = this.preferencesController const preferencesController = this.preferencesController
@ -400,127 +418,24 @@ module.exports = class MetamaskController extends EventEmitter {
} }
} }
setupUntrustedCommunication (connectionStream, originDomain) {
// Check if new connection is blacklisted
if (this.blacklistController.checkForPhishing(originDomain)) {
log.debug('MetaMask - sending phishing warning for', originDomain)
this.sendPhishingWarning(connectionStream, originDomain)
return
}
// setup multiplexing
const mux = setupMultiplex(connectionStream)
// connect features
this.setupProviderConnection(mux.createStream('provider'), originDomain)
this.setupPublicConfig(mux.createStream('publicConfig'))
}
setupTrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
const mux = setupMultiplex(connectionStream)
// connect features
this.setupControllerConnection(mux.createStream('controller'))
this.setupProviderConnection(mux.createStream('provider'), originDomain)
}
sendPhishingWarning (connectionStream, hostname) {
const mux = setupMultiplex(connectionStream)
const phishingStream = mux.createStream('phishing')
phishingStream.write({ hostname })
}
setupControllerConnection (outStream) {
const api = this.getApi()
const dnode = Dnode(api)
pump(
outStream,
dnode,
outStream,
(err) => {
if (err) log.error(err)
}
)
dnode.on('remote', (remote) => {
// push updates to popup
const sendUpdate = remote.sendUpdate.bind(remote)
this.on('update', sendUpdate)
})
}
setupProviderConnection (outStream, origin) {
// setup json rpc engine stack
const engine = new RpcEngine()
// create filter polyfill middleware
const filterMiddleware = createFilterMiddleware({
provider: this.provider,
blockTracker: this.provider._blockTracker,
})
engine.push(createOriginMiddleware({ origin }))
engine.push(createLoggerMiddleware({ origin }))
engine.push(filterMiddleware)
engine.push(createProviderMiddleware({ provider: this.provider }))
// setup connection
const providerStream = createEngineStream({ engine })
pump(
outStream,
providerStream,
outStream,
(err) => {
// cleanup filter polyfill middleware
filterMiddleware.destroy()
if (err) log.error(err)
}
)
}
setupPublicConfig (outStream) {
pump(
asStream(this.publicConfigStore),
outStream,
(err) => {
if (err) log.error(err)
}
)
}
privateSendUpdate () {
this.emit('update', this.getState())
}
getGasPrice () { //=============================================================================
const { recentBlocksController } = this // VAULT / KEYRING RELATED METHODS
const { recentBlocks } = recentBlocksController.store.getState() //=============================================================================
// Return 1 gwei if no blocks have been observed:
if (recentBlocks.length === 0) {
return '0x' + GWEI_BN.toString(16)
}
const lowestPrices = recentBlocks.map((block) => {
if (!block.gasPrices || block.gasPrices.length < 1) {
return GWEI_BN
}
return block.gasPrices
.map(hexPrefix => hexPrefix.substr(2))
.map(hex => new BN(hex, 16))
.sort((a, b) => {
return a.gt(b) ? 1 : -1
})[0]
})
.map(number => number.div(GWEI_BN).toNumber())
const percentileNum = percentile(50, lowestPrices)
const percentileNumBn = new BN(percentileNum)
return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
}
//
// Vault Management
//
/**
* Creates a new Vault(?) and create a new keychain(?)
*
* A vault is ...
*
* A keychain is ...
*
*
* @param {} password
*
* @returns {} vault
*/
async createNewVaultAndKeychain (password) { async createNewVaultAndKeychain (password) {
const release = await this.createVaultMutex.acquire() const release = await this.createVaultMutex.acquire()
let vault let vault
@ -544,6 +459,11 @@ module.exports = class MetamaskController extends EventEmitter {
return vault return vault
} }
/**
* Create a new Vault and restore an existent keychain
* @param {} password
* @param {} seed
*/
async createNewVaultAndRestore (password, seed) { async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire() const release = await this.createVaultMutex.acquire()
try { try {
@ -557,16 +477,28 @@ module.exports = class MetamaskController extends EventEmitter {
} }
} }
/**
* Retrieves the first Identiy from the passed Vault and selects the related address
*
* An Identity is ...
*
* @param {} vault
*/
selectFirstIdentity (vault) { selectFirstIdentity (vault) {
const { identities } = vault const { identities } = vault
const address = Object.keys(identities)[0] const address = Object.keys(identities)[0]
this.preferencesController.setSelectedAddress(address) this.preferencesController.setSelectedAddress(address)
} }
// // ?
// Opinionated Keyring Management // Opinionated Keyring Management
// //
/**
* Adds a new account to ...
*
* @returns {} keyState
*/
async addNewAccount () { async addNewAccount () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) { if (!primaryKeyring) {
@ -588,10 +520,12 @@ module.exports = class MetamaskController extends EventEmitter {
return keyState return keyState
} }
// Adds the current vault's seed words to the UI's state tree. /**
// * Adds the current vault's seed words to the UI's state tree.
// Used when creating a first vault, to allow confirmation. *
// Also used when revealing the seed words in the confirmation view. * Used when creating a first vault, to allow confirmation.
* Also used when revealing the seed words in the confirmation view.
*/
placeSeedWords (cb) { placeSeedWords (cb) {
this.verifySeedPhrase() this.verifySeedPhrase()
@ -604,10 +538,13 @@ module.exports = class MetamaskController extends EventEmitter {
}) })
} }
// Verifies the current vault's seed words if they can restore the /**
// accounts belonging to the current vault. * Verifies the validity of the current vault's seed phrase.
// *
// Called when the first account is created and on unlocking the vault. * Validity: seed phrase restores the accounts belonging to the current vault.
*
* Called when the first account is created and on unlocking the vault.
*/
async verifySeedPhrase () { async verifySeedPhrase () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
@ -632,22 +569,33 @@ module.exports = class MetamaskController extends EventEmitter {
} }
} }
// ClearSeedWordCache /**
// * Remove the primary account seed phrase from the UI's state tree.
// Removes the primary account's seed words from the UI's state tree, *
// ensuring they are only ever available in the background process. * The seed phrase remains available in the background process.
*
*/
clearSeedWordCache (cb) { clearSeedWordCache (cb) {
this.configManager.setSeedWords(null) this.configManager.setSeedWords(null)
cb(null, this.preferencesController.getSelectedAddress()) cb(null, this.preferencesController.getSelectedAddress())
} }
/**
* ?
*/
resetAccount (cb) { resetAccount (cb) {
const selectedAddress = this.preferencesController.getSelectedAddress() const selectedAddress = this.preferencesController.getSelectedAddress()
this.txController.wipeTransactions(selectedAddress) this.txController.wipeTransactions(selectedAddress)
cb(null, selectedAddress) cb(null, selectedAddress)
} }
/**
* Imports an account ... ?
*
* @param {} strategy
* @param {} args
* @param {} cb
*/
importAccountWithStrategy (strategy, args, cb) { importAccountWithStrategy (strategy, args, cb) {
accountImporter.importAccount(strategy, args) accountImporter.importAccount(strategy, args)
.then((privateKey) => { .then((privateKey) => {
@ -659,11 +607,150 @@ module.exports = class MetamaskController extends EventEmitter {
.catch((reason) => { cb(reason) }) .catch((reason) => { cb(reason) })
} }
// ---------------------------------------------------------------------------
// Identity Management (sign)
// /**
// Identity Management * @param {} msgParams
// * @param {} cb
// */
signMessage (msgParams, cb) {
log.info('MetaMaskController - signMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.messageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.messageManager.setMsgStatusSigned(msgId, rawSig)
return this.getState()
})
}
// Prefixed Style Message Signing Methods:
/**
*
* @param {} msgParams
* @param {} cb
*/
approvePersonalMessage (msgParams, cb) {
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'signed':
return cb(null, data.rawSig)
case 'rejected':
return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
default:
return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
}
})
}
/**
* @param {} msgParams
*/
signPersonalMessage (msgParams) {
log.info('MetaMaskController - signPersonalMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.personalMessageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signPersonalMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.personalMessageManager.setMsgStatusSigned(msgId, rawSig)
return this.getState()
})
}
/**
* @param {} msgParams
*/
signTypedMessage (msgParams) {
log.info('MetaMaskController - signTypedMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.typedMessageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signTypedMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.typedMessageManager.setMsgStatusSigned(msgId, rawSig)
return this.getState()
})
}
// ---------------------------------------------------------------------------
// Account Restauration
/**
* ?
*
* @param {} migratorOutput
*/
restoreOldVaultAccounts (migratorOutput) {
const { serialized } = migratorOutput
return this.keyringController.restoreKeyring(serialized)
.then(() => migratorOutput)
}
/**
* ?
*
* @param {} migratorOutput
*/
restoreOldLostAccounts (migratorOutput) {
const { lostAccounts } = migratorOutput
if (lostAccounts) {
this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
return this.importLostAccounts(migratorOutput)
}
return Promise.resolve(migratorOutput)
}
/**
* Import (lost) Accounts
*
* @param {Object} {lostAccounts} @Array accounts <{ address, privateKey }>
*
* Uses the array's private keys to create a new Simple Key Pair keychain
* and add it to the keyring controller.
*/
importLostAccounts ({ lostAccounts }) {
const privKeys = lostAccounts.map(acct => acct.privateKey)
return this.keyringController.restoreKeyring({
type: 'Simple Key Pair',
data: privKeys,
})
}
//=============================================================================
// END (VAULT / KEYRING RELATED METHODS)
//=============================================================================
//
//=============================================================================
// MESSAGES
//=============================================================================
async retryTransaction (txId, cb) { async retryTransaction (txId, cb) {
await this.txController.retryTransaction(txId) await this.txController.retryTransaction(txId)
@ -730,85 +817,13 @@ module.exports = class MetamaskController extends EventEmitter {
}) })
} }
signMessage (msgParams, cb) {
log.info('MetaMaskController - signMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.messageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.messageManager.setMsgStatusSigned(msgId, rawSig)
return this.getState()
})
}
cancelMessage (msgId, cb) { cancelMessage (msgId, cb) {
const messageManager = this.messageManager const messageManager = this.messageManager
messageManager.rejectMsg(msgId) messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') { if (cb && typeof cb === 'function') {
cb(null, this.getState()) cb(null, this.getState())
} }
} }
// Prefixed Style Message Signing Methods:
approvePersonalMessage (msgParams, cb) {
const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
this.personalMessageManager.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'signed':
return cb(null, data.rawSig)
case 'rejected':
return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
default:
return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
}
})
}
signPersonalMessage (msgParams) {
log.info('MetaMaskController - signPersonalMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.personalMessageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signPersonalMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.personalMessageManager.setMsgStatusSigned(msgId, rawSig)
return this.getState()
})
}
signTypedMessage (msgParams) {
log.info('MetaMaskController - signTypedMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.typedMessageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signTypedMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.typedMessageManager.setMsgStatusSigned(msgId, rawSig)
return this.getState()
})
}
cancelPersonalMessage (msgId, cb) { cancelPersonalMessage (msgId, cb) {
const messageManager = this.personalMessageManager const messageManager = this.personalMessageManager
@ -844,36 +859,130 @@ module.exports = class MetamaskController extends EventEmitter {
cb() cb()
} }
restoreOldVaultAccounts (migratorOutput) { //=============================================================================
const { serialized } = migratorOutput // SETUP
return this.keyringController.restoreKeyring(serialized) //=============================================================================
.then(() => migratorOutput)
}
restoreOldLostAccounts (migratorOutput) { setupUntrustedCommunication (connectionStream, originDomain) {
const { lostAccounts } = migratorOutput // Check if new connection is blacklisted
if (lostAccounts) { if (this.blacklistController.checkForPhishing(originDomain)) {
this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address)) log.debug('MetaMask - sending phishing warning for', originDomain)
return this.importLostAccounts(migratorOutput) this.sendPhishingWarning(connectionStream, originDomain)
return
} }
return Promise.resolve(migratorOutput)
// setup multiplexing
const mux = setupMultiplex(connectionStream)
// connect features
this.setupProviderConnection(mux.createStream('provider'), originDomain)
this.setupPublicConfig(mux.createStream('publicConfig'))
} }
// IMPORT LOST ACCOUNTS setupTrustedCommunication (connectionStream, originDomain) {
// @Object with key lostAccounts: @Array accounts <{ address, privateKey }> // setup multiplexing
// Uses the array's private keys to create a new Simple Key Pair keychain const mux = setupMultiplex(connectionStream)
// and add it to the keyring controller. // connect features
importLostAccounts ({ lostAccounts }) { this.setupControllerConnection(mux.createStream('controller'))
const privKeys = lostAccounts.map(acct => acct.privateKey) this.setupProviderConnection(mux.createStream('provider'), originDomain)
return this.keyringController.restoreKeyring({ }
type: 'Simple Key Pair',
data: privKeys, sendPhishingWarning (connectionStream, hostname) {
const mux = setupMultiplex(connectionStream)
const phishingStream = mux.createStream('phishing')
phishingStream.write({ hostname })
}
setupControllerConnection (outStream) {
const api = this.getApi()
const dnode = Dnode(api)
pump(
outStream,
dnode,
outStream,
(err) => {
if (err) log.error(err)
}
)
dnode.on('remote', (remote) => {
// push updates to popup
const sendUpdate = remote.sendUpdate.bind(remote)
this.on('update', sendUpdate)
}) })
} }
// setupProviderConnection (outStream, origin) {
// config // setup json rpc engine stack
// const engine = new RpcEngine()
// create filter polyfill middleware
const filterMiddleware = createFilterMiddleware({
provider: this.provider,
blockTracker: this.provider._blockTracker,
})
engine.push(createOriginMiddleware({ origin }))
engine.push(createLoggerMiddleware({ origin }))
engine.push(filterMiddleware)
engine.push(createProviderMiddleware({ provider: this.provider }))
// setup connection
const providerStream = createEngineStream({ engine })
pump(
outStream,
providerStream,
outStream,
(err) => {
// cleanup filter polyfill middleware
filterMiddleware.destroy()
if (err) log.error(err)
}
)
}
setupPublicConfig (outStream) {
pump(
asStream(this.publicConfigStore),
outStream,
(err) => {
if (err) log.error(err)
}
)
}
privateSendUpdate () {
this.emit('update', this.getState())
}
getGasPrice () {
const { recentBlocksController } = this
const { recentBlocks } = recentBlocksController.store.getState()
// Return 1 gwei if no blocks have been observed:
if (recentBlocks.length === 0) {
return '0x' + GWEI_BN.toString(16)
}
const lowestPrices = recentBlocks.map((block) => {
if (!block.gasPrices || block.gasPrices.length < 1) {
return GWEI_BN
}
return block.gasPrices
.map(hexPrefix => hexPrefix.substr(2))
.map(hex => new BN(hex, 16))
.sort((a, b) => {
return a.gt(b) ? 1 : -1
})[0]
})
.map(number => number.div(GWEI_BN).toNumber())
const percentileNum = percentile(50, lowestPrices)
const percentileNumBn = new BN(percentileNum)
return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
}
//=============================================================================
// CONFIG
//=============================================================================
// Log blocks // Log blocks

Loading…
Cancel
Save