1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 01:39:44 +01:00

Merge pull request #1914 from MetaMask/history-diff

Move Tx State History to diff-based format
This commit is contained in:
Dan Finlay 2017-08-18 13:14:17 -07:00 committed by GitHub
commit e0c35179c2
8 changed files with 3210 additions and 25 deletions

View File

@ -1,6 +1,5 @@
const EventEmitter = require('events') const EventEmitter = require('events')
const extend = require('xtend') const extend = require('xtend')
const clone = require('clone')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const EthQuery = require('ethjs-query') const EthQuery = require('ethjs-query')
@ -8,6 +7,7 @@ const TxProviderUtil = require('../lib/tx-utils')
const PendingTransactionTracker = require('../lib/pending-tx-tracker') const PendingTransactionTracker = require('../lib/pending-tx-tracker')
const createId = require('../lib/random-id') const createId = require('../lib/random-id')
const NonceTracker = require('../lib/nonce-tracker') const NonceTracker = require('../lib/nonce-tracker')
const txStateHistoryHelper = require('../lib/tx-state-history-helper')
module.exports = class TransactionController extends EventEmitter { module.exports = class TransactionController extends EventEmitter {
constructor (opts) { constructor (opts) {
@ -128,19 +128,17 @@ module.exports = class TransactionController extends EventEmitter {
updateTx (txMeta) { updateTx (txMeta) {
// create txMeta snapshot for history // create txMeta snapshot for history
const txMetaForHistory = clone(txMeta) const currentState = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
// dont include previous history in this snapshot // recover previous tx state obj
delete txMetaForHistory.history const previousState = txStateHistoryHelper.replayHistory(txMeta.history)
// add snapshot to tx history // generate history entry and add to history
if (!txMeta.history) txMeta.history = [] const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState)
txMeta.history.push(txMetaForHistory) txMeta.history.push(entry)
// commit txMeta to state
const txId = txMeta.id const txId = txMeta.id
const txList = this.getFullTxList() const txList = this.getFullTxList()
const index = txList.findIndex(txData => txData.id === txId) const index = txList.findIndex(txData => txData.id === txId)
if (!txMeta.history) txMeta.history = []
txMeta.history.push(txMetaForHistory)
txList[index] = txMeta txList[index] = txMeta
this._saveTxList(txList) this._saveTxList(txList)
this.emit('update') this.emit('update')
@ -148,16 +146,22 @@ module.exports = class TransactionController extends EventEmitter {
// Adds a tx to the txlist // Adds a tx to the txlist
addTx (txMeta) { addTx (txMeta) {
const txCount = this.getTxCount() // initialize history
const network = this.getNetwork() txMeta.history = []
const fullTxList = this.getFullTxList() // capture initial snapshot of txMeta for history
const txHistoryLimit = this.txHistoryLimit const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
txMeta.history.push(snapshot)
// checks if the length of the tx history is // checks if the length of the tx history is
// longer then desired persistence limit // longer then desired persistence limit
// and then if it is removes only confirmed // and then if it is removes only confirmed
// or rejected tx's. // or rejected tx's.
// not tx's that are pending or unapproved // not tx's that are pending or unapproved
const txCount = this.getTxCount()
const network = this.getNetwork()
const fullTxList = this.getFullTxList()
const txHistoryLimit = this.txHistoryLimit
if (txCount > txHistoryLimit - 1) { if (txCount > txHistoryLimit - 1) {
const index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId)) const index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId))
fullTxList.splice(index, 1) fullTxList.splice(index, 1)
@ -206,7 +210,6 @@ module.exports = class TransactionController extends EventEmitter {
status: 'unapproved', status: 'unapproved',
metamaskNetworkId: this.getNetwork(), metamaskNetworkId: this.getNetwork(),
txParams: txParams, txParams: txParams,
history: [],
} }
// add default tx params // add default tx params
await this.addTxDefaults(txMeta) await this.addTxDefaults(txMeta)

View File

@ -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
}

View File

@ -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
}

View File

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

View File

@ -82,6 +82,7 @@
"express": "^4.14.0", "express": "^4.14.0",
"extension-link-enabler": "^1.0.0", "extension-link-enabler": "^1.0.0",
"extensionizer": "^1.0.0", "extensionizer": "^1.0.0",
"fast-json-patch": "^2.0.4",
"fast-levenshtein": "^2.0.6", "fast-levenshtein": "^2.0.6",
"gulp": "github:gulpjs/gulp#4.0", "gulp": "github:gulpjs/gulp#4.0",
"gulp-eslint": "^4.0.0", "gulp-eslint": "^4.0.0",

File diff suppressed because it is too large Load Diff

View File

@ -6,12 +6,15 @@ const clone = require('clone')
const sinon = require('sinon') const sinon = require('sinon')
const TransactionController = require('../../app/scripts/controllers/transactions') const TransactionController = require('../../app/scripts/controllers/transactions')
const TxProvideUtils = require('../../app/scripts/lib/tx-utils') const TxProvideUtils = require('../../app/scripts/lib/tx-utils')
const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper')
const noop = () => true const noop = () => true
const currentNetworkId = 42 const currentNetworkId = 42
const otherNetworkId = 36 const otherNetworkId = 36
const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
const { createStubedProvider } = require('../stub/provider') const { createStubedProvider } = require('../stub/provider')
describe('Transaction Controller', function () { describe('Transaction Controller', function () {
let txController, engine, provider, providerResultStub let txController, engine, provider, providerResultStub
@ -47,7 +50,7 @@ describe('Transaction Controller', function () {
metamaskNetworkId: currentNetworkId, metamaskNetworkId: currentNetworkId,
txParams, txParams,
} }
txController._saveTxList([txMeta]) txController.addTx(txMeta)
stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txMeta)) stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txMeta))
}) })
@ -279,12 +282,15 @@ describe('Transaction Controller', function () {
it('replaces the tx with the same id', function () { it('replaces the tx with the same id', function () {
txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
txController.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} }) const tx1 = txController.getTx('1')
var result = txController.getTx('1') tx1.status = 'blah'
assert.equal(result.hash, 'foo') tx1.hash = 'foo'
txController.updateTx(tx1)
const savedResult = txController.getTx('1')
assert.equal(savedResult.hash, 'foo')
}) })
it('updates gas price', function () { it('updates gas price and adds history items', function () {
const originalGasPrice = '0x01' const originalGasPrice = '0x01'
const desiredGasPrice = '0x02' const desiredGasPrice = '0x02'
@ -297,13 +303,22 @@ describe('Transaction Controller', function () {
}, },
} }
const updatedMeta = clone(txMeta)
txController.addTx(txMeta) txController.addTx(txMeta)
updatedMeta.txParams.gasPrice = desiredGasPrice const updatedTx = txController.getTx('1')
txController.updateTx(updatedMeta) // verify tx was initialized correctly
var result = txController.getTx('1') 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') 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)')
}) })
}) })

View File

@ -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')
})
})
})
})