Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spurious Dragon Working Branch #147

Merged
merged 16 commits into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,23 +162,48 @@ Emits the result of the transaction.

# TESTING

### Running Tests

_Note: Requires at least Node.js `8.0.0` installed to run the tests, this is because `ethereumjs-testing` uses `async/await` and other ES2015 language features_

Tests can be found in the ``tests`` directory, with ``FORK_CONFIG`` set in ``tests/tester.js``.

There are test runners for [State tests](http://www.ethdocs.org/en/latest/contracts-and-transactions/ethereum-tests/state_tests/index.html) and [Blockchain tests](http://www.ethdocs.org/en/latest/contracts-and-transactions/ethereum-tests/blockchain_tests/index.html). VM tests are disabled since Frontier gas costs are not supported any more.

Tests are then executed by the [ethereumjs-testing](https://github.com/ethereumjs/ethereumjs-testing) utility library using the official client-independent [Ethereum tests](https://github.com/ethereum/tests).

Running all the tests:

`npm test`
if you want to just run the State tests run

Running the State tests:

`node ./tests/tester -s`
if you want to just run the Blockchain tests run

Running the Blockchain tests:

`node ./tests/tester -b`

To run the all the blockchain tests in a file:
State tests run significantly faster than Blockchain tests, so it is often a good choice to start fixing State tests.

Running all the blockchain tests in a file:

`node ./tests/tester -b --file='randomStatetest303'`

To run a specific state test case:
Running a specific state test case:

`node ./tests/tester -s --test='stackOverflow'`

### Debugging

Blockchain tests support `--debug` to verify the postState:

`node ./tests/tester -b --debug --test='ZeroValue_SELFDESTRUCT_ToOneStorageKey_OOGRevert_d0g0v0_EIP158'`

All/most State tests are replicated as Blockchain tests in a ``GeneralStateTests`` [sub directory](https://github.com/ethereum/tests/tree/develop/BlockchainTests/GeneralStateTests) in the Ethereum tests repo, so for debugging single test cases the Blockchain test version of the State test can be used.

For comparing ``EVM`` traces [here](https://gist.github.com/cdetrio/41172f374ae32047a6c9e97fa9d09ad0) are some instructions for setting up ``pyethereum`` to generate corresponding traces for state tests.

# Internal Structure
The VM processes state changes at many levels.

Expand Down
70 changes: 44 additions & 26 deletions lib/opFns.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module.exports = {
DIV: function (a, b, runState) {
a = new BN(a)
b = new BN(b)

var r
if (b.isZero()) {
r = [0]
Expand All @@ -61,7 +62,6 @@ module.exports = {
a = new BN(a)
b = new BN(b)
var r

if (b.isZero()) {
r = [0]
} else {
Expand Down Expand Up @@ -550,14 +550,22 @@ module.exports = {
return
}

if (!exists) {
try {
subGas(runState, new BN(fees.callNewAccountGas.v))
} catch (e) {
done(e.error)
return
stateManager.accountIsEmpty(toAddress, function (err, empty) {
if (err) {
done(err)
}
}

if (!exists || empty) {
if (!value.isZero()) {
try {
subGas(runState, new BN(fees.callNewAccountGas.v))
} catch (e) {
done(e.error)
return
}
}
}
})

try {
checkCallMemCost(runState, options, localOpts)
Expand Down Expand Up @@ -678,6 +686,7 @@ module.exports = {
var stateManager = runState.stateManager
var contract = runState.contract
var contractAddress = runState.address
var zeroBalance = new BN(0)
suicideToAddress = utils.setLengthLeft(suicideToAddress, 20)

stateManager.getAccount(suicideToAddress, function (err, toAccount) {
Expand All @@ -687,27 +696,36 @@ module.exports = {
return
}

if (!toAccount.exists) {
try {
subGas(runState, new BN(fees.callNewAccountGas.v))
} catch (e) {
cb(e.error)
stateManager.accountIsEmpty(suicideToAddress, function (error, empty) {
if (error) {
cb(error)
return
}
}

// only add to refund if this is the first suicide for the address
if (!runState.suicides[contractAddress.toString('hex')]) {
runState.gasRefund = runState.gasRefund.add(new BN(fees.suicideRefundGas.v))
}
runState.suicides[contractAddress.toString('hex')] = suicideToAddress
runState.stopped = true

var newBalance = new Buffer(new BN(contract.balance).add(new BN(toAccount.balance)).toArray())
async.series([
stateManager.putAccountBalance.bind(stateManager, suicideToAddress, newBalance),
stateManager.putAccountBalance.bind(stateManager, contractAddress, new BN(0))
], cb)
if ((new BN(contract.balance)).gt(zeroBalance)) {
if (!toAccount.exists || empty) {
try {
subGas(runState, new BN(fees.callNewAccountGas.v))
} catch (e) {
cb(e.error)
return
}
}
}

// only add to refund if this is the first suicide for the address
if (!runState.suicides[contractAddress.toString('hex')]) {
runState.gasRefund = runState.gasRefund.add(new BN(fees.suicideRefundGas.v))
}
runState.suicides[contractAddress.toString('hex')] = suicideToAddress
runState.stopped = true

var newBalance = new Buffer(new BN(contract.balance).add(new BN(toAccount.balance)).toArray())
async.series([
stateManager.putAccountBalance.bind(stateManager, suicideToAddress, newBalance),
stateManager.putAccountBalance.bind(stateManager, contractAddress, new BN(0))
], cb)
})
})
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/runCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ module.exports = function (opts, cb) {
createdAddress = toAddress = ethUtil.generateAddress(caller, newNonce.toArray())
stateManager.getAccount(createdAddress, function (err, account) {
toAccount = account
const NONCE_OFFSET = 1
toAccount.nonce = new BN(toAccount.nonce).addn(NONCE_OFFSET).toBuffer()
done(err)
})
} else {
Expand All @@ -93,6 +95,7 @@ module.exports = function (opts, cb) {
// 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)
}

function loadCode (cb) {
Expand Down
1 change: 1 addition & 0 deletions lib/runCode.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ module.exports = function (opts, cb) {
if (results.exceptionError) {
delete results.gasRefund
delete results.suicides
self.stateManager.touched = []
}

if (err) {
Expand Down
27 changes: 25 additions & 2 deletions lib/runTx.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ module.exports = function (opts, cb) {
accounts.add(tx.to.toString('hex'))
accounts.add(block.header.coinbase.toString('hex'))

self.stateManager.touched.push(tx.to)

if (opts.populateCache === false) {
return cb()
}
Expand Down Expand Up @@ -156,14 +158,17 @@ module.exports = function (opts, cb) {
.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
self.stateManager.cache.put(block.header.coinbase, minerAccount)
if (!(new BN(minerAccount.balance).isZero())) {
self.stateManager.cache.put(block.header.coinbase, minerAccount)
}

if (!results.vm.suicides) {
results.vm.suicides = {}
Expand All @@ -175,7 +180,25 @@ module.exports = function (opts, cb) {
self.stateManager.cache.del(new Buffer(s, 'hex'))
})

cb()
// 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 () {
self.stateManager.touched = []
cb()
})
}
}

Expand Down
21 changes: 21 additions & 0 deletions lib/stateManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function StateManager (opts) {
self.trie = trie
self._storageTries = {} // the storage trie cache
self.cache = new Cache(trie)
self.touched = []
}

var proto = StateManager.prototype
Expand All @@ -52,6 +53,7 @@ proto._putAccount = function (address, account, cb) {
// 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.trie.put(addressHex, account.serialize(), cb)
cb()
}
Expand All @@ -68,10 +70,16 @@ proto.getAccountBalance = function (address, cb) {

proto.putAccountBalance = function (address, balance, cb) {
var self = this

self.getAccount(address, function (err, account) {
if (err) {
return cb(err)
}

if ((new BN(balance)).isZero() && !account.exists) {
return cb(null)
}

account.balance = balance
self._putAccount(address, account, cb)
})
Expand Down Expand Up @@ -115,6 +123,7 @@ proto._lookupStorageTrie = function (address, cb) {
}
var storageTrie = self.trie.copy()
storageTrie.root = account.stateRoot
self.touched.push(address)
storageTrie._checkpoints = []
cb(null, storageTrie)
})
Expand Down Expand Up @@ -172,6 +181,7 @@ proto.putContractStorage = function (address, key, value, cb) {
var contract = self.cache.get(address)
contract.stateRoot = storageTrie.root
self._putAccount(address, contract, cb)
self.touched.push(address)
}
})
}
Expand Down Expand Up @@ -304,3 +314,14 @@ proto.generateGenesis = function (initState, cb) {
self.trie.put(address, account.serialize(), done)
}, cb)
}

proto.accountIsEmpty = function (address, cb) {
var self = this
self.getAccount(address, function (err, account) {
if (err) {
return cb(err)
}

cb(null, account.nonce.toString('hex') === '' && account.balance.toString('hex') === '' && account.codeHash.toString('hex') === utils.SHA3_NULL_S)
})
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dependencies": {
"async": "^2.1.2",
"async-eventemitter": "^0.2.2",
"ethereum-common": "0.0.18",
"ethereum-common": "0.1.0",
"ethereumjs-account": "^2.0.3",
"ethereumjs-block": "^1.2.2",
"ethereumjs-util": "4.5.0",
Expand All @@ -19,7 +19,7 @@
"babelify": "^7.3.0",
"ethereumjs-blockchain": "^1.4.1",
"ethereumjs-testing": "https://github.com/ethereumjs/ethereumjs-testing",
"ethereumjs-tx": "1.1.4",
"ethereumjs-tx": "1.3.3",
"level": "^1.4.0",
"leveldown": "^1.4.6",
"levelup": "^1.3.2",
Expand Down
2 changes: 1 addition & 1 deletion tests/tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const argv = require('minimist')(process.argv.slice(2))
const async = require('async')
const tape = require('tape')
const testing = require('ethereumjs-testing')
const FORK_CONFIG = argv.fork || 'EIP150'
const FORK_CONFIG = argv.fork || 'EIP158'
const skip = [
'CreateHashCollision', // impossible hash collision on generating address
'SuicidesMixingCoinbase', // sucides to the coinbase, since we run a blockLevel we create coinbase account.
Expand Down