Merge branch 'master' into transactionControllerRefractorPt3

feature/default_network_editable
frankiebee 7 years ago
commit fbba3a1ac8
  1. 17
      CHANGELOG.md
  2. 2
      app/manifest.json
  3. 2
      app/scripts/controllers/transactions.js
  4. 5
      app/scripts/lib/auto-reload.js
  5. 37
      app/scripts/lib/tx-state-history-helper.js
  6. 26
      app/scripts/lib/tx-state-manager.js
  7. 52
      app/scripts/migrations/018.js
  8. 1
      app/scripts/migrations/index.js
  9. 3
      package.json
  10. 3053
      test/data/v17-long-history.json
  11. 87
      test/unit/actions/tx_test.js
  12. 9
      test/unit/tx-controller-test.js
  13. 23
      test/unit/tx-state-history-helper.js
  14. 19
      test/unit/tx-state-manager-test.js
  15. 13
      ui/app/account-detail.js
  16. 23
      ui/app/actions.js
  17. 2
      ui/app/app.js
  18. 11
      ui/app/components/account-dropdowns.js
  19. 2
      ui/app/components/pending-msg-details.js
  20. 5
      ui/app/components/pending-msg.js
  21. 45
      ui/app/components/token-list.js

@ -2,6 +2,23 @@
## Current Master
## 3.9.9 2017-8-18
- Fix bug where some transaction submission errors would show an empty screen.
- Fix bug that could mis-render token balances when very small.
- Fix formatting of eth_sign "Sign Message" view.
- Add deprecation warning to eth_sign "Sign Message" view.
## 3.9.8 2017-8-16
- Reenable token list.
- Remove default tokens.
## 3.9.7 2017-8-15
- hotfix - disable token list
- Added a deprecation warning for web3 https://github.com/ethereum/mist/releases/tag/v0.9.0
## 3.9.6 2017-8-09
- Replace account screen with an account drop-down menu.

