1. 程式人生 > >ethereumjs/ethereumjs-blockchain-2-test

ethereumjs/ethereumjs-blockchain-2-test

https://github.com/ethereumjs/ethereumjs-blockchain/tree/master/test

 

'use strict'

const test = require('tape')
const Blockchain = require('..')
const Block = require('ethereumjs-block')
const Common = require('ethereumjs-common')
const async = require('async')
const ethUtil = require('ethereumjs-util
') const level = require('level-mem') const testData = require('./testdata.json') const BN = require('bn.js') const rlp = ethUtil.rlp test('blockchain test', function (t) { t.plan(73) var blockchain = new Blockchain()//還沒有區塊頭 var genesisBlock var blocks = [] var forkHeader blockchain.validate
= false //不進行區塊驗證 async.series([ function (done) { blockchain.getHead(function (err, head) {//返回指定迭代區塊頭,這裡沒有設定name,則name = 'vm' if (err) return done(err) t.ok(true, 'should not crash on getting head of a blockchain without a genesis') //因為這個區塊鏈還沒有區塊頭,所以是得不到區塊頭資訊的 done() }) }, function initialization (done) {
const common = new Common('ropsten') t.throws(function () { new Blockchain({ chain: 'ropsten', common: common }) }, /not allowed!$/, 'should throw on initialization with chain and common parameter') // eslint-disable-line const bc0 = new Blockchain({ chain: 'ropsten' }) //兩條鏈是相同的,所以得到的區塊頭是相同的 const bc1 = new Blockchain({ common: common }) async.parallel([ (cb) => bc0.getHead(cb), (cb) => bc1.getHead(cb) ], (err, heads) => { if (err) return done(err) t.equals(heads[0].hash().toString('hex'), common.genesis().hash.slice(2), 'correct genesis hash') t.equals(heads[0].hash().toString('hex'), heads[1].hash().toString('hex'), 'genesis blocks match') done() }) }, function alternateConstructors (done) { var db = level() var blockchain = new Blockchain(db) t.equals(db, blockchain.db, 'support constructor with db parameter') blockchain = new Blockchain({detailsDb: db, blockDb: db})//棄用的引數,detailsDb會被忽略 t.equals(db, blockchain.db, 'support blockDb and detailsDb params') t.notOk(blockchain.detailsDb, 'ignore detailsDb param') done() }, function addgenesis (done) { genesisBlock = new Block() genesisBlock.setGenesisParams() //設定初始區塊 blockchain.putGenesis(genesisBlock, function (err) {//將該初始區塊新增到區塊鏈上 if (err) return done(err) t.equals(genesisBlock.hash().toString('hex'), blockchain.meta.genesis.toString('hex'), 'genesis block hash should be correct') blocks.push(genesisBlock) done() }) }, function invalidGenesis (done) { var badBlock = new Block() //該區塊其實不是初始區塊,但是我通過將其badBlock.header.number更改成空陣列buffer來假裝初始區塊 badBlock.header.number = Buffer.from([]) blockchain.validate = true //進行區塊驗證 blockchain.putBlock(badBlock, function (err) { //然後將這個區塊新增進區塊鏈中 t.ok(err, 'should not validate a block incorrectly flagged as genesis') //會失敗,因為區塊驗證過程中會發現它不是初始區塊 blockchain.validate = false done() }, false) }, function addBlocks (done) { //即從blockNumber = 1新增到blockNumber = 10結束 function addNextBlock (blockNumber) { var block = new Block() block.header.number = ethUtil.toBuffer(blockNumber) block.header.difficulty = '0xfffffff' block.header.parentHash = blocks[blockNumber - 1].hash() blockchain.putBlock(block, function (err) { if (err) return done(err) blocks.push(block) if (blocks.length === 10) { t.ok(true, 'added 10 blocks') done() } else { addNextBlock(blockNumber + 1) } }) } addNextBlock(1) }, function getBlockByNumber (done) { //通過blocknumber得到block blockchain.getBlock(1, function (err, block) { if (err) return done(err) t.equals(block.hash().toString('hex'), blocks[1].hash().toString('hex'), 'should get block by number') done() }) }, function getBlockByHash (done) {//通過blockhash得到block blockchain.getBlock(genesisBlock.hash(), function (err, block) { if (err) return done(err) t.equals(block.hash().toString('hex'), genesisBlock.hash().toString('hex'), 'should get block by hash') done() }) }, function getBlocks1 (done) { // start: genesisHash, max: 5, skip: 0, reverse: false,正向從初始區塊開始獲取最多5個區塊,跳過第一個區塊(即初始區塊) blockchain.getBlocks(genesisBlock.hash(), 5, 0, false, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 5, 'should get 5 blocks') t.ok(isConsecutive(blocks), 'blocks should be consecutive')//是連續的區塊 done() }) }, function getBlocks2 (done) { // start: genesisHash, max: 5, skip: 1, reverse: false blockchain.getBlocks(genesisBlock.hash(), 5, 1, false, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 5, 'should get 5 blocks') t.ok(!isConsecutive(blocks), 'blocks should not be consecutive') //因為跳過了第二個區塊,所以得到的區塊就不連續了 done() }) }, function getBlocks3 (done) { // start: genesisHash, max: 5, skip: 2, reverse: false blockchain.getBlocks(genesisBlock.hash(), 5, 2, false, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 4, 'should get 4 blocks') t.ok(!isConsecutive(blocks), 'blocks should not be consecutive') done() }) }, function getBlocks4 (done) { // start: genesisHash, max: 12, skip: 0, reverse: false blockchain.getBlocks(genesisBlock.hash(), 12, 0, false, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 10, 'should get 10 blocks') t.ok(isConsecutive(blocks), 'blocks should be consecutive') done() }) }, function getBlocks5 (done) { // start: 0, max: 5, skip: 0, reverse: false blockchain.getBlocks(0, 5, 0, false, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 5, 'should get 5 blocks') t.ok(isConsecutive(blocks), 'blocks should be consecutive') done() }) }, function getBlocks6 (done) { // start: 0, max: 5, skip: 1, reverse: false blockchain.getBlocks(1, 5, 1, false, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 5, 'should get 5 blocks') t.ok(!isConsecutive(blocks), 'blocks should not be consecutive') done() }) }, function getBlocks7 (done) { // start: 0, max: 5, skip: 2, reverse: false blockchain.getBlocks(0, 5, 2, false, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 4, 'should get 4 blocks') t.ok(!isConsecutive(blocks), 'blocks should not be consecutive') done() }) }, function getBlocks8 (done) { // start: 0, max: 12, skip: 0, reverse: false blockchain.getBlocks(0, 12, 0, false, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 10, 'should get 10 blocks') t.ok(isConsecutive(blocks), 'blocks should be consecutive') done() }) }, function getBlocks9 (done) { // start: 1, max: 5, skip: 0, reverse: false blockchain.getBlocks(1, 5, 0, false, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 5, 'should get 5 blocks') t.ok(isConsecutive(blocks), 'blocks should be consecutive') done() }) }, function getBlocks10 (done) { // start: 5, max: 5, skip: 0, reverse: true ,從第六個區塊開始反向獲取最多5個區塊,跳過第一個區塊 blockchain.getBlocks(5, 5, 0, true, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 5, 'should get 5 blocks') t.ok(isConsecutive(blocks.reverse()), 'blocks should be consecutive') done() }) }, function getBlocks11 (done) { // start: 5, max: 10, skip: 0, reverse: true blockchain.getBlocks(5, 10, 0, true, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 6, 'should get 6 blocks') t.ok(isConsecutive(blocks.reverse()), 'blocks should be consecutive') done() }) }, function getBlocks12 (done) { // start: 5, max: 10, skip: 0, reverse: true blockchain.getBlocks(5, 10, 1, true, function (err, blocks) { if (err) return done(err) t.equals(blocks.length, 3, 'should get 3 blocks') t.ok(!isConsecutive(blocks.reverse()), 'blocks should not be consecutive') done() }) }, function selectNeededHashes (done) { var neededHash = Buffer.from('abcdef', 'hex') blockchain.selectNeededHashes([//即這三個hash值表示的區塊只有neededHash不在區塊鏈上,所以最後得到的陣列結果中有的是是neededHash這個hash,表示需要的區塊 blocks[0].hash(), blocks[9].hash(), neededHash ], (err, hashes) => { if (err) return done(err) t.equals(hashes[0].toString('hex'), neededHash.toString('hex'), 'should find needed hash') done() }) }, function iterateBlocks (done) {//迭代從blocknumber = 0 到 9這十個之前加到區塊鏈上的區塊,他們的區塊hash是相同的,所以符合迭代的if條件,直至i=9才結束 var i = 0 blockchain.iterator('test', function (block, reorg, cb) { if (block.hash().equals(blocks[i + 1].hash())) i++ cb() }, function () { t.equals(i, 9, 'should iterate through 9 blocks') done() }) }, function iterateError (done) { blockchain.iterator('error', function (block, reorg, cb) {//就是一開始迭代就返回錯誤 cb(new Error('iterator func error')) }, function (err) { //然後就會觸發回撥函式,err則為上面返回的錯誤 t.ok(err, 'should catch iterator func error') t.equal(err.message, 'iterator func error', 'should return correct error') done() }) }, function iterateEmpty (done) { var blockchain = new Blockchain() blockchain.validate = false //不驗證區塊 blockchain.iterator('test', function () { //這個沒有返回錯誤就結束了 t.ok(false, 'should not call iterator function') done() }, function (err) {//所以回撥函式的err為null t.error(err, 'should not return error') t.ok(true, 'should finish iterating') done() }) }, function getMeta (done) { //得到區塊鏈的元資料的方法 t.equals(blockchain.meta.rawHead.toString('hex'), blocks[9].hash().toString('hex'), 'should get meta.rawHead') t.equals(blockchain.meta.genesis.toString('hex'), genesisBlock.hash().toString('hex'), 'should get meta.genesis') t.ok(blockchain.meta.heads['test'], 'should get meta.heads') done() }, function addForkHeaderAndResetStaleHeads (done) { forkHeader = new Block.Header() forkHeader.number = ethUtil.toBuffer(9)//新得到的區塊索引是9 forkHeader.difficulty = '0xffffffff' forkHeader.parentHash = blocks[8].hash() blockchain._heads['staletest'] = blockchain._headHeader blockchain.putHeader(forkHeader, function (err) {//新增區塊頭,這裡的回撥中沒有返回儲存的header!!!!,即(err,header)後面測試header其值為 // [ undefined, // undefined, // [ undefined, undefined ], // undefined, // [ undefined, undefined ], // undefined ] t.equals(blockchain._heads['staletest'].toString('hex'), blocks[8].hash().toString('hex'), 'should update stale head')//blockchain._heads['staletest']是舊的blockchain._headHeader的值,等於blocks[8].hash().toString('hex') t.equals(blockchain._headBlock.toString('hex'), blocks[8].hash().toString('hex'), 'should update stale headBlock')//因為只添加了區塊頭,沒有新增區塊,所以現在的blockchain._headBlock.toString('hex')等於blocks[8].hash().toString('hex') // t.notOk(err, 'should add new block in fork') //err為null,沒出錯,接下來就是應該新增新的區塊到分支上,光新增區塊頭是不夠的 done() }) }, function delForkHeader (done) { blockchain.delBlock(forkHeader.hash(), (err) => {//forkHeader.hash()等於區塊hash,即上面新增的區塊頭,刪除指定的區塊 t.ok(!err, 'should delete fork block') //即err = null t.equals(blockchain._headHeader.toString('hex'), blocks[8].hash().toString('hex'), 'should reset headHeader')//因為成功將上面新增的區塊頭刪除了,所以現在的blockchain._headHeader == blocks[8].hash() t.equals(blockchain._headBlock.toString('hex'), blocks[8].hash().toString('hex'), 'should not change headBlock') //因為blocks[8]是有區塊的,所以這兩個值是相等的 done() }) }, function delBlocks (done) { function delNextBlock (number, cb) { var block = blocks[number] blockchain.delBlock(block.hash(), (err) => { if (err) return cb(err) if (number > 6) { return delNextBlock(--number, cb) } cb() }) } delNextBlock(9, (err) => {//從blocknumber = 9的區塊刪到6,然後是5的區塊變成了頭 t.ok(!err, 'should delete blocks in canonical chain') t.equals(blockchain._headHeader.toString('hex'), blocks[5].hash().toString('hex'), 'should have block 5 as head') done() }) }, function delBlockAndChildren (done) { blockchain.delBlock(blocks[1].hash(), (err) => {//當你直接刪除blocknumber = 1的區塊時,意味著將其後面的子區塊都刪除了,所以此時的區塊頭為初始區塊 t.ok(!err, 'should delete block and children') t.equals(blockchain._headHeader.toString('hex'), genesisBlock.hash().toString('hex'), 'should have genesis as head') done() }) }, function putBlocks (done) { blockchain.putBlocks(blocks.slice(1), (err) => { t.ok(!err, 'should put multiple blocks at once') done() }) }, function getHeads (done) { createTestDB((err, db, genesis) => { if (err) return done(err) var blockchain = new Blockchain({db: db}) blockchain.getHead((err, head) => { if (err) return done(err) t.equals(head.hash().toString('hex'), genesis.hash().toString('hex'), 'should get head') t.equals(blockchain._heads['head0'].toString('hex'), 'abcd', 'should get state root heads') done() }) }) }, function validate (done) {//會對新增進的區塊進行驗證 var blockchain = new Blockchain({validate: true}) var genesisBlock = new Block() genesisBlock.setGenesisParams() blockchain.putGenesis(genesisBlock, function (err) { t.notOk(err, 'should validate genesisBlock') var invalidBlock = new Block() blockchain.putBlock(invalidBlock, function (err) { t.ok(err, 'should not validate an invalid block') done() }) }) }, function addBlockWithBody (done) {//使用rlp值來新增區塊 var blockchain = new Blockchain({validate: false}) var genesisBlock = new Block(Buffer.from(testData.genesisRLP.slice(2), 'hex'))//slice(2)即去掉'0x' blockchain.putGenesis(genesisBlock, function (err) { if (err) return done(err) var block = new Block(Buffer.from(testData.blocks[0].rlp.slice(2), 'hex')) blockchain.putBlock(block, function (err) { if (err) return done(err) t.notOk(err, 'should add block with a body') done() }) }) }, function uncachedDbOps (done) { createTestDB((err, db, genesis) => { if (err) return done(err) var blockchain = new Blockchain({db: db}) async.series([ cb => blockchain._hashToNumber(genesisBlock.hash(), (err, number) => { t.equals(number.toString(10), '0', 'should perform _hashToNumber correctly') cb(err) }), cb => blockchain._numberToHash(new BN(0), (err, hash) => { t.equals(genesisBlock.hash().toString('hex'), hash.toString('hex'), 'should perform _numberToHash correctly') cb(err) }), cb => blockchain._getTd(genesisBlock.hash(), new BN(0), (err, td) => { t.equals(td.toBuffer().toString('hex'), genesis.header.difficulty.toString('hex'), 'should perform _getTd correctly') cb(err) }) ], done) }) }, function saveHeads (done) { var db = level() var blockchain = new Blockchain({db: db, validate: false}) var header = new Block.Header() header.number = ethUtil.toBuffer(1) header.difficulty = '0xfffffff' header.parentHash = blocks[0].hash() blockchain.putHeader(header, (err) => { if (err) return done(err) blockchain = new Blockchain({db: db, validate: false}) async.series([ (cb) => blockchain.getLatestHeader((err, latest) => {//得到最新的區塊頭 if (err) return done(err) t.equals(latest.hash().toString('hex'), header.hash().toString('hex'), 'should save headHeader') cb() }), (cb) => blockchain.getLatestBlock((err, latest) => {//得到最新的區塊 if (err) return done(err) t.equals(latest.hash().toString('hex'), blocks[0].hash().toString('hex'), 'should save headBlock') cb() }) ], done) }) }, function immutableCachedObjects (done) { var blockchain = new Blockchain({validate: false}) // clone blocks[1] var testBlock = new Block(rlp.decode(rlp.encode(blocks[1].raw))) var cachedHash async.series([ (cb) => blockchain.putBlock(testBlock, (err) => { if (err) return done(err) cachedHash = testBlock.hash() cb() }), (cb) => { // change testBlock's extraData in order to modify its hash testBlock.header.extraData = Buffer.from([1]) //改變testBlock的extraData的資料,這將導致其hash值變化,但是它的變化並沒有改變儲存的區塊中的值,說明儲存物件是不可變的 blockchain.getBlock(1, (err, block) => { if (err) return done(err) t.equals(cachedHash.toString('hex'), block.hash().toString('hex'), 'should not modify cached objects') cb() }) } ], done) }, function getLatest (done) { var blockchain = new Blockchain({validate: false}) var headers = [new Block.Header(), new Block.Header()] headers[0].number = ethUtil.toBuffer(1) headers[0].difficulty = '0xfffffff' headers[0].parentHash = blocks[0].hash() headers[1].number = ethUtil.toBuffer(2) headers[1].difficulty = '0xfffffff' headers[1].parentHash = headers[0].hash() async.series([ // first, add some headers and make sure the latest block remains the same (cb) => blockchain.putHeaders(headers, (err) => { if (err) return cb(err) async.series([ (cb) => blockchain.getLatestHeader((err, header) => { if (err) return done(err) t.equals(header.hash().toString('hex'), headers[1].hash().toString('hex'), 'should update latest header') cb() }), (cb) => blockchain.getLatestBlock((err, block) => { if (err) return done(err) t.equals(block.hash().toString('hex'), blocks[0].hash().toString('hex'), 'should not change latest block')//此時header為1,block為0,說明下面應該新增區塊1 cb() }) ], cb) }), // then, add a full block and make sure the latest header remains the same (cb) => blockchain.putBlock(blocks[1], (err) => {//所以這裡就新增區塊1 if (err) return cb(err) async.series([ (cb) => blockchain.getLatestHeader((err, header) => { if (err) return done(err) t.equals(header.hash().toString('hex'), headers[1].hash().toString('hex'), 'should not change latest header') cb() }), (cb) => blockchain.getLatestBlock((err, block) => { if (err) return done(err) t.equals(block.hash().toString('hex'), blocks[1].hash().toString('hex'), 'should update latest block')//然後最新的區塊就變成了區塊1 cb() }) ], cb) }) ], done) }, function mismatchedChains (done) { var common = new Common('rinkeby') var blockchain = new Blockchain({common: common, validate: false}) var blocks = [ new Block(null, {common: common}), new Block(null, {chain: 'rinkeby'}), new Block(null, {chain: 'ropsten'})//連線的chain不同,這個區塊新增的過程會導致Chain mismatch的錯誤 ] blocks[0].setGenesisParams() blocks[1].header.number = 1 blocks[1].header.parentHash = blocks[0].hash() blocks[2].header.number = 2 blocks[2].header.parentHash = blocks[1].hash() async.eachOfSeries(blocks, (block, i, cb) => { if (i === 0) { blockchain.putGenesis(block, cb) } else { blockchain.putBlock(block, (err) => { if (i === 2) { t.ok(err.message.match('Chain mismatch'), 'should return chain mismatch error')//當new Block(null, {chain: 'ropsten'}區塊新增時會報錯 } else { t.error(err, 'should not return mismatch error') } cb() }) } }) } ], function (err) { if (err) { t.ok(false, err) } else { t.ok(true, 'no errors') } }) }) function isConsecutive (blocks) { var isConsecutive = true blocks.some(function (block, index) { if (index === 0) return false if (Buffer.compare(block.header.parentHash, blocks[index - 1].hash()) !== 0) { isConsecutive = false return true } }) return isConsecutive } function createTestDB (cb) { var genesis = new Block() genesis.setGenesisParams() var db = level() db.batch([{ type: 'put', key: Buffer.from('6800000000000000006e', 'hex'), keyEncoding: 'binary', valueEncoding: 'binary', value: genesis.hash() }, { type: 'put', key: Buffer.from('48d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3', 'hex'), keyEncoding: 'binary', valueEncoding: 'binary', value: Buffer.from('00', 'hex') }, { type: 'put', key: 'LastHeader', keyEncoding: 'binary', valueEncoding: 'binary', value: genesis.hash() }, { type: 'put', key: 'LastBlock', keyEncoding: 'binary', valueEncoding: 'binary', value: genesis.hash() }, { type: 'put', key: Buffer.from('680000000000000000d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3', 'hex'), keyEncoding: 'binary', valueEncoding: 'binary', value: ethUtil.rlp.encode(genesis.header.raw) }, { type: 'put', key: Buffer.from('680000000000000000d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa374', 'hex'), keyEncoding: 'binary', valueEncoding: 'binary', value: ethUtil.rlp.encode(new BN(17179869184).toBuffer()) }, { type: 'put', key: Buffer.from('620000000000000000d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3', 'hex'), keyEncoding: 'binary', valueEncoding: 'binary', value: ethUtil.rlp.encode(genesis.serialize(false).slice(1)) }, { type: 'put', key: 'heads', valueEncoding: 'json', value: { 'head0': { 'type': 'Buffer', 'data': [171, 205] } } }], (err) => { cb(err, db, genesis) }) }

 

執行:

npm run test

返回:

userdeMBP:ethereumjs-blockchain-master user$ npm run test

> [email protected]3.3.1 test /Users/user/ethereumjs-blockchain-master
> tape ./test/index.js

TAP version 13
# blockchain test
ok 1 should not crash on getting head of a blockchain without a genesis
ok 2 should throw on initialization with chain and common parameter
ok 3 correct genesis hash
ok 4 genesis blocks match
ok 5 support constructor with db parameter
ok 6 support blockDb and detailsDb params
ok 7 ignore detailsDb param
ok 8 genesis block hash should be correct
ok 9 should not validate a block incorrectly flagged as genesis
ok 10 added 10 blocks
ok 11 should get block by number
ok 12 should get block by hash
ok 13 should get 5 blocks
ok 14 blocks should be consecutive
ok 15 should get 5 blocks
ok 16 blocks should not be consecutive
ok 17 should get 4 blocks
ok 18 blocks should not be consecutive
ok 19 should get 10 blocks
ok 20 blocks should be consecutive
ok 21 should get 5 blocks
ok 22 blocks should be consecutive
ok 23 should get 5 blocks
ok 24 blocks should not be consecutive
ok 25 should get 4 blocks
ok 26 blocks should not be consecutive
ok 27 should get 10 blocks
ok 28 blocks should be consecutive
ok 29 should get 5 blocks
ok 30 blocks should be consecutive
ok 31 should get 5 blocks
ok 32 blocks should be consecutive
ok 33 should get 6 blocks
ok 34 blocks should be consecutive
ok 35 should get 3 blocks
ok 36 blocks should not be consecutive
ok 37 should find needed hash
ok 38 should iterate through 9 blocks
ok 39 should catch iterator func error
ok 40 should return correct error
ok 41 should not return error
ok 42 should finish iterating
ok 43 should get meta.rawHead
ok 44 should get meta.genesis
ok 45 should get meta.heads
ok 46 should update stale head
ok 47 should update stale headBlock
ok 48 should add new block in fork
ok 49 should delete fork block
ok 50 should reset headHeader
ok 51 should not change headBlock
ok 52 should delete blocks in canonical chain
ok 53 should have block 5 as head
ok 54 should delete block and children
ok 55 should have genesis as head
ok 56 should put multiple blocks at once
ok 57 should get head
ok 58 should get state root heads
ok 59 should validate genesisBlock
ok 60 should not validate an invalid block
ok 61 should add block with a body
ok 62 should perform _hashToNumber correctly
ok 63 should perform _numberToHash correctly
ok 64 should perform _getTd correctly
ok 65 should save headHeader
ok 66 should save headBlock
ok 67 should not modify cached objects
ok 68 should update latest header
ok 69 should not change latest block
ok 70 should not change latest header
ok 71 should update latest block
ok 72 should not return mismatch error
ok 73 should return chain mismatch error

1..73
# tests 73
# pass  73

# ok