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

[VM] update VM test runner #849

Merged
merged 6 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
98 changes: 74 additions & 24 deletions .github/workflows/vm-pr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: VM Nightly
name: VM
on:
pull_request:
types: [opened, reopened, synchronize]
Expand All @@ -19,29 +19,40 @@ jobs:
node-version: 12.x
- uses: actions/checkout@v1

# This is important, so the CI runs with a fresh combination of packages
- run: rm package-lock.json packages/*/package-lock.json
working-directory: ${{github.workspace}}

- name: Dependency cache
uses: actions/cache@v2
id: cache
with:
key: VM-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
key: VM-${{ runner.os }}-12-${{ hashFiles('**/package-lock.json') }}
path: '**/node_modules'

- run: npm install
# Installs root dependencies, ignoring Bootstrap All script.
# Bootstraps the current package only
- run: npm install --ignore-scripts && npm run bootstrap:vm
if: steps.cache.outputs.cache-hit != 'true'
working-directory: ${{github.workspace}}

- run: npm run build
# Builds current package and the ones it depends from.
- run: npm run build:vm
working-directory: ${{github.workspace}}

- run: npm run coverage

- uses: codecov/codecov-action@v1
with:
file: ${{ env.cwd }}/coverage/lcov.info
flags: vm

- run: npm run test:API
- run: npm run test:API:browser

- run: npm run lint

test-vm-state:
runs-on: ubuntu-latest
strategy:
matrix:
fork: ['Berlin', 'MuirGlacier', 'Petersburg', 'Istanbul', 'Byzantium', 'SpuriousDragon', 'TangerineWhistle', 'Homestead', 'Chainstart']
fail-fast: false
steps:
- uses: actions/setup-node@v1
with:
Expand All @@ -52,20 +63,28 @@ jobs:
uses: actions/cache@v2
id: cache
with:
key: VM-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
key: VM-${{ runner.os }}-12-${{ hashFiles('**/package-lock.json') }}
path: '**/node_modules'

- run: npm install
# Installs root dependencies, ignoring Bootstrap All script.
# Bootstraps the current package only
- run: npm install --ignore-scripts && npm run bootstrap:vm
if: steps.cache.outputs.cache-hit != 'true'
working-directory: ${{github.workspace}}

- run: npm run build
# Builds current package and the ones it depends from.
- run: npm run build:vm
working-directory: ${{github.workspace}}

- run: npm run test:state:allForks
- run: npm run test:state -- --fork=${{ matrix.fork }} --verify-test-amount-alltests

test-vm-blockchain:
runs-on: ubuntu-latest
strategy:
matrix:
# Args to pass to the tester. Note that some have splitted the slow tests and only running those: these are only on forks where that is applicable (see PR #489 for numbers on these)
args: ['--fork=Chainstart --expected-test-amount=4385', '--fork=Homestead --expected-test-amount=6997', '--fork=Petersburg --excludeDir=stTimeConsuming --expected-test-amount=17174', '--fork=Petersburg --dir=GeneralStateTests/stTimeConsuming --expected-test-amount=15561', '--fork=Istanbul --excludeDir=stTimeConsuming --expected-test-amount=19817', '--fork=Istanbul --dir=GeneralStateTests/stTimeConsuming --expected-test-amount=15561', '--fork=Berlin --expected-test-amount=33']
fail-fast: false
steps:
- uses: actions/setup-node@v1
with:
Expand All @@ -76,26 +95,28 @@ jobs:
uses: actions/cache@v2
id: cache
with:
key: VM-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
key: VM-${{ runner.os }}-12-${{ hashFiles('**/package-lock.json') }}
path: '**/node_modules'

- run: npm install
# Installs root dependencies, ignoring Bootstrap All script.
# Bootstraps the current package only
- run: npm install --ignore-scripts && npm run bootstrap:vm
if: steps.cache.outputs.cache-hit != 'true'
working-directory: ${{github.workspace}}

- run: npm run build
# Builds current package and the ones it depends from.
- run: npm run build:vm
working-directory: ${{github.workspace}}

- run: npm run test:blockchain:allForks
working-directory: '${{ env.cwd }}'
- run: npm run test:blockchain -- ${{ matrix.args }}

test-vm-slow:
vm-benchmarks:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: 12.x
- uses: actions/checkout@v1
- uses: actions/checkout@v2

