diff --git a/lib/runCall.js b/lib/runCall.js index f4e53bb8b7..5c163c4478 100644 --- a/lib/runCall.js +++ b/lib/runCall.js @@ -52,10 +52,10 @@ module.exports = function (opts, cb) { stateManager.checkpoint() // run and parse - subTxValue() - async.series([ + subTxValue, loadToAccount, + addTxValue, loadCode, runCode, saveCode @@ -83,26 +83,29 @@ module.exports = function (opts, cb) { } } - function subTxValue () { + function subTxValue (cb) { if (delegatecall) { + cb() return } - account.balance = new BN(account.balance).sub(txValue) - stateManager.cache.put(caller, account) + var newBalance = new BN(account.balance).sub(txValue) + account.balance = newBalance + stateManager.putAccountBalance(ethUtil.toBuffer(caller), newBalance, cb) } - function addTxValue () { + function addTxValue (cb) { if (delegatecall) { + cb() return } // add the amount sent to the `to` account - toAccount.balance = new BN(toAccount.balance).add(txValue) - stateManager.cache.put(toAddress, toAccount) - stateManager.touched.push(toAddress) + var newBalance = new BN(toAccount.balance).add(txValue) + toAccount.balance = newBalance + // putAccount as the nonce may have changed for contract creation + stateManager.putAccount(ethUtil.toBuffer(toAddress), toAccount, cb) } function loadCode (cb) { - addTxValue() // loads the contract's code if the account is a contract if (code || !(toAccount.isContract() || self._precompiled[toAddress.toString('hex')])) { cb() diff --git a/lib/runCode.js b/lib/runCode.js index 5b9938a542..8e1ed4ad0e 100644 --- a/lib/runCode.js +++ b/lib/runCode.js @@ -248,7 +248,6 @@ module.exports = function (opts, cb) { if (results.exceptionError) { delete results.gasRefund delete results.selfdestruct - self.stateManager.touched = [] } if (err && err.error !== ERROR.REVERT) { diff --git a/lib/runTx.js b/lib/runTx.js index ad2e73f927..28ae76b32e 100644 --- a/lib/runTx.js +++ b/lib/runTx.js @@ -69,7 +69,6 @@ module.exports = function (opts, cb) { if (tx.to.toString('hex') !== '') { accounts.add(tx.to.toString('hex')) - self.stateManager.touched.push(tx.to) } if (opts.populateCache === false) { @@ -148,53 +147,50 @@ module.exports = function (opts, cb) { } results.amountSpent = results.gasUsed.mul(new BN(tx.gasPrice)) - // refund the leftover gas amount - fromAccount.balance = new BN(tx.gasLimit).sub(results.gasUsed) - .mul(new BN(tx.gasPrice)) - .add(new BN(fromAccount.balance)) - - self.stateManager.cache.put(tx.from, fromAccount) - self.stateManager.touched.push(tx.from) - - var minerAccount = self.stateManager.cache.get(block.header.coinbase) - // add the amount spent on gas to the miner's account - minerAccount.balance = new BN(minerAccount.balance) - .add(results.amountSpent) - - // save the miner's account - if (!(new BN(minerAccount.balance).isZero())) { - self.stateManager.cache.put(block.header.coinbase, minerAccount) + + async.series([ + updateFromAccount, + updateMinerAccount, + cleanupAccounts + ], cb) + + function updateFromAccount (next) { + // refund the leftover gas amount + var finalFromBalance = new BN(tx.gasLimit).sub(results.gasUsed) + .mul(new BN(tx.gasPrice)) + .add(new BN(fromAccount.balance)) + fromAccount.balance = finalFromBalance + + self.stateManager.putAccountBalance(utils.toBuffer(tx.from), finalFromBalance, next) } - if (!results.vm.selfdestruct) { - results.vm.selfdestruct = {} + function updateMinerAccount (next) { + var minerAccount = self.stateManager.cache.get(block.header.coinbase) + // add the amount spent on gas to the miner's account + minerAccount.balance = new BN(minerAccount.balance) + .add(results.amountSpent) + + // save the miner's account + if (!(new BN(minerAccount.balance).isZero())) { + self.stateManager.cache.put(block.header.coinbase, minerAccount) + } + + next(null) } - var keys = Object.keys(results.vm.selfdestruct) - - keys.forEach(function (s) { - self.stateManager.cache.del(Buffer.from(s, 'hex')) - }) - - // delete all touched accounts - var touched = self.stateManager.touched - async.forEach(touched, function (address, next) { - self.stateManager.accountIsEmpty(address, function (err, empty) { - if (err) { - next(err) - return - } - - if (empty) { - self.stateManager.cache.del(address) - } - next(null) + function cleanupAccounts (next) { + if (!results.vm.selfdestruct) { + results.vm.selfdestruct = {} + } + + var keys = Object.keys(results.vm.selfdestruct) + + keys.forEach(function (s) { + self.stateManager.cache.del(Buffer.from(s, 'hex')) }) - }, - function () { - self.stateManager.touched = [] - cb() - }) + + self.stateManager.cleanupTouchedAccounts(next) + } } } diff --git a/lib/stateManager.js b/lib/stateManager.js index 4bd9b35d20..2d503e2d2f 100644 --- a/lib/stateManager.js +++ b/lib/stateManager.js @@ -28,7 +28,7 @@ function StateManager (opts) { self.trie = trie self._storageTries = {} // the storage trie cache self.cache = new Cache(trie) - self.touched = [] + self._touched = new Set() } var proto = StateManager.prototype @@ -47,14 +47,13 @@ proto.exists = function (address, cb) { } // saves the account -proto._putAccount = function (address, account, cb) { +proto.putAccount = function (address, account, cb) { var self = this - var addressHex = Buffer.from(address, 'hex') // TODO: dont save newly created accounts that have no balance // if (toAccount.balance.toString('hex') === '00') { // if they have money or a non-zero nonce or code, then write to tree - self.cache.put(addressHex, account) - self.touched.push(address) + self.cache.put(address, account) + self._touched.add(address.toString('hex')) // self.trie.put(addressHex, account.serialize(), cb) cb() } @@ -82,7 +81,7 @@ proto.putAccountBalance = function (address, balance, cb) { } account.balance = balance - self._putAccount(address, account, cb) + self.putAccount(address, account, cb) }) } @@ -98,7 +97,7 @@ proto.putContractCode = function (address, value, cb) { if (err) { return cb(err) } - self._putAccount(address, account, cb) + self.putAccount(address, account, cb) }) }) } @@ -180,8 +179,8 @@ proto.putContractStorage = function (address, key, value, cb) { // update contract stateRoot var contract = self.cache.get(address) contract.stateRoot = storageTrie.root - self._putAccount(address, contract, cb) - self.touched.push(address) + self.putAccount(address, contract, cb) + self._touched.add(address.toString('hex')) } }) } @@ -204,6 +203,7 @@ proto.commitContracts = function (cb) { proto.revertContracts = function () { var self = this self._storageTries = {} + self._touched.clear() } // @@ -325,3 +325,26 @@ proto.accountIsEmpty = function (address, cb) { cb(null, account.nonce.toString('hex') === '' && account.balance.toString('hex') === '' && account.codeHash.toString('hex') === utils.SHA3_NULL_S) }) } + +proto.cleanupTouchedAccounts = function (cb) { + var self = this + var touchedArray = Array.from(self._touched) + async.forEach(touchedArray, function (addressHex, next) { + var address = Buffer.from(addressHex, 'hex') + self.accountIsEmpty(address, function (err, empty) { + if (err) { + next(err) + return + } + + if (empty) { + self.cache.del(address) + } + next(null) + }) + }, + function () { + self._touched.clear() + cb() + }) +}