根據代碼發現還要了解的模塊有:node
ethereumjs/merkle-patricia-tree -對應數據存儲的數據結構 ethereumjs-blockchain —— 區塊鏈 ethereumjs-block ——區塊 levelup —— 數據庫
ethereumjs-account ——帳戶狀態
在本博客的ethereumjs分類中可見他們的學習文檔git
其實這就是怎麼本身使用各個模塊來生成一個相似geth客戶端的以太坊虛擬機,而後進行各種區塊鏈操做。而後上面的每一個模塊對應的就是實現這個虛擬機的各個部分github
1.數據庫
ethereumjs-vm/tests/BlockchainTestsRunner.jsjson
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函數api
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.dom
ethereumjs-vm/tests/api/index.jsasync
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))()