- name: Dependency cache
uses: actions/cache@v2
Expand All @@ -104,11 +125,40 @@ jobs:
key: VM-${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
path: '**/node_modules'

- run: npm install
# Installs root dependencies, ignoring Bootstrap All script.
# Bootstraps the current package only
- run: npm install --ignore-scripts && npm run bootstrap:vm
if: steps.cache.outputs.cache-hit != 'true'
working-directory: ${{github.workspace}}

- run: npm run build
# Builds current package and the ones it depends from.
- run: npm run build:vm
working-directory: ${{github.workspace}}

- run: npm run test:state:slow

- run: npm run build:benchmarks
working-directory: ${{ env.cwd }}

- run: npm run benchmarks -- 10 | tee output.txt
working-directory: ${{ env.cwd }}

# Run git stash in case github-action-benchmark has trouble switching to gh-pages branch due to differing package-locks
- run: git stash

- name: Compare benchmarks
uses: rhysd/github-action-benchmark@v1
with:
tool: 'benchmarkjs'
# Where the output from the benchmark tool is stored
output-file-path: ${{ env.cwd }}/output.txt
# Enable alert commit comment
comment-on-alert: true
# Always leave a commit comment comparing the current benchmark with previous
comment-always: true
# GitHub API token to make a commit comment
github-token: ${{ secrets.GITHUB_TOKEN }}
# Push and deploy to GitHub pages branch automatically (if on master)
auto-push: 'false'

# Re-apply git stash to prepare for saving back to cache.
# Avoids exit code 1 by checking if there are changes to be stashed first
- run: STASH_LIST=`git stash list` && [ ! -z $STASH_LIST ] && git stash apply || echo "No files to stash-apply. Skipping…"
6 changes: 3 additions & 3 deletions packages/vm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
"coverage:test": "tape './tests/api/**/*.js' ./tests/tester.js --state --dist",
"docs:build": "typedoc --options typedoc.js",
"test:state": "ts-node ./tests/tester --state",
"test:state:allForks": "echo 'Chainstart Homestead dao TangerineWhistle SpuriousDragon Byzantium Constantinople Petersburg Istanbul MuirGlacier Berlin ByzantiumToConstantinopleFixAt5 EIP158ToByzantiumAt5 FrontierToHomesteadAt5 HomesteadToDaoAt5 HomesteadToEIP150At5' | xargs -n1 | xargs -I v1 npm run test:state -- --fork=v1",
"test:state:selectedForks": "echo 'Homestead TangerineWhistle SpuriousDragon Petersburg' | xargs -n1 | xargs -I v1 npm run test:state -- --fork=v1",
"test:state:allForks": "echo 'Chainstart Homestead dao TangerineWhistle SpuriousDragon Byzantium Constantinople Petersburg Istanbul MuirGlacier Berlin ByzantiumToConstantinopleFixAt5 EIP158ToByzantiumAt5 FrontierToHomesteadAt5 HomesteadToDaoAt5 HomesteadToEIP150At5' | xargs -n1 | xargs -I v1 npm run test:state -- --fork=v1 --verify-test-amount-alltests",
"test:state:selectedForks": "echo 'Homestead TangerineWhistle SpuriousDragon Petersburg' | xargs -n1 | xargs -I v1 npm run test:state -- --fork=v1 --verify-test-amount-alltests",
"test:state:slow": "npm run test:state -- --runSkipped=slow",
"test:buildIntegrity": "npm run test:state -- --test='stackOverflow'",
"test:blockchain": "node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain",
"test:blockchain:allForks": "echo 'Chainstart Homestead dao TangerineWhistle SpuriousDragon Byzantium Constantinople Petersburg Istanbul MuirGlacier Berlin ByzantiumToConstantinopleFixAt5 EIP158ToByzantiumAt5 FrontierToHomesteadAt5 HomesteadToDaoAt5 HomesteadToEIP150At5' | xargs -n1 | xargs -I v1 node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=v1",
"test:blockchain:allForks": "echo 'Chainstart Homestead dao TangerineWhistle SpuriousDragon Byzantium Constantinople Petersburg Istanbul MuirGlacier Berlin ByzantiumToConstantinopleFixAt5 EIP158ToByzantiumAt5 FrontierToHomesteadAt5 HomesteadToDaoAt5 HomesteadToEIP150At5' | xargs -n1 | xargs -I v1 node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=v1 --verify-test-amount-alltests",
"test:API": "tape -r ts-node/register --stack-size=1500 './tests/api/**/*.js'",
"test:API:browser": "npm run build && karma start karma.conf.js",
"test": "echo \"[INFO] Generic test cmd not used. See package.json for more specific test run cmds.\"",
Expand Down
51 changes: 31 additions & 20 deletions packages/vm/tests/BlockchainTestsRunner.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { setupPreConditions, verifyPostConditions, getDAOCommon } = require('./util.js')
const { addHexPrefix } = require('ethereumjs-util')
const { addHexPrefix, rlp, bufferToInt } = require('ethereumjs-util')
const Trie = require('merkle-patricia-tree').SecureTrie
const { Block, BlockHeader } = require('@ethereumjs/block')
const Blockchain = require('@ethereumjs/blockchain').default
Expand Down Expand Up @@ -39,6 +39,7 @@ module.exports = async function runBlockchainTest(options, testData, t) {
}

const { common } = options
common.setHardforkByBlockNumber(0)

const blockchain = new Blockchain({
db: blockchainDB,
Expand Down Expand Up @@ -86,7 +87,7 @@ module.exports = async function runBlockchainTest(options, testData, t) {

await blockchain.putGenesis(genesisBlock)

async function handleError(error, expectException, cacheDB) {
async function handleError(error, expectException) {
if (expectException) {
t.pass(`Expected exception ${expectException}`)
} else {
Expand All @@ -95,25 +96,41 @@ module.exports = async function runBlockchainTest(options, testData, t) {
}
}

const numBlocks = testData.blocks.length
let currentBlock = 0
let lastBlock = false
let currentFork = common.hardfork()
let currentBlock
let lastBlock = 0
for (const raw of testData.blocks) {
currentBlock++
lastBlock = (currentBlock == numBlocks)
lastBlock = currentBlock
const paramFork = `expectException${options.forkConfigTestSuite}`
// Two naming conventions in ethereum/tests to indicate "exception occurs on all HFs" semantics
// Last checked: ethereumjs-testing v1.3.1 (2020-05-11)
const paramAll1 = 'expectExceptionALL'
const paramAll2 = 'expectException'
const expectException = raw[paramFork] ? raw[paramFork] : raw[paramAll1] || raw[paramAll2] || raw.blockHeader == undefined

// here we convert the rlp to block only to extract the number
// we have to do this again later because the common might run on a new hardfork
try {
let block = new Block(Buffer.from(raw.rlp.slice(2), 'hex'), {
common
})
currentBlock = bufferToInt(block.header.number)
} catch(e) {
handleError(e, expectException)
continue
}

if (currentBlock < lastBlock) {
// "re-org": rollback the blockchain to currentBlock (i.e. delete that block number in the blockchain plus the children)
t.fail("re-orgs are not supported by the test suite")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not completely getting this re-org scenario: is this actually triggered somewhere in the tests? Then some of the tests should fail according to the code here I would assume? Or is this a "just in case" implementation? 😛

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that if you disable this, most tests pass (see the skipped tests which have a few tests which are added), but not all of these blocks are evaluated (these are the re-org blocks). So the test is only executed partially. There is only one test which actually fails due to this. I just don't like that tests are only partially executed. One of the features of these tests seems to be that it does this reorg stuff, but we are not testing this, so I do not think we should just allow the tests and make them pass while we know that they are only partially executed.

It is rather cumbersome that these tests setup the canonical chain, then do some reorg, and then basically "reorg back" to the initial canonical chain, which means that the tip block of the chain in our implementation is also the tip block which is expected by the test suite, which is also the reason why these tests pass. The blocks in the reorg are not evaluated. If not clear let me know 😄 Related issue which tracks this problem: #879

return
}
try {

// check if we should update common.
if (common.isNextHardforkBlock(currentBlock)) {
const activeHardforks = common.activeHardforks(currentBlock)
const hardforkName = activeHardforks[activeHardforks.length - 1].name
common.setHardfork(hardforkName)
// create a new VM (need access to new opcodes)
let newFork = common.setHardforkByBlockNumber(currentBlock)
if (newFork != currentFork) {
currentFork = newFork
vm._updateOpcodes()
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified with Common.setHardforkByBlockNumber()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah there is some weird stuff going on over here, because if I re-instantiate a VM after I've updated Common (assuming that is what you mean here) then suddenly a lot more tests start failing. This used to be the way how most blockchain/state tests passed. 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth investigating why this happens though as it should definitely work if we re-instantiate the VM with a correctly-set Common 👍

Expand All @@ -127,15 +144,9 @@ module.exports = async function runBlockchainTest(options, testData, t) {
// blockchain tests come with their own `pre` world state.
// TODO: Add option to `runBlockchain` not to generate genesis state.
vm._common.genesis().stateRoot = vm.stateManager._trie.root

await vm.runBlockchain()

const headBlock = await vm.blockchain.getHead()

if (expectException !== undefined && lastBlock) { // only check last block hash on last block
t.equal(headBlock.hash().toString('hex'), testData.lastblockhash, 'last block hash')
}

// if the test fails, then block.header is the prej because
// vm.runBlock has a check that prevents the actual postState from being
// imported if it is not equal to the expected postState. it is useful
Expand All @@ -159,13 +170,13 @@ module.exports = async function runBlockchainTest(options, testData, t) {
} catch (error) {
// caught an error, reduce block number
currentBlock--
await handleError(error, expectException, cacheDB)
await handleError(error, expectException)
}
}
t.equal(
blockchain.meta.rawHead.toString('hex'),
testData.lastblockhash,
'correct header block',
'correct last header block',
)
await cacheDB.close()
}
Expand Down
Loading