-
Notifications
You must be signed in to change notification settings - Fork 839
Stateless execution prototype #556
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
Changes from 14 commits
bb85e06
86dfb24
a407fb8
b539607
7421331
236b825
2f50243
0ddb845
b1208da
92bf49d
570cba8
3809733
9572f57
fc13bb5
a383522
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 |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| const ethUtil = require('ethereumjs-util') | ||
| const BN = ethUtil.BN | ||
| const { getRequiredForkConfigAlias, setupPreConditions, makeTx, makeBlockFromEnv } = require('./util') | ||
| const Account = require('@ethereumjs/account').default | ||
| const Trie = require('merkle-patricia-tree').SecureTrie | ||
| const { default: Common } = require('@ethereumjs/common') | ||
| const { default: VM } = require('../dist/index.js') | ||
| const { default: DefaultStateManager } = require('../dist/state/stateManager') | ||
|
|
||
| async function runTestCase (options, testData, t) { | ||
| let expectedPostStateRoot = testData.postStateRoot | ||
| if (expectedPostStateRoot.substr(0, 2) === '0x') { | ||
| expectedPostStateRoot = expectedPostStateRoot.substr(2) | ||
| } | ||
|
|
||
| // Prepare tx and block | ||
| let tx = makeTx(testData.transaction) | ||
| let block = makeBlockFromEnv(testData.env) | ||
| tx._homestead = true | ||
| tx.enableHomestead = true | ||
| block.isHomestead = function () { | ||
| return true | ||
| } | ||
| if (!tx.validate()) { | ||
| return | ||
| } | ||
|
|
||
| const common = new Common('mainnet', options.forkConfigVM.toLowerCase()) | ||
| const stateManager = new DefaultStateManager({ common: common }) | ||
| await setupPreConditions(stateManager._trie, testData) | ||
| const preStateRoot = stateManager._trie.root | ||
|
|
||
| // Set up VM | ||
| let vm = new VM({ | ||
| stateManager: stateManager, | ||
| common: common | ||
| }) | ||
| if (options.jsontrace) { | ||
| hookVM(vm, t) | ||
| } | ||
|
|
||
| // Determine set of all node hashes in the database | ||
| // before running the tx. | ||
| const existingKeys = new Set() | ||
| const it = stateManager._trie.db.iterator() | ||
|
Member
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. Can you also give me a hint where this
Contributor
Author
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 was possibly a local change, can't find an iterator method in MPT too. Levelup has an Update: I don't seem to be the handling values, only keys. So maybe |
||
| const next = promisify(it.next.bind(it)) | ||
| while (true) { | ||
| const key = await next() | ||
| if (!key) break | ||
| existingKeys.add(key.toString('hex')) | ||
| } | ||
|
|
||
| // Hook leveldb.get and add any node that was fetched during execution | ||
| // to a bag of proof nodes, under the condition that this node existed | ||
| // before execution. | ||
| const proofNodes = new Map() | ||
| const getFunc = stateManager._trie.db.get.bind(stateManager._trie.db) | ||
| stateManager._trie.db.get = (key, opts, cb) => { | ||
| getFunc(key, opts, (err, v) => { | ||
| if (!err && v) { | ||
| if (existingKeys.has(key.toString('hex'))) { | ||
| proofNodes.set(key.toString('hex'), v) | ||
| } | ||
| } | ||
| cb(err, v) | ||
| }) | ||
| } | ||
|
|
||
| try { | ||
| await vm.runTx({ tx: tx, block: block }) | ||
| } catch (err) { | ||
| await deleteCoinbase(new PStateManager(stateManager), block.header.coinbase) | ||
| } | ||
| t.equal(stateManager._trie.root.toString('hex'), expectedPostStateRoot, 'the state roots should match') | ||
|
|
||
| // Save bag of proof nodes to a new trie's underlying leveldb | ||
| const trie = new Trie(null, preStateRoot) | ||
| const opStack = [] | ||
| for (const [k, v] of proofNodes) { | ||
| opStack.push({ type: 'put', key: Buffer.from(k, 'hex'), value: v }) | ||
| } | ||
| await promisify(trie.db.batch.bind(trie.db))(opStack) | ||
|
|
||
| stateManager = new StateManager({ trie: trie }) | ||
| vm = new VM({ | ||
| stateManager: stateManager, | ||
| hardfork: options.forkConfig.toLowerCase() | ||
| }) | ||
| if (options.jsontrace) { | ||
| hookVM(vm, t) | ||
| } | ||
| try { | ||
| await vm.runTx({ tx: tx, block: block }) | ||
| } catch (err) { | ||
| await deleteCoinbase(stateManager, block.header.coinbase) | ||
| } | ||
| t.equal(stateManager._trie.root.toString('hex'), expectedPostStateRoot, 'the state roots should match') | ||
| } | ||
|
|
||
| /* | ||
| * If tx is invalid and coinbase is empty, the test harness | ||
| * expects the coinbase account to be deleted from state. | ||
| * Without this ecmul_0-3_5616_28000_96 would fail. | ||
| */ | ||
| async function deleteCoinbase (stateManager, coinbaseAddr) { | ||
| const account = await stateManager.getAccount(coinbaseAddr) | ||
| if (new BN(account.balance).isZero()) { | ||
| await stateManager.putAccount(coinbaseAddr, new Account()) | ||
| await stateManager.cleanupTouchedAccounts() | ||
| await stateManager._wrapped._cache.flush() | ||
| } | ||
| } | ||
|
|
||
| function hookVM (vm, t) { | ||
| vm.on('step', function (e) { | ||
| let hexStack = [] | ||
| hexStack = e.stack.map(item => { | ||
| return '0x' + new BN(item).toString(16, 0) | ||
| }) | ||
|
|
||
| var opTrace = { | ||
| 'pc': e.pc, | ||
| 'op': e.opcode.opcode, | ||
| 'gas': '0x' + e.gasLeft.toString('hex'), | ||
| 'gasCost': '0x' + e.opcode.fee.toString(16), | ||
| 'stack': hexStack, | ||
| 'depth': e.depth, | ||
| 'opName': e.opcode.name | ||
| } | ||
|
|
||
| t.comment(JSON.stringify(opTrace)) | ||
| }) | ||
| vm.on('afterTx', function (results) { | ||
| let stateRoot = { | ||
| 'stateRoot': vm.stateManager._trie.root.toString('hex') | ||
| } | ||
| t.comment(JSON.stringify(stateRoot)) | ||
| }) | ||
| } | ||
|
|
||
| function parseTestCases (forkConfig, testData, data, gasLimit, value) { | ||
| let testCases = [] | ||
| if (testData['post'][forkConfig]) { | ||
| testCases = testData['post'][forkConfig].map(testCase => { | ||
| let testIndexes = testCase['indexes'] | ||
| let tx = { ...testData.transaction } | ||
| if (data !== undefined && testIndexes['data'] !== data) { | ||
| return null | ||
| } | ||
|
|
||
| if (value !== undefined && testIndexes['value'] !== value) { | ||
| return null | ||
| } | ||
|
|
||
| if (gasLimit !== undefined && testIndexes['gas'] !== gasLimit) { | ||
| return null | ||
| } | ||
|
|
||
| tx.data = testData.transaction.data[testIndexes['data']] | ||
| tx.gasLimit = testData.transaction.gasLimit[testIndexes['gas']] | ||
| tx.value = testData.transaction.value[testIndexes['value']] | ||
| return { | ||
| 'transaction': tx, | ||
| 'postStateRoot': testCase['hash'], | ||
| 'env': testData['env'], | ||
| 'pre': testData['pre'] | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| testCases = testCases.filter(testCase => { | ||
| return testCase != null | ||
| }) | ||
|
|
||
| return testCases | ||
| } | ||
|
|
||
| module.exports = async function runStateTest (options, testData, t) { | ||
| const forkConfig = getRequiredForkConfigAlias(options.forkConfigTestSuite) | ||
| try { | ||
| const testCases = parseTestCases(forkConfig, testData, options.data, options.gasLimit, options.value) | ||
| if (testCases.length > 0) { | ||
| for (const testCase of testCases) { | ||
| await runTestCase(options, testCase, t) | ||
| } | ||
| } else { | ||
| t.comment(`No ${forkConfig} post state defined, skip test`) | ||
| return | ||
| } | ||
| } catch (e) { | ||
| t.fail('error running test case for fork: ' + forkConfig) | ||
| console.log('error:', e) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,7 @@ | ||
| { | ||
| "extends": "@ethereumjs/config-tsc", | ||
| "compilerOptions": { | ||
| "downlevelIteration": true | ||
| }, | ||
| "include": ["lib/**/*.ts"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| { | ||
| "extends": "@ethereumjs/config-tsc", | ||
| "compilerOptions": { | ||
| "outDir": "./dist" | ||
| "outDir": "./dist", | ||
| "downlevelIteration": true | ||
| }, | ||
| "include": ["lib/**/*.ts"] | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.