Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
holgerd77 authored and holgerd77 committed Jul 28, 2017
2 parents 29004b6 + 9fc6e38 commit b487c8d
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 52 deletions.
41 changes: 33 additions & 8 deletions README.md
Expand Up @@ -68,7 +68,7 @@ Process a transaction.

#### `vm.runBlock(opts, cb)`
Processes the `block` running all of the transactions it contains and updating the miner's account.
- `opts.block` - The [`Block`](./block.md) to process
- `opts.block` - The [`Block`](https://github.com/ethereumjs/ethereumjs-block) to process
- `opts.generate` - a `Boolean`; whether to generate the stateRoot. If false `runBlock` will check the stateRoot of the block against the Trie
- `cb` - The callback. It is given two arguments, an `error` string containing an error that may have happened or `null`, and a `results` object with the following properties:
- `receipts` - the receipts from the transactions in the block
Expand All @@ -92,17 +92,17 @@ Runs EVM code
- `opts.code` - The EVM code to run given as a `Buffer`
- `opts.data` - The input data given as a `Buffer`
- `opts.value` - The value in ether that is being sent to `opt.address`. Defaults to `0`
- `opts.block` - The [`Block`](./block.md) the `tx` belongs to. If omitted a blank block will be used.
- `opts.block` - The [`Block`](https://github.com/ethereumjs/ethereumjs-block) the `tx` belongs to. If omitted a blank block will be used.
- `opts.gasLimit` - The gas limit for the code given as a `Buffer`
- `opts.account` - The [`Account`](./account.md) that the executing code belongs to. If omitted an empty account will be used
- `opts.account` - The [`Account`](https://github.com/ethereumjs/ethereumjs-account) that the executing code belongs to. If omitted an empty account will be used
- `opts.address` - The address of the account that is executing this code. The address should be a `Buffer` of bytes. Defaults to `0`
- `opts.origin` - The address where the call originated from. The address should be a `Buffer` of 20bits. Defaults to `0`
- `opts.caller` - The address that ran this code. The address should be a `Buffer` of 20bits. Defaults to `0`
- `cb` - The callback. It is given two arguments, an `error` string containing an error that may have happened or `null` and a `results` object with the following properties
- `gas` - the amount of gas left as a `bignum`
- `gasUsed` - the amount of gas as a `bignum` the code used to run.
- `gasRefund` - a `Bignum` containing the amount of gas to refund from deleting storage values
- `suicides` - an `Object` with keys for accounts that have suicided and values for balance transfer recipient accounts.
- `selfdestruct` - an `Object` with keys for accounts that have selfdestructed and values for balance transfer recipient accounts.
- `logs` - an `Array` of logs that the contract emitted.
- `exception` - `0` if the contract encountered an exception, `1` otherwise.
- `exceptionError` - a `String` describing the exception if there was one.
Expand Down 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
78 changes: 48 additions & 30 deletions lib/opFns.js
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 @@ -674,40 +682,50 @@ module.exports = {
runState.returnValue = memLoad(runState, offset, length)
},
// '0x70', range - other
SUICIDE: function (suicideToAddress, runState, cb) {
SELFDESTRUCT: function (selfdestructToAddress, runState, cb) {
var stateManager = runState.stateManager
var contract = runState.contract
var contractAddress = runState.address
suicideToAddress = utils.setLengthLeft(suicideToAddress, 20)
var zeroBalance = new BN(0)
selfdestructToAddress = utils.setLengthLeft(selfdestructToAddress, 20)

stateManager.getAccount(suicideToAddress, function (err, toAccount) {
stateManager.getAccount(selfdestructToAddress, function (err, toAccount) {
// update balances
if (err) {
cb(err)
return
}

if (!toAccount.exists) {
try {
subGas(runState, new BN(fees.callNewAccountGas.v))
} catch (e) {
cb(e.error)
stateManager.accountIsEmpty(selfdestructToAddress, 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 selfdestruct for the address
if (!runState.selfdestruct[contractAddress.toString('hex')]) {
runState.gasRefund = runState.gasRefund.add(new BN(fees.suicideRefundGas.v))
}
runState.selfdestruct[contractAddress.toString('hex')] = selfdestructToAddress
runState.stopped = true

var newBalance = new Buffer(new BN(contract.balance).add(new BN(toAccount.balance)).toArray())
async.series([
stateManager.putAccountBalance.bind(stateManager, selfdestructToAddress, newBalance),
stateManager.putAccountBalance.bind(stateManager, contractAddress, new BN(0))
], cb)
})
})
}
}
Expand Down Expand Up @@ -843,7 +861,7 @@ function makeCall (runState, callOptions, localOpts, cb) {
callOptions.gasPrice = runState.gasPrice
callOptions.block = runState.block
callOptions.populateCache = false
callOptions.suicides = runState.suicides
callOptions.selfdestruct = runState.selfdestruct

// increment the runState.depth
callOptions.depth = runState.depth + 1
Expand Down
2 changes: 1 addition & 1 deletion lib/opcodes.js
Expand Up @@ -149,7 +149,7 @@ const codes = {
0xf4: ['DELEGATECALL', 700, 6, 1, true],

// '0x70', range - other
0xff: ['SUICIDE', 5000, 1, 0, false]
0xff: ['SELFDESTRUCT', 5000, 1, 0, false]
}

module.exports = function (op, full) {
Expand Down
8 changes: 6 additions & 2 deletions lib/runCall.js
Expand Up @@ -41,7 +41,8 @@ module.exports = function (opts, cb) {
var origin = opts.origin
var isCompiled = opts.compiled
var depth = opts.depth
var suicides = opts.suicides
// opts.suicides is kept for backward compatiblity with pre-EIP6 syntax
var selfdestruct = opts.selfdestruct || opts.suicides
var delegatecall = opts.delegatecall || false

txValue = new BN(txValue)
Expand Down Expand Up @@ -69,6 +70,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 +96,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 Expand Up @@ -136,7 +140,7 @@ module.exports = function (opts, cb) {
value: new Buffer(txValue.toArray()),
block: block,
depth: depth,
suicides: suicides,
selfdestruct: selfdestruct,
populateCache: false
}

Expand Down
8 changes: 5 additions & 3 deletions lib/runCode.js
Expand Up @@ -62,7 +62,8 @@ module.exports = function (opts, cb) {
gasRefund: new BN(0),
highestMemCost: new BN(0),
depth: opts.depth || 0,
suicides: opts.suicides || {},
// opts.suicides is kept for backward compatiblity with pre-EIP6 syntax
selfdestruct: opts.selfdestruct || opts.suicides || {},
block: block,
callValue: opts.value || new BN(0),
address: opts.address || utils.zeros(32),
Expand Down Expand Up @@ -212,7 +213,7 @@ module.exports = function (opts, cb) {

var results = {
runState: runState,
suicides: runState.suicides,
selfdestruct: runState.selfdestruct,
gasRefund: runState.gasRefund,
exception: err ? 0 : 1,
exceptionError: err,
Expand All @@ -223,7 +224,8 @@ module.exports = function (opts, cb) {

if (results.exceptionError) {
delete results.gasRefund
delete results.suicides
delete results.selfdestruct
self.stateManager.touched = []
}

if (err) {
Expand Down
33 changes: 28 additions & 5 deletions lib/runTx.js
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,26 +158,47 @@ 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 = {}
if (!results.vm.selfdestruct) {
results.vm.selfdestruct = {}
}

var keys = Object.keys(results.vm.suicides)
var keys = Object.keys(results.vm.selfdestruct)

keys.forEach(function (s) {
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

0 comments on commit b487c8d

Please sign in to comment.