diff --git a/lib/block/block.js b/lib/block/block.js index e127797..1b067b8 100644 --- a/lib/block/block.js +++ b/lib/block/block.js @@ -43,18 +43,7 @@ Block._from = function _from(arg) { } else if (JSUtil.isValidJSON(arg)) { info = Block._fromJSON(arg); } else if (_.isObject(arg)) { - info = { - /** - * @name Block#header - * @type {BlockHeader} - */ - header: arg.header, - /** - * @name Block#transactions - * @type {Transaction[]} - */ - transactions: arg.transactions - }; + info = Block._fromObject(arg); } else { throw new TypeError('Unrecognized argument for Block'); } @@ -62,27 +51,35 @@ Block._from = function _from(arg) { }; /** - * @param {String|Object} - A JSON string or object + * @param {String} - A JSON string * @returns {Object} - An object representing block data * @private */ Block._fromJSON = function _fromJSON(data) { - if (JSUtil.isValidJSON(data)) { - data = JSON.parse(data); - } + $.checkArgument(JSUtil.isValidJSON(data), 'data must be valid JSON'); + data = JSON.parse(data); + return Block._fromObject(data); +}; + +/** + * @param {Object} - A plain javascript object + * @returns {Object} - An object representing block data + * @private + */ +Block._fromObject = function _fromObject(data) { var transactions = []; data.transactions.forEach(function(data) { transactions.push(Transaction().fromJSON(data)); }); var info = { - header: BlockHeader.fromJSON(data.header), + header: BlockHeader.fromObject(data.header), transactions: transactions }; return info; }; /** - * @param {String|Object} - A JSON string or object + * @param {String} - A JSON string * @returns {Block} - An instance of block */ Block.fromJSON = function fromJSON(json) { @@ -90,6 +87,15 @@ Block.fromJSON = function fromJSON(json) { return new Block(info); }; +/** + * @param {Object} - A plain javascript object + * @returns {Block} - An instance of block + */ +Block.fromObject = function fromObject(obj) { + var info = Block._fromObject(obj); + return new Block(info); +}; + /** * @param {BufferReader} - Block data * @returns {Object} - An object representing the block data diff --git a/lib/block/blockheader.js b/lib/block/blockheader.js index 01f2e81..c3280e3 100644 --- a/lib/block/blockheader.js +++ b/lib/block/blockheader.js @@ -7,6 +7,7 @@ var BufferReader = require('../encoding/bufferreader'); var BufferWriter = require('../encoding/bufferwriter'); var Hash = require('../crypto/hash'); var JSUtil = require('../util/js'); +var $ = require('../util/preconditions'); /** * Instantiate a BlockHeader from a Buffer, JSON object, or Object with @@ -37,14 +38,7 @@ BlockHeader._from = function _from(arg) { } else if (JSUtil.isValidJSON(arg)) { info = BlockHeader._fromJSON(arg); } else if (_.isObject(arg)) { - info = { - version: arg.version, - prevHash: arg.prevHash, - merkleRoot: arg.merkleRoot, - time: arg.time, - bits: arg.bits, - nonce: arg.nonce - }; + info = BlockHeader._fromObject(arg); } else { throw new TypeError('Unrecognized argument for BlockHeader'); } @@ -52,18 +46,35 @@ BlockHeader._from = function _from(arg) { }; /** - * @param {String|Object} - A JSON string or object + * @param {String} - A JSON string * @returns {Object} - An object representing block header data * @private */ BlockHeader._fromJSON = function _fromJSON(data) { - if (JSUtil.isValidJSON(data)) { - data = JSON.parse(data); + $.checkArgument(JSUtil.isValidJSON(data), 'data must be a valid JSON string'); + data = JSON.parse(data); + return BlockHeader._fromObject(data); +}; + +/** + * @param {Object} - A JSON string + * @returns {Object} - An object representing block header data + * @private + */ +BlockHeader._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + var prevHash = data.prevHash; + var merkleRoot = data.merkleRoot; + if (_.isString(data.prevHash)) { + prevHash = BufferUtil.reverse(new Buffer(data.prevHash, 'hex')); + } + if (_.isString(data.merkleRoot)) { + merkleRoot = BufferUtil.reverse(new Buffer(data.merkleRoot, 'hex')); } var info = { version: data.version, - prevHash: new Buffer(data.prevHash, 'hex'), - merkleRoot: new Buffer(data.merkleRoot, 'hex'), + prevHash: prevHash, + merkleRoot: merkleRoot, time: data.time, timestamp: data.time, bits: data.bits, @@ -73,7 +84,7 @@ BlockHeader._fromJSON = function _fromJSON(data) { }; /** - * @param {String|Object} - A JSON string or object + * @param {String} - A JSON string or object * @returns {BlockHeader} - An instance of block header */ BlockHeader.fromJSON = function fromJSON(json) { @@ -81,6 +92,15 @@ BlockHeader.fromJSON = function fromJSON(json) { return new BlockHeader(info); }; +/** + * @param {Object} - A plain javascript object + * @returns {BlockHeader} - An instance of block header + */ +BlockHeader.fromObject = function fromObject(obj) { + var info = BlockHeader._fromObject(obj); + return new BlockHeader(info); +}; + /** * @param {Binary} - Raw block binary data or buffer * @returns {BlockHeader} - An instance of block header @@ -144,8 +164,8 @@ BlockHeader.fromBufferReader = function fromBufferReader(br) { BlockHeader.prototype.toObject = function toObject() { return { version: this.version, - prevHash: this.prevHash.toString('hex'), - merkleRoot: this.merkleRoot.toString('hex'), + prevHash: BufferUtil.reverse(this.prevHash).toString('hex'), + merkleRoot: BufferUtil.reverse(this.merkleRoot).toString('hex'), time: this.time, bits: this.bits, nonce: this.nonce @@ -216,8 +236,8 @@ var idProperty = { writeable: false, enumerable: true, /** - * @returns {string} - The big endian hash buffer of the header - */ + * @returns {string} - The big endian hash buffer of the header + */ get: function() { if (!this._id) { this._id = BufferReader(this._getHash()).readReverse().toString('hex'); diff --git a/lib/block/merkleblock.js b/lib/block/merkleblock.js index 331d8c1..279209e 100644 --- a/lib/block/merkleblock.js +++ b/lib/block/merkleblock.js @@ -288,7 +288,7 @@ MerkleBlock._fromJSON = function _fromJSON(data) { data = JSON.parse(data); } var info = { - header: BlockHeader.fromJSON(data.header), + header: BlockHeader.fromObject(data.header), numTransactions: data.numTransactions, hashes: data.hashes, flags: data.flags, diff --git a/lib/transaction/input/input.js b/lib/transaction/input/input.js index b9c0d74..223954f 100644 --- a/lib/transaction/input/input.js +++ b/lib/transaction/input/input.js @@ -55,13 +55,16 @@ Input.prototype._fromObject = function(params) { }; Input.prototype.toObject = function toObject() { - return { + var obj = { prevTxId: this.prevTxId.toString('hex'), outputIndex: this.outputIndex, sequenceNumber: this.sequenceNumber, script: this.script.toString(), - output: this.output ? this.output.toObject() : undefined }; + if (this.output) { + obj.output = this.output.toObject(); + } + return obj; }; Input.fromObject = function(obj) { diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 09ccc1a..5d0713c 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -320,6 +320,9 @@ Transaction.prototype.toObject = function toObject() { Transaction.prototype.fromObject = function(transaction) { var self = this; + if (transaction instanceof Transaction) { + transaction = transaction.toObject(); + } _.each(transaction.inputs, function(input) { if (!input.output || !input.output.script) { self.uncheckedAddInput(new Input(input)); @@ -595,6 +598,7 @@ Transaction.prototype.hasAllUtxoInfo = function() { * @return {Transaction} this, for chaining */ Transaction.prototype.fee = function(amount) { + $.checkArgument(_.isNumber(amount), 'amount must be a number'); this._fee = amount; this._updateChangeOutput(); return this; diff --git a/test/block.js b/test/block.js index 5e7c52c..9ed2848 100644 --- a/test/block.js +++ b/test/block.js @@ -24,7 +24,7 @@ describe('Block', function() { var blockbuf = new Buffer(blockhex, 'hex'); var bh = BlockHeader.fromBuffer(new Buffer(data.blockheaderhex, 'hex')); var txs = []; - JSON.parse(dataJson).transactions.forEach(function(tx){ + JSON.parse(dataJson).transactions.forEach(function(tx) { txs.push(new Transaction().fromJSON(tx)); }); var json = dataJson; @@ -32,6 +32,9 @@ describe('Block', function() { var genesishex = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'; var genesisbuf = new Buffer(genesishex, 'hex'); var genesisidhex = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'; + var blockOneHex = '010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000'; + var blockOneBuf = new Buffer(blockOneHex, 'hex'); + var blockOneId = '00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048'; it('should make a new block', function() { var b = Block(blockbuf); @@ -56,7 +59,7 @@ describe('Block', function() { }); it('should properly deserialize blocks', function() { - dataBlocks.forEach(function(block){ + dataBlocks.forEach(function(block) { var b = Block.fromBuffer(new Buffer(block.data, 'hex')); b.transactions.length.should.equal(block.transactions); }); @@ -95,11 +98,6 @@ describe('Block', function() { should.exist(block.transactions); }); - it('accepts an object as argument', function() { - var block = Block.fromRawBlock(dataRawBlockBuffer); - Block.fromJSON(block.toObject()).should.exist(); - }); - }); describe('#toJSON', function() { @@ -164,6 +162,47 @@ describe('Block', function() { }); + describe('#toObject', function() { + + it('should recover a block from genesis block buffer', function() { + var block = Block.fromBuffer(blockOneBuf); + block.id.should.equal(blockOneId); + block.toObject().should.deep.equal({ + header: { + version: 1, + prevHash: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', + merkleRoot: '0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098', + time: 1231469665, + bits: 486604799, + nonce: 2573394689 + }, + transactions: [{ + version: 1, + inputs: [{ + prevTxId: '0000000000000000000000000000000000000000000000000000000000000000', + outputIndex: 4294967295, + sequenceNumber: 4294967295, + script: '4 0xffff001d 1 0x04' + }], + outputs: [{ + satoshis: 5000000000, + script: '65 0x0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be' + + '63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee OP_CHECKSIG' + }], + nLockTime: 0 + }] + }); + }); + + it('roundtrips correctly', function() { + var block = Block.fromBuffer(blockOneBuf); + var obj = block.toObject(); + var block2 = Block.fromObject(obj); + block2.toObject().should.deep.equal(block.toObject()); + }); + + }); + describe('#_getHash', function() { it('should return the correct hash of the genesis block', function() { @@ -190,7 +229,7 @@ describe('Block', function() { it('should return the correct inspect of the genesis block', function() { var block = Block.fromBuffer(genesisbuf); - block.inspect().should.equal(''); + block.inspect().should.equal(''); }); }); diff --git a/test/blockheader.js b/test/blockheader.js index 7341722..16dbee6 100644 --- a/test/blockheader.js +++ b/test/blockheader.js @@ -68,14 +68,14 @@ describe('BlockHeader', function() { describe('#fromJSON', function() { it('should set all the variables', function() { - var bh = BlockHeader.fromJSON({ + var bh = BlockHeader.fromJSON(JSON.stringify({ version: version, prevHash: prevblockidbuf.toString('hex'), merkleRoot: merklerootbuf.toString('hex'), time: time, bits: bits, nonce: nonce - }); + })); should.exist(bh.version); should.exist(bh.prevHash); should.exist(bh.merkleRoot); diff --git a/test/data/merkleblocks.js b/test/data/merkleblocks.js index b6bff4d..2465104 100644 --- a/test/data/merkleblocks.js +++ b/test/data/merkleblocks.js @@ -28,8 +28,8 @@ module.exports = { { // Mainnet Block 100014 header: { version: 1, - prevHash: "82bb869cf3a793432a66e826e05a6fc37469f8efb7421dc88067010000000000", - merkleRoot: "7f16c5962e8bd963659c793ce370d95f093bc7e367117b3c30c1f8fdd0d97287", + prevHash: "0000000000016780c81d42b7eff86974c36f5ae026e8662a4393a7f39c86bb82", + merkleRoot: "8772d9d0fdf8c1303c7b1167e3c73b095fd970e33c799c6563d98b2e96c5167f", time: 1293629558, bits: 453281356, nonce: 151839121 @@ -46,8 +46,8 @@ module.exports = { { // Mainnet Block 12363 header: { version: 1, - prevHash: "f8aeda5ec9058d406dc0c3ad73cd8761accfd07cdc5ee055a0e6c3ac00000000", - merkleRoot: "d008a007400650a08aeb612ef60a1f7c72961a8a578093feaf2fb6737a3e3167", + prevHash: "00000000acc3e6a055e05edc7cd0cfac6187cd73adc3c06d408d05c95edaaef8", + merkleRoot: "67313e7a73b62faffe9380578a1a96727c1f0af62e61eb8aa050064007a008d0", time: 1240800408, nonce: 2506812214, bits: 486604799, @@ -443,12 +443,12 @@ module.exports = { "036bf6944a47791471e9a2cb86615de837f3aa234a7d1cd024026b3e1daee79e" ], header : { - merkleRoot : "cb83ce96b3202ecc3e6cca64c7111f54bf31b0f48e83d253c6ae959c12402f79", + prevHash : "000000000000000124f6ce137a43bb288d63cc84f9847033cb84595ead05f9de", + merkleRoot : "792f40129c95aec653d2838ef4b031bf541f11c764ca6c3ecc2e20b396ce83cb", time : 1389715824, version : 2, nonce : 322045839, bits : 419587686, - prevHash : "def905ad5e5984cb337084f984cc638d28bb437a13cef6240100000000000000" } } ]