ethereumjs/ethereumjs-vm-4-tests
阿新 • • 發佈:2018-12-12
根據程式碼發現還要了解的模組有:
ethereumjs/merkle-patricia-tree -對應資料儲存的資料結構 ethereumjs-blockchain —— 區塊鏈 ethereumjs-block ——區塊 levelup —— 資料庫
ethereumjs-account ——賬戶狀態
在本部落格的ethereumjs分類中可見他們的學習文件
其實這就是怎麼自己使用各個模組來生成一個類似geth客戶端的以太坊虛擬機器,然後進行各類區塊鏈操作。然後上面的每個模組對應的就是實現這個虛擬機器的各個部分
1.
ethereumjs-vm/tests/BlockchainTestsRunner.js
sssconst testUtil = require('./util.js') const ethUtil = require('ethereumjs-util') const Trie = require('merkle-patricia-tree/secure') const Block = require('ethereumjs-block') const Blockchain = require('ethereumjs-blockchain') const BlockHeader = require('ethereumjs-block/header.js') const Level = require('levelup') var cacheDB = new Level('./.cachedb') //生成一個node.js輕量級資料庫 module.exports = function runBlockchainTest (options, testData, t, cb) { var blockchainDB = new Level('', { db: require('memdown') //如果沒有設定則預設使用'memdown' }) var state = new Trie() var validate = false //不對加入的區塊進行驗證 // Only run with block validation when sealEngine present in test file// and being set to Ethash PoW validation ,只有在test檔案中出現sealEngine時,執行才帶著區塊驗證,並且將別設定為Ethash工作量證明驗證 if (testData.sealEngine && testData.sealEngine === 'Ethash') { validate = true } var blockchain = new Blockchain({ db: blockchainDB, hardfork: options.forkConfig.toLowerCase(), //硬分叉 validate: validate }) if (validate) { blockchain.ethash.cacheDB = cacheDB } var VM if (options.dist) { VM = require('../dist/index.js') } else { VM = require('../lib/index.js') } var vm = new VM({ state: state, //state字首樹,key = address,value = account state(賬戶狀態) blockchain: blockchain, //設定好的區塊鏈 hardfork: options.forkConfig.toLowerCase() //使用的硬分支規則 }) var genesisBlock = new Block({ hardfork: options.forkConfig.toLowerCase() })//初始區塊 testData.homestead = true //以太坊的版本 if (testData.homestead) { vm.on('beforeTx', function (tx) { tx._homestead = true }) vm.on('beforeBlock', function (block) { block.header.isHomestead = function () { return true } }) } async.series([ // set up pre-state,設定預編譯的狀態,就是使用預編譯中定義的賬戶的nonce\balance\code\storage,根據storage的值去構建一個字首樹,然後該字首樹的root值將作為賬戶的storageRoot
//然後將codeBuf設定到account上
//最後構建了整個區塊鏈上的state字首樹,記錄所有賬戶 function (done) { testUtil.setupPreConditions(state, testData, function () { //詳細看下面 done() }) }, function (done) { // create and add genesis block ,建立並新增初始區塊 genesisBlock.header = new BlockHeader(formatBlockHeader(testData.genesisBlockHeader), { //先建立區塊的區塊頭,並新增到區塊上 hardfork: options.forkConfig.toLowerCase() })
//因為state.root即state字首樹的root值,也就是區塊頭中記錄的stateRoot的值,所以他們相等 t.equal(state.root.toString('hex'), genesisBlock.header.stateRoot.toString('hex'), 'correct pre stateRoot') if (testData.genesisRLP) { t.equal(genesisBlock.serialize().toString('hex'), testData.genesisRLP.slice(2), 'correct genesis RLP') } blockchain.putGenesis(genesisBlock, function (err) {//新增初始區塊到區塊鏈上 done(err) }) }, function (done) {//然後再根據testData上的blocks的資訊生成一個區塊 async.eachSeries(testData.blocks, function (raw, cb) { try { var block = new Block(Buffer.from(raw.rlp.slice(2), 'hex'), { hardfork: options.forkConfig.toLowerCase() }) // forces the block into thinking they are homestead if (testData.homestead) { block.header.isHomestead = function () { return true } block.uncleHeaders.forEach(function (uncle) { uncle.isHomestead = function () { return true } }) } blockchain.putBlock(block, function (err) { //新增該普通區塊 cb(err) }) } catch (err) { cb() } }, function () { done() }) }, function runBlockchain (done) {//執行區塊鏈,處理上面新增的區塊,驗證不正確的將不會連上區塊鏈 vm.runBlockchain(function () { done() }) }, function getHead (done) { vm.blockchain.getHead(function (err, block) {//得到最新的區塊頭 if (testData.lastblockhash.substr(0, 2) === '0x') { // fix for BlockchainTests/GeneralStateTests/stRandom/* testData.lastblockhash = testData.lastblockhash.substr(2)//testData.lastblockhash為最後新增的普通區塊的hash值 } t.equal(block.hash().toString('hex'), testData.lastblockhash, 'last block hash')//檢查最新區塊的hash是否為最後新增的普通區塊的hash值,是則說明區塊鏈執行成功 //如果測試失敗,那麼block.header是preState,因為vm.runBlock有一個防止實際的postState在其不等於預期的postState時被匯入的檢查 //跳過這一點對於除錯很有用,這樣verifyPostConditions可以比較testData.postState與實際的postState,而不是與preState。 if (!options.debug) {//不進行state字首樹的除錯 // make sure the state is set before checking post conditions // 保證在檢視post條件前就設定了狀態 state.root = block.header.stateRoot //則直接賦值state字首樹的root } done(err) }) }, function (done) { if (options.debug) {//進行除錯,則testData.postState中是執行後正確的賬戶的資訊,state是當前區塊鏈上的字首樹,對比兩者的狀態是否相同,相同才除錯成功 testUtil.verifyPostConditions(state, testData.postState, t, done) } else { done() } } ], function () { t.equal(blockchain.meta.rawHead.toString('hex'), testData.lastblockhash, 'correct header block') cb() }) } function formatBlockHeader (data) { var r = {} var keys = Object.keys(data) keys.forEach(function (key) { r[key] = ethUtil.addHexPrefix(data[key]) }) return r }
setupPreConditions函式:
/** * setupPreConditions given JSON testData * @param {[type]} state - the state DB/trie * @param {[type]} testData - JSON from tests repo * @param {Function} done - callback when function is completed */ exports.setupPreConditions = function (state, testData, done) { // set up pre-state,設定預編譯的狀態,就是使用預編譯中定義的賬戶的nonce\balance\code\storage var keysOfPre = Object.keys(testData.pre) async.eachSeries(keysOfPre, function (key, callback) { var acctData = testData.pre[key] var account = new Account() account.nonce = format(acctData.nonce) account.balance = format(acctData.balance) var codeBuf = Buffer.from(acctData.code.slice(2), 'hex') var storageTrie = state.copy() storageTrie.root = null async.series([ function (cb2) { var keys = Object.keys(acctData.storage) async.forEachSeries(keys, function (key, cb3) {//根據storage的值去構建一個字首樹 var val = acctData.storage[key] val = rlp.encode(Buffer.from(val.slice(2), 'hex')) key = utils.setLength(Buffer.from(key.slice(2), 'hex'), 32) storageTrie.put(key, val, cb3) }, cb2) }, function (cb2) {//然後將codeBuf設定到account上 account.setCode(state, codeBuf, cb2) }, function (cb2) {//然後該字首樹的root值將作為賬戶的storageRoot account.stateRoot = storageTrie.root if (testData.exec && key === testData.exec.address) { testData.root = storageTrie.root }
//最後構建了整個區塊鏈上的state字首樹,記錄所有賬戶 state.put(Buffer.from(utils.stripHexPrefix(key), 'hex'), account.serialize(), function () { cb2() }) } ], callback) }, done) }
verifyPostConditions和verifyAccountPostConditions函式
exports.verifyPostConditions = function (state, testData, t, cb) {//testData = testData.postState(即testData.json檔案中的postState值) var hashedAccounts = {} var keyMap = {} for (var key in testData) {//key為testData的索引,即address var hash = utils.keccak256(Buffer.from(utils.stripHexPrefix(key), 'hex')).toString('hex')//得到address相應hash值 hashedAccounts[hash] = testData[key] //將testData.postState中對應索引key的賬戶資訊儲存到hashedAccounts[hash]中 keyMap[hash] = key //address存到KeyMap[hash]中 } var q = async.queue(function (task, cb2) { exports.verifyAccountPostConditions(state, task.address, task.account, task.testData, t, cb2) }, 1) var stream = state.createReadStream()//生成state可讀流,state為區塊鏈上的state 字首樹 stream.on('data', function (data) { //得到state 字首樹上的所有賬戶資訊 var acnt = new Account(rlp.decode(data.value)) //對應下面的testData var key = data.key.toString('hex') //對應下面的address var testData = hashedAccounts[key]//這兩個是testData.postState中的資訊 var address = keyMap[key] delete keyMap[key] if (testData) { q.push({ //然後呼叫verifyAccountPostConditions函式對他們兩個的值進行對比 address: address, account: acnt, testData: testData }) } else { t.fail('invalid account in the trie: ' + key) } }) stream.on('end', function () { function onEnd () { for (hash in keyMap) { t.fail('Missing account!: ' + keyMap[hash]) } cb() } if (q.length()) { q.drain = onEnd } else { onEnd() } }) } /** * verifyAccountPostConditions using JSON from tests repo * @param {[type]} state DB/trie * @param {[type]} string Account Address * @param {[type]} account to verify * @param {[type]} acctData postconditions JSON from tests repo * @param {Function} cb completion callback */ exports.verifyAccountPostConditions = function (state, address, account, acctData, t, cb) {//account是區塊鏈上state字首樹的賬戶資訊,acctData是testData.postState上的 t.comment('Account: ' + address) t.equal(format(account.balance, true).toString('hex'), format(acctData.balance, true).toString('hex'), 'correct balance')//檢查餘額值是否相同 t.equal(format(account.nonce, true).toString('hex'), format(acctData.nonce, true).toString('hex'), 'correct nonce') //檢查nonce值是否相同 // validate storage,下面都是用於檢查storage的值 var origRoot = state.root var storageKeys = Object.keys(acctData.storage) var hashedStorage = {} for (var key in acctData.storage) { hashedStorage[utils.keccak256(utils.setLength(Buffer.from(key.slice(2), 'hex'), 32)).toString('hex')] = acctData.storage[key] } if (storageKeys.length > 0) {//即testData.postState中的storage中有值,那麼就要進行對比 state.root = account.stateRoot var rs = state.createReadStream() rs.on('data', function (data) {//得到區塊鏈上state字首樹的所有賬戶資訊 var key = data.key.toString('hex') var val = '0x' + rlp.decode(data.value).toString('hex') if (key === '0x') { key = '0x00' //將state字首樹中的key從'0x'改為'0x00' //下面是相應地改testData.postState中的key acctData.storage['0x00'] = acctData.storage['0x00'] ? acctData.storage['0x00'] : acctData.storage['0x']//即acctData.storage['0x00']是否存在,存在則用它,否則就將acctData.storage['0x']的值賦值給acctData.storage['0x00'] delete acctData.storage['0x'] //刪掉acctData.storage['0x'],就是讓testData.postState中只留key == '0x00'的情況 } //然後再進行比較,直至state字首樹的所有賬戶資訊比較完 t.equal(val, hashedStorage[key], 'correct storage value') delete hashedStorage[key] }) rs.on('end', function () {//然後最後檢視hashedStorage是否有剩下的非空賬戶的賬戶資訊,空賬戶可不相同 for (var key in hashedStorage) { if (hashedStorage[key] !== '0x00') {//有則說明testData.postState中有賬戶在區塊鏈的state字首樹上沒找到,失敗;否則成功 t.fail('key: ' + key + ' not found in storage') } } state.root = origRoot //那麼區塊鏈的state字首樹是對的,不改變其root,除錯通過 cb() }) } else { cb() } }
相應的testData資料為:
{ "_info" : { "comment" : "Taken from ethereum/tests, SimpleTx3_Byzantium, on 27/09/2018", "filledwith" : "cpp-1.3.0+commit.6e0ce939.Linux.g++", "lllcversion" : "Version: 0.4.18-develop.2017.9.25+commit.a72237f2.Linux.g++", "source" : "src/BlockchainTestsFiller/bcValidBlockTest/SimpleTx3Filler.json", "sourceHash" : "373dcc1d6dd499c8b9c23b8b4bd87688e216bc8dea7061bdc26da711c33f59cb" }, "blocks" : [ { "blockHeader" : { "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "coinbase" : "0x8888f1f195afa192cfee860698584c030f4c9db1", "difficulty" : "0x020000", "extraData" : "", "gasLimit" : "0x2fefba", "gasUsed" : "0x5208", "hash" : "0x9a843b51370ec98baabc087b27273b193cdeac52ff14314b2781b5eb562179b1", "mixHash" : "0xe8fbc818dfd6d2f606c65794513dde2e93c5828fda4fea138d1a3ecf49f67115", "nonce" : "0xdffdb73abb355e95", "number" : "0x01", "parentHash" : "0x701327478e9e63cff9633717a6bb288db43cd4027b5b3e9afe76bfec0eac1c8e", "receiptTrie" : "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "stateRoot" : "0xc70abbdbb7533542fc237ad6e7f6bdc4cd6c60f865f15bfe88c55ffc6d8c0a0a", "timestamp" : "0x59d776fa", "transactionsTrie" : "0x53d5b71a8fbb9590de82d69dfa4ac31923b0c8afce0d30d0d8d1e931f25030dc", "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" }, "rlp" : "0xf90260f901f9a0701327478e9e63cff9633717a6bb288db43cd4027b5b3e9afe76bfec0eac1c8ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0c70abbdbb7533542fc237ad6e7f6bdc4cd6c60f865f15bfe88c55ffc6d8c0a0aa053d5b71a8fbb9590de82d69dfa4ac31923b0c8afce0d30d0d8d1e931f25030dca0056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefba8252088459d776fa80a0e8fbc818dfd6d2f606c65794513dde2e93c5828fda4fea138d1a3ecf49f6711588dffdb73abb355e95f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba0f3266921c93d600c43f6fa4724b7abae079b35b9e95df592f95f9f3445e94c88a012f977552ebdb7a492cf35f3106df16ccb4576ebad4113056ee1f52cbe4978c1c0", "transactions" : [ { "data" : "", "gasLimit" : "0xc350", "gasPrice" : "0x0a", "nonce" : "0x00", "r" : "0xf3266921c93d600c43f6fa4724b7abae079b35b9e95df592f95f9f3445e94c88", "s" : "0x12f977552ebdb7a492cf35f3106df16ccb4576ebad4113056ee1f52cbe4978c1", "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", "v" : "0x1b", "value" : "0x0a" } ], "uncleHeaders" : [ ] } ], "genesisBlockHeader" : { "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "coinbase" : "0x8888f1f195afa192cfee860698584c030f4c9db1", "difficulty" : "0x020000", "extraData" : "0x42", "gasLimit" : "0x2fefd8", "gasUsed" : "0x00", "hash" : "0x701327478e9e63cff9633717a6bb288db43cd4027b5b3e9afe76bfec0eac1c8e", "mixHash" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "nonce" : "0x0102030405060708", "number" : "0x00", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "receiptTrie" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "stateRoot" : "0x7d883d38bc7a640dd66e5cda78cd01b52a7dc40e61f7c2ddbab7cb3ae3b8b9f2", "timestamp" : "0x54c98c81", "transactionsTrie" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" }, "genesisRLP" : "0xf901fcf901f7a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a07d883d38bc7a640dd66e5cda78cd01b52a7dc40e61f7c2ddbab7cb3ae3b8b9f2a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000080832fefd8808454c98c8142a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421880102030405060708c0c0", "lastblockhash" : "0x9a843b51370ec98baabc087b27273b193cdeac52ff14314b2781b5eb562179b1", "network" : "Byzantium", "postState" : { "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { "balance" : "0x0a", "code" : "", "nonce" : "0x00", "storage" : { } }, "0x49ec3a96efcc4f9e2e741ea2af622b91f74a2bcc" : { "balance" : "0x02540be400", "code" : "", "nonce" : "0x03", "storage" : { } }, "0x8888f1f195afa192cfee860698584c030f4c9db1" : { "balance" : "0x29a2241af62f3450", "code" : "", "nonce" : "0x00", "storage" : { } }, "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { "balance" : "0x025408afa6", "code" : "", "nonce" : "0x01", "storage" : { } }, "0xe4ccfc7cc4a70f5efd0b87867f20acbf68842ef8" : { "balance" : "0x02540be400", "code" : "", "nonce" : "0x00", "storage" : { } } }, "pre" : { //這裡就是預編譯時用來生成賬戶的資料 "0x49ec3a96efcc4f9e2e741ea2af622b91f74a2bcc" : { "balance" : "0x02540be400", "code" : "", "nonce" : "0x03", "storage" : { } }, "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { "balance" : "0x02540be400", "code" : "", "nonce" : "0x00", "storage" : { } }, "0xe4ccfc7cc4a70f5efd0b87867f20acbf68842ef8" : { "balance" : "0x02540be400", "code" : "", "nonce" : "0x00", "storage" : { } } } }
2.
ethereumjs-vm/tests/api/index.js
const { promisify } = require('util') const tape = require('tape') const util = require('ethereumjs-util') const Block = require('ethereumjs-block') const VM = require('../../lib/index') const { setupVM } = require('./utils') const { setupPreConditions } = require('../util') const testData = require('./testdata.json') tape('VM with fake blockchain', (t) => { t.test('should insantiate without params', (st) => { const vm = new VM() //沒有帶著任何引數的初始化 st.ok(vm.stateManager) st.deepEqual(vm.stateManager._trie.root, util.KECCAK256_RLP, 'it has default trie') //會得到一個預設的字首樹 st.ok(vm.blockchain.fake, 'it has fake blockchain by default') //會得到一個預設的假區塊鏈 st.end() }) t.test('should be able to activate precompiles', (st) => { let vm = new VM({ activatePrecompiles: true }) //啟用預編譯,就會建立一個新字首樹例項,trie = new Trie(),然後新增八個賬戶,之後還在this._precompiled字典中新增八個事前預編譯好的合約 st.notEqual(vm.stateManager._trie.root, util.KECCAK256_RLP, 'it has different root') //所以不再是預設的字首樹 st.end() }) t.test('should only accept valid chain and fork', (st) => { let vm = new VM({ chain: 'ropsten', hardfork: 'byzantium' }) //根據定義好的chain和byzantium,其對應的一些值則是預設好的 st.equal(vm.stateManager._common.param('gasPrices', 'ecAdd'), 500) try { vm = new VM({ chain: 'mainchain', hardfork: 'homestead' }) //沒有這個chain st.fail('should have failed for invalid chain') } catch (e) { st.ok(e.message.includes('not supported')) } st.end() }) t.test('should run blockchain without blocks', async (st) => { const vm = new VM() const run = promisify(vm.runBlockchain.bind(vm)) await run() st.end() }) }) tape('VM with blockchain', (t) => { t.test('should instantiate', (st) => { const vm = setupVM() st.deepEqual(vm.stateManager._trie.root, util.KECCAK256_RLP, 'it has default trie') st.notOk(vm.stateManager.fake, 'it doesn\'t have fake blockchain') //這種生成vm的方法是沒有假區塊鏈生成的 st.end() }) t.test('should run blockchain without blocks', async (st) => { const vm = setupVM() await runBlockchainP(vm) st.end() }) t.test('should run blockchain with mocked runBlock', async (st) => { const vm = setupVM() const genesis = new Block(Buffer.from(testData.genesisRLP.slice(2), 'hex')) const block = new Block(Buffer.from(testData.blocks[0].rlp.slice(2), 'hex')) await putGenesisP(vm.blockchain, genesis) //將初始區塊新增進區塊鏈中 st.equal(vm.blockchain.meta.genesis.toString('hex'), testData.genesisBlockHeader.hash.slice(2)) await putBlockP(vm.blockchain, block) const head = await getHeadP(vm.blockchain) st.equal( head.hash().toString('hex'), testData.blocks[0].blockHeader.hash.slice(2) ) const setupPreP = promisify(setupPreConditions) await setupPreP(vm.stateManager._trie, testData) vm.runBlock = (block, cb) => cb(new Error('test')) runBlockchainP(vm) .then(() => st.fail('it hasn\'t returned any errors')) .catch((e) => { st.equal(e.message, 'test', 'it has correctly propagated runBlock\'s error') st.end() }) }) t.test('should run blockchain with blocks', async (st) => { const vm = setupVM() const genesis = new Block(Buffer.from(testData.genesisRLP.slice(2), 'hex')) const block = new Block(Buffer.from(testData.blocks[0].rlp.slice(2), 'hex')) await putGenesisP(vm.blockchain, genesis) st.equal(vm.blockchain.meta.genesis.toString('hex'), testData.genesisBlockHeader.hash.slice(2)) await putBlockP(vm.blockchain, block) const head = await getHeadP(vm.blockchain) st.equal( head.hash().toString('hex'), testData.blocks[0].blockHeader.hash.slice(2) ) const setupPreP = promisify(setupPreConditions) await setupPreP(vm.stateManager._trie, testData) await runBlockchainP(vm) st.end() }) }) const runBlockchainP = (vm) => promisify(vm.runBlockchain.bind(vm))() const putGenesisP = (blockchain, genesis) => promisify(blockchain.putGenesis.bind(blockchain))(genesis) const putBlockP = (blockchain, block) => promisify(blockchain.putBlock.bind(blockchain))(block) const getHeadP = (blockchain) => promisify(blockchain.getHead.bind(blockchain))()