@ -1,7 +1,7 @@
{
"name": "MetaMask",
"short_name": "Metamask",
"version": "3.9.6",
"version": "3.9.9",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",

@ -114,6 +114,7 @@ module.exports = class TransactionController extends EventEmitter {
// listen for tx completion (success, fail)
return new Promise((resolve, reject) => {
this.txStateManager.once(`${txMeta.id}:finished`, (completedTx) => {
this.emit('updateBadge')
switch (completedTx.status) {
case 'submitted':
return resolve(completedTx.hash)
@ -136,7 +137,6 @@ module.exports = class TransactionController extends EventEmitter {
status: 'unapproved',
metamaskNetworkId: this.getNetwork(),
txParams: txParams,
history: [],
}
// add default tx params
await this.addTxDefaults(txMeta)

@ -5,7 +5,10 @@ function setupDappAutoReload (web3, observable) {
global.web3 = new Proxy(web3, {
get: (_web3, name) => {
// get the time of use
if (name !== '_used') _web3._used = Date.now()
if (name !== '_used') {
console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/ethereum/mist/releases/tag/v0.9.0')
_web3._used = Date.now()
}
return _web3[name]
},
set: (_web3, name, value) => {

@ -0,0 +1,37 @@
const jsonDiffer = require('fast-json-patch')
const clone = require('clone')
module.exports = {
generateHistoryEntry,
replayHistory,
snapshotFromTxMeta,
migrateFromSnapshotsToDiffs,
}
function migrateFromSnapshotsToDiffs(longHistory) {
return (
longHistory
// convert non-initial history entries into diffs
.map((entry, index) => {
if (index === 0) return entry
return generateHistoryEntry(longHistory[index - 1], entry)
})
)
}
function generateHistoryEntry(previousState, newState) {
return jsonDiffer.compare(previousState, newState)
}
function replayHistory(shortHistory) {
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
}
function snapshotFromTxMeta(txMeta) {
// create txMeta snapshot for history
const snapshot = clone(txMeta)
// dont include previous history in this snapshot
delete snapshot.history
return snapshot
}

@ -1,6 +1,6 @@
const clone = require('clone')
const extend = require('xtend')
const ObservableStore = require('obs-store')
const txStateHistoryHelper = require('./tx-state-history-helper')
module.exports = class TransactionStateManger extends ObservableStore {
constructor ({initState, txHistoryLimit, getNetwork}) {
@ -41,6 +41,11 @@ module.exports = class TransactionStateManger extends ObservableStore {
this.once(`${txMeta.id}:rejected`, function (txId) {
this.removeAllListeners(`${txMeta.id}:signed`)
})
// initialize history
txMeta.history = []
// capture initial snapshot of txMeta for history
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
txMeta.history.push(snapshot)
const transactions = this.getFullTxList()
const txCount = this.getTxCount()
@ -57,6 +62,7 @@ module.exports = class TransactionStateManger extends ObservableStore {
}
transactions.push(txMeta)
this._saveTxList(transactions)
return txMeta
}
// gets tx by Id and returns it
getTx (txId) {
@ -66,19 +72,17 @@ module.exports = class TransactionStateManger extends ObservableStore {
updateTx (txMeta) {
// create txMeta snapshot for history
const txMetaForHistory = clone(txMeta)
// dont include previous history in this snapshot
delete txMetaForHistory.history
// add snapshot to tx history
if (!txMeta.history) txMeta.history = []
txMeta.history.push(txMetaForHistory)
const currentState = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
// recover previous tx state obj
const previousState = txStateHistoryHelper.replayHistory(txMeta.history)
// generate history entry and add to history
const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState)
txMeta.history.push(entry)
// commit txMeta to state
const txId = txMeta.id
const txList = this.getFullTxList()
const index = txList.findIndex(txData => txData.id === txId)
if (!txMeta.history) txMeta.history = []
txMeta.history.push(txMetaForHistory)
txList[index] = txMeta
this._saveTxList(txList)
}

@ -0,0 +1,52 @@
const version = 18
/*
This migration updates "transaction state history" to diffs style
*/
const clone = require('clone')
const txStateHistoryHelper = require('../lib/tx-state-history-helper')
module.exports = {
version,
migrate: function (originalVersionedData) {
const versionedData = clone(originalVersionedData)
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = state
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta) => {
// no history: initialize
if (!txMeta.history || txMeta.history.length === 0) {
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
txMeta.history = [snapshot]
return txMeta
}
// has history: migrate
const newHistory = (
txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history)
// remove empty diffs
.filter((entry) => {
return !Array.isArray(entry) || entry.length > 0
})
)
txMeta.history = newHistory
return txMeta
})
return newState
}

@ -28,4 +28,5 @@ module.exports = [
require('./015'),
require('./016'),
require('./017'),
require('./018'),
]

@ -73,7 +73,7 @@
"eth-query": "^2.1.2",
"eth-sig-util": "^1.2.2",
"eth-simple-keyring": "^1.1.1",
"eth-token-tracker": "^1.1.2",
"eth-token-tracker": "^1.1.3",
"ethereumjs-tx": "^1.3.0",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
"ethereumjs-wallet": "^0.6.0",
@ -82,6 +82,7 @@
"express": "^4.14.0",
"extension-link-enabler": "^1.0.0",
"extensionizer": "^1.0.0",
"fast-json-patch": "^2.0.4",
"fast-levenshtein": "^2.0.6",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-eslint": "^4.0.0",

File diff suppressed because it is too large Load Diff

@ -53,7 +53,7 @@ describe('tx confirmation screen', function () {
result = reducers(initialState, action)
done()
})
})
it('should transition to the account detail view', function () {
@ -65,91 +65,6 @@ describe('tx confirmation screen', function () {
assert.equal(count, 0)
})
})
describe('sendTx', function () {
var result
describe('when there is an error', function () {
before(function (done) {
actions._setBackgroundConnection({
approveTransaction (txId, cb) { cb({message: 'An error!'}) },
})
actions.sendTx({id: firstTxId})(function (action) {
result = reducers(initialState, action)
done()
})
})
it('should stay on the page', function () {
assert.equal(result.appState.currentView.name, 'confTx')
})
it('should set errorMessage on the currentView', function () {
assert(result.appState.currentView.errorMessage)
})
})
describe('when there is success', function () {
it('should complete tx and go home', function () {
actions._setBackgroundConnection({
approveTransaction (txId, cb) { cb() },
})
var dispatchExpect = sinon.mock()
dispatchExpect.twice()
actions.sendTx({id: firstTxId})(dispatchExpect)
})
})
})
describe('when there are two pending txs', function () {
var firstTxId = 1457634084250832
var result, initialState
before(function (done) {
initialState = {
appState: {
currentView: {
name: 'confTx',
},
},
metamask: {
unapprovedTxs: {
'1457634084250832': {
id: firstTxId,
status: 'unconfirmed',
time: 1457634084250,
},
'1457634084250833': {
id: 1457634084250833,
status: 'unconfirmed',
time: 1457634084255,
},
},
},
}
freeze(initialState)
// Mocking a background connection:
actions._setBackgroundConnection({
approveTransaction (firstTxId, cb) { cb() },
})
actions.sendTx({id: firstTxId})(function (action) {
result = reducers(initialState, action)
})
done()
})
it('should stay on the confTx view', function () {
assert.equal(result.appState.currentView.name, 'confTx')
})
it('should transition to the first tx', function () {
assert.equal(result.appState.currentView.context, 0)
})
})
})
})

@ -6,12 +6,15 @@ const clone = require('clone')
const sinon = require('sinon')
const TransactionController = require('../../app/scripts/controllers/transactions')
const TxProvideUtils = require('../../app/scripts/lib/tx-utils')
const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper')
const noop = () => true
const currentNetworkId = 42
const otherNetworkId = 36
const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
const { createStubedProvider } = require('../stub/provider')
describe('Transaction Controller', function () {
let txController, engine, provider, providerResultStub
@ -46,9 +49,10 @@ describe('Transaction Controller', function () {
id: 1,
metamaskNetworkId: currentNetworkId,
txParams,
history: [],
}
txController.txStateManager._saveTxList([txMeta])
stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txMeta))
stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txController.txStateManager.addTx(txMeta)))
})
afterEach(function () {
@ -163,7 +167,8 @@ describe('Transaction Controller', function () {
describe('#addTx', function () {
it('should emit updates', function (done) {
txMeta = {
const txMeta = {
id: '1',
status: 'unapproved',
id: 1,
metamaskNetworkId: currentNetworkId,

@ -0,0 +1,23 @@
const assert = require('assert')
const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper')
const testVault = require('../data/v17-long-history.json')
describe('tx-state-history-helper', function () {
it('migrates history to diffs and can recover original values', function () {
testVault.data.TransactionController.transactions.forEach((tx, index) => {
const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
newHistory.forEach((newEntry, index) => {
if (index === 0) {
assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj')
} else {
assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj')
}
const oldEntry = tx.history[index]
const historySubset = newHistory.slice(0, index + 1)
const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset)
assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs')
})
})
})
})

@ -150,7 +150,7 @@ describe('TransactionStateManger', function () {
assert.equal(result.hash, 'foo')
})
it('updates gas price', function () {
it('updates gas price and adds history items', function () {
const originalGasPrice = '0x01'
const desiredGasPrice = '0x02'
@ -166,10 +166,21 @@ describe('TransactionStateManger', function () {
const updatedMeta = clone(txMeta)
txStateManager.addTx(txMeta)
updatedMeta.txParams.gasPrice = desiredGasPrice
txStateManager.updateTx(updatedMeta)
let result = txStateManager.getTx('1')
const updatedTx = txController.getTx('1')
// verify tx was initialized correctly
assert.equal(updatedTx.history.length, 1, 'one history item (initial)')
assert.equal(Array.isArray(updatedTx.history[0]), false, 'first history item is initial state')
assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state')
// modify value and updateTx
updatedTx.txParams.gasPrice = desiredGasPrice
txController.updateTx(updatedTx)
// check updated value
const result = txController.getTx('1')
assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated')
// validate history was updated
assert.equal(result.history.length, 2, 'two history items (initial + diff)')
const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice }
assert.deepEqual(result.history[1], [expectedEntry], 'two history items (initial + diff)')
})
})

@ -107,14 +107,23 @@ AccountDetailScreen.prototype.render = function () {
},
[
h(
'h2.font-medium.color-forest',
'div.font-medium.color-forest',
{
name: 'edit',
style: {
},
},
[
identity && identity.name,
h('h2', {
style: {
maxWidth: '180px',
overflow: 'hidden',
textOverflow: 'ellipsis',
padding: '5px 0px',
},
}, [
identity && identity.name,
]),
]
),
h(

@ -97,7 +97,6 @@ var actions = {
cancelMsg: cancelMsg,
signPersonalMsg,
cancelPersonalMsg,
sendTx: sendTx,
signTx: signTx,
updateAndApproveTx,
cancelTx: cancelTx,
@ -397,26 +396,13 @@ function signPersonalMsg (msgData) {
function signTx (txData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
global.ethQuery.sendTransaction(txData, (err, data) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.hideWarning())
})
dispatch(this.showConfTxPage())
}
}
function sendTx (txData) {
log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
return (dispatch) => {
log.debug(`actions calling background.approveTransaction`)
background.approveTransaction(txData.id, (err) => {
if (err) {
dispatch(actions.txError(err))
return log.error(err.message)
}
dispatch(actions.completedTx(txData.id))
if (err) dispatch(actions.displayWarning(err.message))
dispatch(this.goHome())
})
dispatch(actions.showConfTxPage())
}
}
@ -428,6 +414,7 @@ function updateAndApproveTx (txData) {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.txError(err))
dispatch(actions.goHome())
return log.error(err.message)
}
dispatch(actions.completedTx(txData.id))

@ -185,7 +185,7 @@ App.prototype.renderAppBar = function () {
style: {},
enableAccountsSelector: true,
identities: this.props.identities,
selected: this.props.selected,
selected: this.props.currentView.context,
network: this.props.network,
}, []),

@ -51,7 +51,16 @@ class AccountDropdowns extends Component {
},
},
),
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, identity.name || ''),
h('span', {
style: {
marginLeft: '20px',
fontSize: '24px',
maxWidth: '145px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
}, identity.name || ''),
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
]
)

@ -38,7 +38,7 @@ PendingMsgDetails.prototype.render = function () {
// message data
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [
h('.flex-row.flex-space-between', [
h('.flex-column.flex-space-between', [
h('label.font-small', 'MESSAGE'),
h('span.font-small', msgParams.data),
]),

@ -18,6 +18,9 @@ PendingMsg.prototype.render = function () {
h('div', {
key: msgData.id,
style: {
maxWidth: '350px',
},
}, [
// header
@ -35,7 +38,7 @@ PendingMsg.prototype.render = function () {
}, `Signing this message can have
dangerous side effects. Only sign messages from
sites you fully trust with your entire account.
This will be fixed in a future version.`),
This dangerous method will be removed in a future version.`),
// message details
h(PendingTxDetails, state),

@ -3,17 +3,6 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const TokenTracker = require('eth-token-tracker')
const TokenCell = require('./token-cell.js')
const normalizeAddress = require('eth-sig-util').normalize
const defaultTokens = []
const contracts = require('eth-contract-metadata')
for (const address in contracts) {
const contract = contracts[address]
if (contract.erc20) {
contract.address = address
defaultTokens.push(contract)
}
}
module.exports = TokenList
@ -38,7 +27,24 @@ TokenList.prototype.render = function () {
if (error) {
log.error(error)
return this.message('There was a problem loading your token balances.')
return h('.hotFix', {
style: {
padding: '80px',
},
}, [
'We had trouble loading your token balances. You can view them ',
h('span.hotFix', {
style: {
color: 'rgba(247, 134, 28, 1)',
cursor: 'pointer',
},
onClick: () => {
global.platform.openWindow({
url: `https://ethplorer.io/address/${userAddress}`,
})
},
}, 'here'),
])
}
const tokenViews = tokens.map((tokenData) => {
@ -153,7 +159,7 @@ TokenList.prototype.createFreshTokenTracker = function () {
this.tracker = new TokenTracker({
userAddress,
provider: global.ethereumProvider,
tokens: uniqueMergeTokens(defaultTokens, this.props.tokens),
tokens: this.props.tokens,
pollingInterval: 8000,
})
@ -199,16 +205,3 @@ TokenList.prototype.componentWillUnmount = function () {
this.tracker.stop()
}
function uniqueMergeTokens (tokensA, tokensB) {
const uniqueAddresses = []
const result = []
tokensA.concat(tokensB).forEach((token) => {
const normal = normalizeAddress(token.address)
if (!uniqueAddresses.includes(normal)) {
uniqueAddresses.push(normal)
result.push(token)
}
})
return result
}

Loading…
Cancel
Save