diff --git a/index.js b/index.js index b79ad06..3a82d95 100644 --- a/index.js +++ b/index.js @@ -30,6 +30,7 @@ bitcore.errors = require('./lib/errors'); // main bitcoin library bitcore.Address = require('./lib/address'); bitcore.Block = require('./lib/block'); +bitcore.MerkleBlock = require('./lib/block/merkleblock'); bitcore.BlockHeader = require('./lib/blockheader'); bitcore.HDPrivateKey = require('./lib/hdprivatekey.js'); bitcore.HDPublicKey = require('./lib/hdpublickey.js'); diff --git a/lib/block/merkleblock.js b/lib/block/merkleblock.js new file mode 100644 index 0000000..d7cccdf --- /dev/null +++ b/lib/block/merkleblock.js @@ -0,0 +1,176 @@ +'use strict'; +var _ = require('lodash'); +var BlockHeader = require('../blockheader'); +var BufferUtil = require('../util/buffer'); +var BufferReader = require('../encoding/bufferreader'); +var BufferWriter = require('../encoding/bufferwriter'); +var JSUtil = require('../util/js'); +var $ = require('../util/preconditions'); + +/** + * Instantiate a MerkleBlock from a Buffer, JSON object, or Object with + * the properties of the Block + * + * @param {*} - A Buffer, JSON string, or Object representing a MerkleBlock + * @returns {MerkleBlock} + * @constructor + */ +function MerkleBlock(arg) { + if (!(this instanceof MerkleBlock)) { + return new MerkleBlock(arg); + } + + var info = {}; + if (BufferUtil.isBuffer(arg)) { + info = MerkleBlock._fromBufferReader(BufferReader(arg)); + } else if (JSUtil.isValidJSON(arg)) { + info = MerkleBlock._fromJSON(arg); + } else if (_.isObject(arg)) { + var header; + if(arg.header instanceof BlockHeader) { + header = arg.header + } else { + header = BlockHeader.fromJSON(JSON.stringify(arg.header)); + } + info = { + /** + * @name MerkleBlock#header + * @type {BlockHeader} + */ + header: header, + /** + * @name MerkleBlock#numTransactions + * @type {Number} + */ + numTransactions: arg.numTransactions, + /** + * @name MerkleBlock#hashes + * @type {String[]} + */ + hashes: arg.hashes, + /** + * @name MerkleBlock#flags + * @type {Number[]} + */ + flags: arg.flags + }; + } else { + throw new TypeError('Unrecognized argument for Block'); + } + _.extend(this,info); + return this; +} + +/** + * @param {Buffer} - MerkleBlock data in a Buffer object + * @returns {MerkleBlock} - A MerkleBlock object + */ +MerkleBlock.fromBuffer = function fromBuffer(buf) { + return MerkleBlock.fromBufferReader(BufferReader(buf)); +} + +/** + * @param {BufferReader} - MerkleBlock data in a BufferReader object + * @returns {MerkleBlock} - A MerkleBlock object + */ +MerkleBlock.fromBufferReader = function fromBufferReader(br) { + return new MerkleBlock(MerkleBlock._fromBufferReader(br)); +} + +/** + * @param {String|Object} - A JSON String or Object + * @returns {MerkleBlock} - A MerkleBlock object + */ +MerkleBlock.fromJSON = function fromJSON(buf) { + return new MerkleBlock(MerkleBlock._fromJSON(buf)); +} + +/** + * @returns {Buffer} - A buffer of the block + */ +MerkleBlock.prototype.toBuffer = function toBuffer() { + return this.toBufferWriter().concat(); +}; + +/** + * @param {BufferWriter} - An existing instance of BufferWriter + * @returns {BufferWriter} - An instance of BufferWriter representation of the MerkleBlock + */ +MerkleBlock.prototype.toBufferWriter = function toBufferWriter(bw) { + if (!bw) { + bw = new BufferWriter(); + } + bw.write(this.header.toBuffer()); + bw.writeUInt32LE(this.numTransactions); + bw.writeVarintNum(this.hashes.length); + for (var i = 0; i < this.hashes.length; i++) { + bw.write(new Buffer(this.hashes[i], 'hex')); + } + bw.writeVarintNum(this.flags.length); + for (var i = 0; i < this.flags.length; i++) { + bw.writeUInt8(this.flags[i]); + } + return bw; +}; + +/** + * @returns {Object} - A plain object with the MerkleBlock properties + */ +MerkleBlock.prototype.toObject = function toObject() { + return { + header: this.header.toObject(), + numTransactions: this.numTransactions, + hashes: this.hashes, + flags: this.flags + }; +}; + +/** + * @returns {String} - A JSON string of a MerkleBlock + */ +MerkleBlock.prototype.toJSON = function toJSON() { + return JSON.stringify(this.toObject()); +}; + +/** + * @param {Buffer} - MerkleBlock data + * @returns {Object} - An Object representing merkleblock data + * @private + */ +MerkleBlock._fromBufferReader = function _fromBufferReader(br) { + $.checkState(!br.finished(), 'No merkleblock data received'); + var info = {}; + info.header = BlockHeader.fromBufferReader(br); + info.numTransactions = br.readUInt32LE(); + var numHashes = br.readVarintNum(); + info.hashes = []; + for (var i = 0; i < numHashes; i++) { + info.hashes.push(br.read(32).toString('hex')); + } + var numFlags = br.readVarintNum(); + info.flags = []; + for (var i = 0; i < numFlags; i++) { + info.flags.push(br.readUInt8()); + } + return info; +}; + +/** + * @param {String|Object} - A JSON or String Object + * @returns {Object} - An Object representing merkleblock data + * @private + */ +MerkleBlock._fromJSON = function _fromJSON(data) { + if (JSUtil.isValidJSON(data)) { + data = JSON.parse(data); + } + var info = { + header: BlockHeader.fromJSON(data.header), + numTransactions: data.numTransactions, + hashes: data.hashes, + flags: data.flags, + }; + return info; +}; + +module.exports = MerkleBlock; diff --git a/test/data/merkleblocks.js b/test/data/merkleblocks.js new file mode 100644 index 0000000..efc6d55 --- /dev/null +++ b/test/data/merkleblocks.js @@ -0,0 +1,44 @@ +'use strict'; + +module.exports = { + HEX: [ + // Mainnet Block 100015 + "01000000" + // Version + "82bb869cf3a793432a66e826e05a6fc37469f8efb7421dc88067010000000000" + // prevHash + "7f16c5962e8bd963659c793ce370d95f093bc7e367117b3c30c1f8fdd0d97287" + // MerkleRoot + "76381b4d" + // Time + "4c86041b" + // Bits + "554b8529" + // Nonce + "07000000" + // Transaction Count + "04" + // Hash Count + "3612262624047ee87660be1a707519a443b1c1ce3d248cbfc6c15870f6c5daa2" + // Hash1 + "019f5b01d4195ecbc9398fbf3c3b1fa9bb3183301d7a1fb3bd174fcfa40a2b65" + // Hash2 + "41ed70551dd7e841883ab8f0b16bf04176b7d1480e4f0af9f3d4c3595768d068" + // Hash3 + "20d2a7bc994987302e5b1ac80fc425fe25f8b63169ea78e68fbaaefa59379bbf" + // Hash4 + "01" + // Num Flag Bytes + "1d" // Flags + ], + JSON: [ + { // Mainnet Block 100015 + header: { + version: 1, + prevHash: "82bb869cf3a793432a66e826e05a6fc37469f8efb7421dc88067010000000000", + merkleRoot: "7f16c5962e8bd963659c793ce370d95f093bc7e367117b3c30c1f8fdd0d97287", + time: 1293629558, + bits: 453281356, + nonce: 151839121 + }, + numTransactions: 7, + hashes: [ + "3612262624047ee87660be1a707519a443b1c1ce3d248cbfc6c15870f6c5daa2", + "019f5b01d4195ecbc9398fbf3c3b1fa9bb3183301d7a1fb3bd174fcfa40a2b65", + "41ed70551dd7e841883ab8f0b16bf04176b7d1480e4f0af9f3d4c3595768d068", + "20d2a7bc994987302e5b1ac80fc425fe25f8b63169ea78e68fbaaefa59379bbf" + ], + flags: [ 29 ] + } + + + + ] +}; diff --git a/test/merkleblock.js b/test/merkleblock.js new file mode 100644 index 0000000..21788e6 --- /dev/null +++ b/test/merkleblock.js @@ -0,0 +1,132 @@ +'use strict'; + +var bitcore = require('..'), + BufferUtil = bitcore.util.buffer, + MerkleBlock = bitcore.MerkleBlock, + BlockHeader = bitcore.BlockHeader, + BufferReader = bitcore.encoding.BufferReader, + BufferWriter = bitcore.encoding.BufferWriter, + data = require('./data/merkleblocks.js'); + +describe('MerkleBlock', function() { + var blockhex = data.HEX[0]; + var blockbuf = new Buffer(blockhex,'hex'); + var blockJSON = JSON.stringify(data.JSON[0]); + var blockObject = JSON.parse(JSON.stringify(data.JSON[0])); + + describe('#constructor', function() { + it('should make a new merkleblock from buffer', function() { + var b = MerkleBlock(blockbuf); + b.toBuffer().toString('hex').should.equal(blockhex); + }); + + it('should make a new merkleblock from object', function() { + var b = MerkleBlock(blockObject); + b.toObject().should.deep.equal(blockObject); + }); + + it('should make a new merkleblock from JSON', function() { + var b = MerkleBlock(blockJSON); + b.toJSON().should.equal(blockJSON); + }); + + it('should not make an empty block', function() { + (function() { + return new MerkleBlock(); + }).should.throw('Unrecognized argument for Block'); + }); + }); + + describe('#fromJSON', function() { + + it('should set these known values', function() { + var block = MerkleBlock.fromJSON(blockJSON); + should.exist(block.header); + should.exist(block.numTransactions); + should.exist(block.hashes); + should.exist(block.flags); + }); + + it('should set these known values', function() { + var block = MerkleBlock(blockJSON); + should.exist(block.header); + should.exist(block.numTransactions); + should.exist(block.hashes); + should.exist(block.flags); + }); + + it('accepts an object as argument', function() { + var block = MerkleBlock(blockbuf); + MerkleBlock.fromJSON(block.toObject()).should.exist(); + }); + + }); + + describe('#toJSON', function() { + + it('should recover these known values', function() { + var block = MerkleBlock.fromJSON(blockJSON); + var b = JSON.parse(block.toJSON()); + should.exist(block.header); + should.exist(block.numTransactions); + should.exist(block.hashes); + should.exist(block.flags); + }); + + }); + + // TODO + //describe('#fromString/#toString', function() { + + //it('should output/input a block hex string', function() { + //var b = MerkleBlock.fromString(blockhex); + //b.toString().should.equal(blockhex); + //}); + + //}); + + describe('#fromBuffer', function() { + + it('should make a block from this known buffer', function() { + var block = MerkleBlock.fromBuffer(blockbuf); + block.toBuffer().toString('hex').should.equal(blockhex); + }); + + }); + + describe('#fromBufferReader', function() { + + it('should make a block from this known buffer', function() { + var block = MerkleBlock.fromBufferReader(BufferReader(blockbuf)); + block.toBuffer().toString('hex').should.equal(blockhex); + }); + + }); + + describe('#toBuffer', function() { + + it('should recover a block from this known buffer', function() { + var block = MerkleBlock.fromBuffer(blockbuf); + block.toBuffer().toString('hex').should.equal(blockhex); + }); + + }); + + describe('#toBufferWriter', function() { + + it('should recover a block from this known buffer', function() { + var block = MerkleBlock.fromBuffer(blockbuf); + block.toBufferWriter().concat().toString('hex').should.equal(blockhex); + }); + + it('doesn\'t create a bufferWriter if one provided', function() { + var writer = new BufferWriter(); + var block = MerkleBlock.fromBuffer(blockbuf); + block.toBufferWriter(writer).should.equal(writer); + }); + + }); + + +}); +