From fe0c8e8b774a02da076b241a71ec1a4baf27df2e Mon Sep 17 00:00:00 2001 From: Vinay Pulim Date: Wed, 23 May 2018 12:43:12 -0400 Subject: [PATCH] Add getLatestHeader() and getLatestBlock() methods, fix saveHeads() bug and update API docs --- README.md | 23 +++++++++++---- index.js | 80 ++++++++++++++++++++++++++++++++++++++++----------- test/index.js | 23 ++++++++++++++- 3 files changed, 104 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 00ba1ac..e716273 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ new Blockchain({db: db}).iterator('i', (block, reorg, cb) => { - [`BlockChain` methods](#blockchain-methods) - [`blockchain.putGenesis(genesis, [cb])`](#blockchainputgenesisgenesis-cb) - [`blockchain.getHead(name, [cb])`](#blockchaingetheadname-cb) + - [`blockchain.getLatestHeader([cb])`](#blockchaingetlatestheadercb) + - [`blockchain.getLatestBlock([cb])`](#blockchaingetlatestblockcb) - [`blockchain.putBlocks(blocks, [cb])`](#blockchainputblocksblocks-cb) - [`blockchain.putBlock(block, [cb])`](#blockchainputblockblock-cb) - [`blockchain.getBlock(hash, [cb])`](#blockchaingetblockhash-cb) @@ -50,7 +52,7 @@ new Blockchain({db: db}).iterator('i', (block, reorg, cb) => { Implements functions for retrieving, manipulating and storing Ethereum's blockchain ### `new Blockchain(opts)` -Creates new Blockchain object +Creates new Blockchain object - `opts.db` - Database to store blocks and metadata. Should be a [levelup](https://github.com/rvagg/node-levelup) instance. - `opts.validate` - this the flag to validate blocks (e.g. Proof-of-Work). @@ -69,12 +71,24 @@ Puts the genesis block in the database. -------------------------------------------------------- #### `blockchain.getHead(name, cb)` -Returns that head block. +Returns the specified iterator head. - `name` - Optional name of the state root head (default: 'vm') - `cb` - the callback. It is given two parameters `err` and the returned `block` -------------------------------------------------------- +#### `blockchain.getLatestHeader(cb)` +Returns the latest header in the canonical chain. +- `cb` - the callback. It is given two parameters `err` and the returned `header` + +-------------------------------------------------------- + +#### `blockchain.getLatestBlock(cb)` +Returns the latest full block in the canonical chain. +- `cb` - the callback. It is given two parameters `err` and the returned `block` + +-------------------------------------------------------- + #### `blockchain.putBlocks(blocks, cb)` Adds many blocks to the blockchain. - `blocks` - the blocks to be added to the blockchain @@ -91,7 +105,7 @@ Adds a block to the blockchain. #### `blockchain.getBlock(blockTag, cb)` Gets a block by its blockTag. - `blockTag` - the block's hash or number -- `cb` - the callback. It is given two parameters `err` and the found `block` (an instance of https://github.com/ethereumjs/ethereumjs-block) if any. +- `cb` - the callback. It is given two parameters `err` and the found `block` (an instance of https://github.com/ethereumjs/ethereumjs-block) if any. -------------------------------------------------------- @@ -113,7 +127,7 @@ Looks up many blocks relative to blockId. #### `blockchain.selectNeededHashes(hashes, cb)` Given an ordered array, returns to the callback an array of hashes that are not in the blockchain yet. - `hashes` - Ordered array of hashes -- `cb` - the callback. It is given two parameters `err` and hashes found. +- `cb` - the callback. It is given two parameters `err` and hashes found. -------------------------------------------------------- @@ -135,4 +149,3 @@ Iterates through blocks starting at the specified verified state root head and c Tests can be found in the ``test`` directory and run with ``npm run test``. These can also be valuable as examples/inspiration on how to run the library and invoke different parts of the API. - diff --git a/index.js b/index.js index e5a9e18..5273453 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,5 @@ 'use strict' -/** - * NOTES - * meta.rawHead is the the head of the chain with the most POW - * meta.head is the head of the chain that has had its state root verifed - */ const async = require('async') const Stoplight = require('flow-stoplight') const semaphore = require('semaphore') @@ -86,8 +81,8 @@ Blockchain.prototype = { /** * Fetches the meta info about the blockchain from the db. Meta info contains - * hashes of the headerchain head, blockchain head, genesis block and verified - * state root heads. + * hashes of the headerchain head, blockchain head, genesis block and iterator + * heads. * @method _init */ Blockchain.prototype._init = function (cb) { @@ -113,8 +108,9 @@ Blockchain.prototype._init = function (cb) { function getHeads (genesisHash, cb) { self._genesis = genesisHash async.series([ - // load verified state root heads + // load verified iterator heads (cb) => self.db.get('heads', { + keyEncoding: 'binary', valueEncoding: 'json' }, (err, heads) => { if (err) heads = {} @@ -124,6 +120,7 @@ Blockchain.prototype._init = function (cb) { }), // load headerchain head (cb) => self.db.get(headHeaderKey, { + keyEncoding: 'binary', valueEncoding: 'binary' }, (err, hash) => { self._headHeader = err ? genesisHash : hash @@ -131,6 +128,7 @@ Blockchain.prototype._init = function (cb) { }), // load blockchain head (cb) => self.db.get(headBlockKey, { + keyEncoding: 'binary', valueEncoding: 'binary' }, (err, hash) => { self._headBlock = err ? genesisHash : hash @@ -161,8 +159,9 @@ Blockchain.prototype.putGenesis = function (genesis, cb) { } /** - * Returns that head block + * Returns the specified iterator head. * @method getHead + * @param name name of the head (default: 'vm') * @param cb Function the callback */ Blockchain.prototype.getHead = function (name, cb) { @@ -185,6 +184,37 @@ Blockchain.prototype.getHead = function (name, cb) { }) } +/** + * Returns the latest header in the canonical chain. + * @method getLatestHeader + * @param cb Function the callback + */ +Blockchain.prototype.getLatestHeader = function (cb) { + const self = this + + // ensure init completed + self._initLock.await(function runGetLatestHeader () { + self.getBlock(self._headHeader, (err, block) => { + if (err) return cb(err) + cb(null, block.header) + }) + }) +} + +/** + * Returns the latest full block in the canonical chain. + * @method getLatestBlock + * @param cb Function the callback + */ +Blockchain.prototype.getLatestBlock = function (cb) { + const self = this + + // ensure init completed + self._initLock.await(function runGetLatestBlock () { + self.getBlock(self._headBlock, cb) + }) +} + /** * Adds many blocks to the blockchain * @method putBlocks @@ -518,9 +548,26 @@ Blockchain.prototype.selectNeededHashes = function (hashes, cb) { } Blockchain.prototype._saveHeads = function (cb) { - this.db.put('heads', this._heads, { - keyEncoding: 'json' - }, cb) + var dbOps = [{ + type: 'put', + key: 'heads', + keyEncoding: 'binary', + valueEncoding: 'json', + value: this._heads + }, { + type: 'put', + key: headHeaderKey, + keyEncoding: 'binary', + valueEncoding: 'binary', + value: this._headHeader + }, { + type: 'put', + key: headBlockKey, + keyEncoding: 'binary', + valueEncoding: 'binary', + value: this._headBlock + }] + this._batchDbOps(dbOps, cb) } // delete canonical number assignments for specified number and above @@ -537,7 +584,7 @@ Blockchain.prototype._deleteStaleAssignments = function (number, headHash, ops, }) self._cache.numberToHash.del(key) - // reset stale verified state root heads to current canonical head + // reset stale iterator heads to current canonical head Object.keys(self._heads).forEach(function (name) { if (self._heads[name].equals(hash)) { self._heads[name] = headHash @@ -751,10 +798,11 @@ Blockchain.prototype._delChild = function (hash, number, headHash, ops, cb) { } /** - * Iterates through blocks starting at the specified verified state root head - * and calls the onBlock function on each block + * Iterates through blocks starting at the specified iterator head and calls + * the onBlock function on each block. The current location of an iterator head + * can be retrieved using the `getHead()`` method * @method iterator - * @param {String} name - the name of the verified state root head + * @param {String} name - the name of the iterator head * @param {function} onBlock - function called on each block with params (block, reorg, cb) * @param {function} cb - a callback function */ diff --git a/test/index.js b/test/index.js index 0dfee59..ced9fa9 100644 --- a/test/index.js +++ b/test/index.js @@ -11,7 +11,7 @@ const testData = require('./testdata.json') const BN = require('bn.js') test('blockchain test', function (t) { - t.plan(57) + t.plan(59) var blockchain = new Blockchain() var genesisBlock var blocks = [] @@ -343,6 +343,27 @@ test('blockchain test', function (t) { }) ], done) }) + }, + function saveHeads (done) { + var db = levelup('', { db: memdown }) + var blockchain = new Blockchain({db: db, validate: false}) + + blockchain.putBlock(blocks[1], (err) => { + if (err) return done(err) + blockchain = new Blockchain({db: db, validate: false}) + async.series([ + (cb) => blockchain.getLatestHeader((err, header) => { + if (err) return done(err) + t.equals(header.hash().toString('hex'), blocks[1].hash().toString('hex'), 'should get latest header') + cb() + }), + (cb) => blockchain.getLatestBlock((err, headBlock) => { + if (err) return done(err) + t.equals(headBlock.hash().toString('hex'), blocks[1].hash().toString('hex'), 'should get latest block') + cb() + }) + ], done) + }) } ], function (err) { if (err) {