-
Notifications
You must be signed in to change notification settings - Fork 765
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
Add state tests for Istanbul #607
Changes from all commits
83e42d8
63c9032
d91f246
3f15631
b0d71f0
29f14fb
1b5538e
7776c06
c3e1ccf
b26f15c
b9ebe69
a29de25
18b34f9
57d7483
970ddbf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,7 +33,6 @@ export interface Env { | |
export interface RunResult { | ||
logs: any // TODO: define type for Log (each log: [Buffer(address), [Buffer(topic0), ...]]) | ||
returnValue?: Buffer | ||
gasRefund: BN | ||
/** | ||
* A map from the accounts that have self-destructed to the addresses to send their funds to | ||
*/ | ||
|
@@ -67,7 +66,6 @@ export default class EEI { | |
this._result = { | ||
logs: [], | ||
returnValue: undefined, | ||
gasRefund: new BN(0), | ||
selfdestruct: {}, | ||
} | ||
} | ||
|
@@ -90,17 +88,17 @@ export default class EEI { | |
* @param amount - Amount of gas refunded | ||
*/ | ||
refundGas(amount: BN): void { | ||
this._result.gasRefund.iadd(amount) | ||
this._evm._refund.iadd(amount) | ||
} | ||
|
||
/** | ||
* Reduces amount of gas to be refunded by a positive value. | ||
* @param amount - Amount to subtract from gas refunds | ||
*/ | ||
subRefund(amount: BN): void { | ||
this._result.gasRefund.isub(amount) | ||
if (this._result.gasRefund.ltn(0)) { | ||
this._result.gasRefund = new BN(0) | ||
this._evm._refund.isub(amount) | ||
if (this._evm._refund.ltn(0)) { | ||
this._evm._refund = new BN(0) | ||
trap(ERROR.REFUND_EXHAUSTED) | ||
} | ||
} | ||
|
@@ -163,10 +161,6 @@ export default class EEI { | |
* input data passed with the message call instruction or transaction. | ||
*/ | ||
getCallDataSize(): BN { | ||
if (this._env.callData.length === 1 && this._env.callData[0] === 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some tests were using I'm not sure why this condition was here? any ideas? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really weird. I checked in pyevm, and it doesn't have this logic either. |
||
return new BN(0) | ||
} | ||
|
||
return new BN(this._env.callData.length) | ||
} | ||
|
||
|
@@ -354,9 +348,7 @@ export default class EEI { | |
async _selfDestruct(toAddress: Buffer): Promise<void> { | ||
// only add to refund if this is the first selfdestruct for the address | ||
if (!this._result.selfdestruct[this._env.address.toString('hex')]) { | ||
this._result.gasRefund = this._result.gasRefund.addn( | ||
this._common.param('gasPrices', 'selfdestructRefund'), | ||
) | ||
this.refundGas(new BN(this._common.param('gasPrices', 'selfdestructRefund'))) | ||
} | ||
|
||
this._result.selfdestruct[this._env.address.toString('hex')] = toAddress | ||
|
@@ -491,11 +483,6 @@ export default class EEI { | |
this._result.logs = this._result.logs.concat(results.execResult.logs) | ||
} | ||
|
||
// add gasRefund | ||
if (results.execResult.gasRefund) { | ||
this._result.gasRefund = this._result.gasRefund.add(results.execResult.gasRefund) | ||
} | ||
|
||
// this should always be safe | ||
this.useGas(results.gasUsed) | ||
|
||
|
@@ -553,11 +540,6 @@ export default class EEI { | |
this._result.logs = this._result.logs.concat(results.execResult.logs) | ||
} | ||
|
||
// add gasRefund | ||
if (results.execResult.gasRefund) { | ||
this._result.gasRefund = this._result.gasRefund.add(results.execResult.gasRefund) | ||
} | ||
|
||
// this should always be safe | ||
this.useGas(results.gasUsed) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ import { | |
import Account from 'ethereumjs-account' | ||
import { ERROR, VmError } from '../exceptions' | ||
import PStateManager from '../state/promisified' | ||
import { getPrecompile, PrecompileFunc } from './precompiles' | ||
import { getPrecompile, PrecompileFunc, ripemdPrecompileAddress } from './precompiles' | ||
import TxContext from './txContext' | ||
import Message from './message' | ||
import EEI from './eei' | ||
|
@@ -61,14 +61,14 @@ export interface ExecResult { | |
* Array of logs that the contract emitted | ||
*/ | ||
logs?: any[] | ||
/** | ||
* Amount of gas to refund from deleting storage values | ||
*/ | ||
gasRefund?: BN | ||
/** | ||
* A map from the accounts that have self-destructed to the addresses to send their funds to | ||
*/ | ||
selfdestruct?: { [k: string]: Buffer } | ||
/** | ||
* Total amount of gas to be refunded from all nested calls. | ||
*/ | ||
gasRefund?: BN | ||
} | ||
|
||
export interface NewContractEvent { | ||
|
@@ -96,12 +96,17 @@ export default class EVM { | |
_state: PStateManager | ||
_tx: TxContext | ||
_block: any | ||
/** | ||
* Amount of gas to refund from deleting storage values | ||
*/ | ||
_refund: BN | ||
|
||
constructor(vm: any, txContext: TxContext, block: any) { | ||
this._vm = vm | ||
this._state = this._vm.pStateManager | ||
this._tx = txContext | ||
this._block = block | ||
this._refund = new BN(0) | ||
} | ||
|
||
/** | ||
|
@@ -120,20 +125,14 @@ export default class EVM { | |
} else { | ||
result = await this._executeCreate(message) | ||
} | ||
// TODO: Move `gasRefund` to a tx-level result object | ||
// instead of `ExecResult`. | ||
result.execResult.gasRefund = this._refund.clone() | ||
|
||
const err = result.execResult.exceptionError | ||
if (err) { | ||
result.execResult.logs = [] | ||
await this._state.revert() | ||
if (message.isCompiled) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we were re-touching any precompile in case of OOG. |
||
// Empty precompiled contracts need to be deleted even in case of OOG | ||
// because the bug in both Geth and Parity led to deleting RIPEMD precompiled in this case | ||
// see https://github.com/ethereum/go-ethereum/pull/3341/files#diff-2433aa143ee4772026454b8abd76b9dd | ||
// We mark the account as touched here, so that is can be removed among other touched empty accounts (after tx finalization) | ||
if (err.error === ERROR.OUT_OF_GAS) { | ||
await this._touchAccount(message.to) | ||
} | ||
} | ||
} else { | ||
await this._state.commit() | ||
} | ||
|
@@ -289,6 +288,7 @@ export default class EVM { | |
eei._result.selfdestruct = message.selfdestruct | ||
} | ||
|
||
const oldRefund = this._refund.clone() | ||
const interpreter = new Interpreter(this._vm, eei) | ||
const interpreterRes = await interpreter.run(message.code as Buffer, opts) | ||
|
||
|
@@ -303,9 +303,10 @@ export default class EVM { | |
result = { | ||
...result, | ||
logs: [], | ||
gasRefund: new BN(0), | ||
selfdestruct: {}, | ||
} | ||
// Revert gas refund if message failed | ||
this._refund = oldRefund | ||
} | ||
|
||
return { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import Common from 'ethereumjs-common' | |
import { genesisStateByName } from 'ethereumjs-common/dist/genesisStates' | ||
import Account from 'ethereumjs-account' | ||
import Cache from './cache' | ||
import { ripemdPrecompileAddress } from '../evm/precompiles' | ||
|
||
/** | ||
* Storage values of an account | ||
|
@@ -102,11 +103,22 @@ export default class StateManager { | |
// if (toAccount.balance.toString('hex') === '00') { | ||
// if they have money or a non-zero nonce or code, then write to tree | ||
this._cache.put(address, account) | ||
this._touched.add(address.toString('hex')) | ||
this.touchAccount(address) | ||
// self._trie.put(addressHex, account.serialize(), cb) | ||
cb() | ||
} | ||
|
||
/** | ||
* Marks an account as touched, according to the definition | ||
* in [EIP-158](https://github.com/ethereum/EIPs/issues/158). | ||
* This happens when the account is triggered for a state-changing | ||
* event. Touched accounts that are empty will be cleared | ||
* at the end of the tx. | ||
*/ | ||
touchAccount(address: Buffer): void { | ||
this._touched.add(address.toString('hex')) | ||
} | ||
|
||
/** | ||
* Adds `value` to the state trie as code, and sets `codeHash` on the account | ||
* corresponding to `address` to reference this. | ||
|
@@ -275,7 +287,7 @@ export default class StateManager { | |
const contract = this._cache.get(address) | ||
contract.stateRoot = storageTrie.root | ||
this.putAccount(address, contract, cb) | ||
this._touched.add(address.toString('hex')) | ||
this.touchAccount(address) | ||
}) | ||
}) | ||
} | ||
|
@@ -372,6 +384,13 @@ export default class StateManager { | |
if (!touched) { | ||
throw new Error('Reverting to invalid state checkpoint failed') | ||
} | ||
// Exceptional case due to consensus issue in Geth and Parity. | ||
// See [EIP-716](https://github.com/ethereum/EIPs/issues/716) for context. | ||
// The RIPEMD precompile has to remain *touched* even when the call reverts, | ||
// and be considered for deletion. | ||
if (this._touched.has(ripemdPrecompileAddress)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved the re-touching of precompiles in case of OOG here (not a strong preference, I could re-move it to evm.ts). It is also limited to the precompile at |
||
touched.add(ripemdPrecompileAddress) | ||
} | ||
this._touched = touched | ||
this._checkpointCount-- | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,8 +18,10 @@ | |
"testStateByzantium": "npm run build:dist && node ./tests/tester -s --fork='Byzantium' --dist", | ||
"testStateConstantinople": "npm run build:dist && node ./tests/tester -s --fork='Constantinople' --dist", | ||
"testStatePetersburg": "npm run build:dist && node ./tests/tester -s --fork='Petersburg' --dist", | ||
"testStateIstanbul": "npm run build:dist && node ./tests/tester -s --fork='Istanbul' --dist", | ||
"testBuildIntegrity": "npm run build:dist && node ./tests/tester -s --dist --test='stackOverflow'", | ||
"testBlockchain": "npm run build:dist && node --stack-size=1500 ./tests/tester -b --fork='Petersburg' --dist --excludeDir='GeneralStateTests'", | ||
"testBlockchain": "npm run build:dist && node --stack-size=1500 ./tests/tester -b --fork='Istanbul' --dist --excludeDir='GeneralStateTests'", | ||
"testBlockchainPetersburg": "npm run build:dist && node --stack-size=1500 ./tests/tester -b --fork='Petersburg' --dist --excludeDir='GeneralStateTests'", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious about this line. Weren't we testing some of the HF in the CI? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are always only testing the latest or eventually the latest two HFs on CI, otherwise this would blow CI time (this nevertheless improved recently and e.g. allowed to add the blockchain tests beneath the state tests at all, before only state tests were running). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, thanks for the reply. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On second thought, maybe we can cron a daily full test run on master using https://circleci.com/docs/2.0/configuration-reference/#schedule I use the travis equivalent for Buidler, and lets me catch errors introduced by dependencies asap. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we're running the state tests for all HFs from Byzantium, but the blockchain tests were being run only for the latest HF because of their runtime, which was improved in #536. So I guess now we could run the blockchain tests for maybe 2-3 HFs. Scheduling cron jobs is a good idea, I like it. We could also run run slow tests (#472) as a cron job. The VM's testing infrastructure could generally use some improvements. |
||
"testBlockchainGeneralStateTests": "npm run build:dist && node --stack-size=1500 ./tests/tester -b --dist --dir='GeneralStateTests'", | ||
"testAPI": "npm run build:dist && tape './tests/api/**/*.js'", | ||
"testAPI:browser": "npm run build:dist && karma start karma.conf.js", | ||
|
@@ -51,11 +53,11 @@ | |
"async-eventemitter": "^0.2.2", | ||
"core-js-pure": "^3.0.1", | ||
"ethereumjs-account": "^3.0.0", | ||
"ethereumjs-block": "~2.2.0", | ||
"ethereumjs-blockchain": "^4.0.1", | ||
"ethereumjs-block": "^2.2.1", | ||
"ethereumjs-blockchain": "^4.0.2", | ||
"ethereumjs-common": "^1.3.2", | ||
"ethereumjs-tx": "^2.1.1", | ||
"ethereumjs-util": "^6.1.0", | ||
"ethereumjs-util": "~6.1.0", | ||
"fake-merkle-patricia-tree": "^1.0.1", | ||
"functional-red-black-tree": "^1.0.1", | ||
"merkle-patricia-tree": "^2.3.2", | ||
|
@@ -74,7 +76,7 @@ | |
"@types/node": "^11.13.4", | ||
"browserify": "^16.2.3", | ||
"coveralls": "^3.0.0", | ||
"ethereumjs-testing": "git+https://github.com/ethereumjs/ethereumjs-testing.git#v1.2.7", | ||
"ethereumjs-testing": "git+https://github.com/ethereumjs/ethereumjs-testing.git#v1.3.0", | ||
"husky": "^2.1.0", | ||
"karma": "^4.0.1", | ||
"karma-browserify": "^6.0.0", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can do
this._result.gasRefund = this._evm._refund
after modifying it. It clutters the code a little, but it avoids a breaking change.As David, said Ganache only depends on
gasRefund
at the end of every tx, so this would work.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I re-added
gasRefund
toExecResult
, by copyingEVM._refund
at the end of message execution in EVM. This should work for the Ganache use-case. I think we should be backwards-compatible now, at least for the most part.