ethereumjs/ethereumjs-blockchain-2-test
阿新 • • 發佈:2018-12-12
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