-
Notifications
You must be signed in to change notification settings - Fork 782
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
Stateless execution prototype #556
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
bb85e06
Add initial stateless protoype tester
s1na 86dfb24
Migrate stateless to ts, mv to lib
s1na a407fb8
stateless: don't throw on non-existent code in stateFromProof
s1na b539607
stateless: prove storage keys with original root, other minor fix
s1na 7421331
stateless: use pre-tx trie to prove contract storage keys
s1na 236b825
Fix formatting issues
s1na 2f50243
stateless: fix bug in proving non-existing storage keys
s1na 0ddb845
stateless: de-duplicate proof nodes, simplify stateFromProofs
s1na b1208da
stateless: all state tests pass, greatly simplified version
s1na 92bf49d
Update trie to v3.0.0, use v3 for StatelessRunner
s1na 570cba8
ci: add stateless test job
s1na 3809733
ci: fix stateless job
s1na 9572f57
vm -> stateless: monorepo transition fixes, tests/tester.js integrati…
holgerd77 fc13bb5
vm -> stateless: adopt to changed test environment, switch to promisi…
holgerd77 a383522
stateless: remove unnecessary homestead flags
s1na File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
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) | ||
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() | ||
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
{ | ||
"extends": "@ethereumjs/config-tsc", | ||
"compilerOptions": { | ||
"downlevelIteration": true | ||
}, | ||
"include": ["lib/**/*.ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Can you also give me a hint where this
iterator()
might have moved to? This was errored as not available when I last ran the code, got stock there to some extend.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.
This was possibly a local change, can't find an iterator method in MPT too. Levelup has an
iterator
method which I probably used. Maybe just replacing that line withstateManager._trie.db._leveldb.iterator()
would work.Update: I don't seem to be the handling values, only keys. So maybe
..._leveldb.iterator({ keys: true, values: false })