diff --git a/lib/bcoin/abstractblock.js b/lib/bcoin/abstractblock.js index 11ee092d..ce219333 100644 --- a/lib/bcoin/abstractblock.js +++ b/lib/bcoin/abstractblock.js @@ -37,6 +37,28 @@ function AbstractBlock(data) { if (!(this instanceof AbstractBlock)) return new AbstractBlock(data); + this.version = 1; + this.prevBlock = null; + this.merkleRoot = null; + this.ts = 0; + this.bits = 0; + this.nonce = 0; + this.totalTX = 0; + this.height = -1; + + this.txs = null; + this.mutable = false; + + this._valid = null; + this._hash = null; + this._size = null; + this._witnessSize = null; + + if (data) + this.parseOptions(data); +} + +AbstractBlock.prototype.parseOptions = function parseOptions(data) { assert(data, 'Block data is required.'); assert(typeof data.version === 'number'); assert(typeof data.prevBlock === 'string'); @@ -61,7 +83,7 @@ function AbstractBlock(data) { this._hash = null; this._size = null; this._witnessSize = null; -} +}; /** * Hash the block headers. @@ -86,8 +108,20 @@ AbstractBlock.prototype.hash = function hash(enc) { * @returns {Buffer} */ -AbstractBlock.prototype.abbr = function abbr() { - return bcoin.protocol.framer.blockHeaders(this); +AbstractBlock.prototype.abbr = function abbr(writer) { + var p = bcoin.writer(writer); + + p.write32(this.version); + p.writeHash(this.prevBlock); + p.writeHash(this.merkleRoot); + p.writeU32(this.ts); + p.writeU32(this.bits); + p.writeU32(this.nonce); + + if (!writer) + p = p.render(); + + return p; }; /** diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 5ee84f28..4c37c68d 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -37,8 +37,6 @@ var constants = bcoin.protocol.constants; */ function Block(data) { - var i; - if (!(this instanceof Block)) return new Block(data); @@ -49,6 +47,24 @@ function Block(data) { this._cbHeight = null; this._commitmentHash = null; + this._raw = null; + this._size = null; + this._witnessSize = null; + + if (data) + this.fromOptions(data); +} + +utils.inherits(Block, bcoin.abstractblock); + +Block.prototype.fromOptions = function fromOptions(data) { + var i; + + this.txs = []; + + this._cbHeight = null; + this._commitmentHash = null; + this._raw = data._raw || null; this._size = data._size || null; this._witnessSize = data._witnessSize != null ? data._witnessSize : null; @@ -57,17 +73,19 @@ function Block(data) { for (i = 0; i < data.txs.length; i++) this.addTX(data.txs[i]); } -} +}; -utils.inherits(Block, bcoin.abstractblock); +Block.fromOptions = function fromOptions(data) { + return new Block().fromOptions(data); +}; /** * Serialize the block. Include witnesses if present. * @returns {Buffer} */ -Block.prototype.render = function render() { - return this.getRaw(); +Block.prototype.render = function render(writer) { + return this.getRaw(writer); }; /** @@ -75,10 +93,10 @@ Block.prototype.render = function render() { * @returns {Buffer} */ -Block.prototype.renderNormal = function renderNormal() { +Block.prototype.renderNormal = function renderNormal(writer) { if (!this.hasWitness()) - return this.getRaw(); - return bcoin.protocol.framer.block(this); + return this.getRaw(writer); + return this.frameNormal(writer); }; /** @@ -86,10 +104,10 @@ Block.prototype.renderNormal = function renderNormal() { * @returns {Buffer} */ -Block.prototype.renderWitness = function renderWitness() { +Block.prototype.renderWitness = function renderWitness(writer) { if (this.hasWitness()) - return this.getRaw(); - return bcoin.protocol.framer.witnessBlock(this); + return this.getRaw(writer); + return this.frameWitness(writer); }; /** @@ -98,19 +116,25 @@ Block.prototype.renderWitness = function renderWitness() { * @returns {Buffer} */ -Block.prototype.getRaw = function getRaw() { +Block.prototype.getRaw = function getRaw(writer) { var raw; if (this._raw) { assert(this._size > 0); assert(this._witnessSize >= 0); + if (writer) { + writer.writeBytes(this._raw); + writer._witnessSize = this._raw._witnessSize; + return writer; + } return this._raw; } - if (this.hasWitness()) - raw = bcoin.protocol.framer.witnessBlock(this); - else - raw = bcoin.protocol.framer.block(this); + raw = this.frameWitness(); + // if (this.hasWitness()) + // raw = this.frameWitness(); + // else + // raw = this.frameNormal(); if (!this.mutable) { this._size = raw.length; @@ -118,6 +142,12 @@ Block.prototype.getRaw = function getRaw() { this._raw = raw; } + if (writer) { + writer.writeBytes(raw); + writer._witnessSize = raw._witnessSize; + return writer; + } + return raw; }; @@ -127,6 +157,8 @@ Block.prototype.getRaw = function getRaw() { */ Block.prototype.getSizes = function getSizes() { + var writer; + if (this._size != null) { return { size: this._size, @@ -142,7 +174,13 @@ Block.prototype.getSizes = function getSizes() { }; } - return bcoin.protocol.framer.block.sizes(this); + writer = new bcoin.writer(); + this.render(writer); + + return { + size: writer.written, + witnessSize: writer._witnessSize + }; }; /** @@ -619,7 +657,7 @@ Block.parseJSON = function parseJSON(json) { json.prevBlock = utils.revHex(json.prevBlock); json.merkleRoot = utils.revHex(json.merkleRoot); json.txs = json.txs.map(function(tx) { - return bcoin.tx.parseJSON(tx); + return bcoin.tx.fromJSON(tx); }); return json; }; @@ -657,11 +695,34 @@ Block.prototype.toRaw = function toRaw(enc) { * @returns {Object} A "naked" block object. */ -Block.parseRaw = function parseRaw(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); +Block.prototype.fromRaw = function fromRaw(data) { + var p = bcoin.reader(data); + var i, tx; - return bcoin.protocol.parser.parseBlock(data); + p.start(); + + this.version = p.readU32(); // Technically signed + this.prevBlock = p.readHash('hex'); + this.merkleRoot = p.readHash('hex'); + this.ts = p.readU32(); + this.bits = p.readU32(); + this.nonce = p.readU32(); + this.totalTX = p.readVarint(); + + this._witnessSize = 0; + + this.txs = []; + + for (i = 0; i < this.totalTX; i++) { + tx = bcoin.tx.fromRaw(p); + this._witnessSize += tx._witnessSize; + this.txs.push(tx); + } + + this._raw = p.endData(); + this._size = this._raw.length; + + return this; }; /** @@ -672,7 +733,9 @@ Block.parseRaw = function parseRaw(data, enc) { */ Block.fromRaw = function fromRaw(data, enc) { - return new Block(Block.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new Block().fromRaw(data); }; /** @@ -687,6 +750,43 @@ Block.prototype.toMerkle = function toMerkle(filter) { return bcoin.merkleblock.fromBlock(this, filter); }; +Block.prototype.frame = function frame(witness, writer) { + var p = bcoin.writer(writer); + var witnessSize = 0; + var i; + + p.write32(this.version); + p.writeHash(this.prevBlock); + p.writeHash(this.merkleRoot); + p.writeU32(this.ts); + p.writeU32(this.bits); + p.writeU32(this.nonce); + p.writeVarint(this.txs.length); + + for (i = 0; i < this.txs.length; i++) { + if (witness) + this.txs[i].render(p); + else + this.txs[i].renderNormal(p); + witnessSize += p._witnessSize; + } + + if (!writer) + p = p.render(); + + p._witnessSize = witnessSize; + + return p; +}; + +Block.prototype.frameNormal = function frameNormal(writer) { + return this.frame(false, writer); +}; + +Block.prototype.frameWitness = function frameWitness(writer) { + return this.frame(true, writer); +}; + /** * Test whether an object is a Block. * @param {Object} obj diff --git a/lib/bcoin/bst.js b/lib/bcoin/bst.js index f60aa50f..861c7e65 100644 --- a/lib/bcoin/bst.js +++ b/lib/bcoin/bst.js @@ -670,7 +670,6 @@ Iterator.prototype.next = function(callback) { Iterator.prototype.seek = function seek(key) { var self = this; - var item; assert(!this.ended, 'Already ended.'); diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 715f92cc..3f87b4b3 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -301,10 +301,19 @@ Chain.prototype._preload = function _preload(callback) { bcoin.debug('Loading %s.', url); - function parseHeader(buf) { - var headers = bcoin.protocol.parser.parseBlockHeaders(buf); - headers.hash = utils.dsha256(buf.slice(0, 80)).toString('hex'); - return headers; + function parseHeader(data) { + var p = bcoin.reader(data, true); + var hash = utils.dsha256(p.readBytes(80)).toString('hex'); + p.seek(-80); + return { + hash: hash, + version: p.readU32(), // Technically signed + prevBlock: p.readHash('hex'), + merkleRoot: p.readHash('hex'), + ts: p.readU32(), + bits: p.readU32(), + nonce: p.readU32() + }; } function save(entry, header) { diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index d25e377e..31c76829 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -15,8 +15,6 @@ var assert = utils.assert; var DUMMY = new Buffer([0]); var BufferWriter = require('./writer'); var BufferReader = require('./reader'); -var Framer = bcoin.protocol.framer; -var Parser = bcoin.protocol.parser; /* * Database Layout: @@ -886,7 +884,7 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb } } - Framer.coin(input.coin, false, undo); + input.coin.toRaw(undo); } for (j = 0; j < tx.outputs.length; j++) { @@ -1368,12 +1366,9 @@ ChainDB.prototype.getUndoCoins = function getUndoCoins(hash, callback) { return this.db.fetch(layout.u(hash), function(data) { var p = new BufferReader(data); var coins = []; - var coin; - while (p.left()) { - coin = Parser.parseCoin(p, false); - coins.push(new bcoin.coin(coin)); - } + while (p.left()) + coins.push(bcoin.coin.fromRaw(p)); return coins; }, callback); diff --git a/lib/bcoin/coin.js b/lib/bcoin/coin.js index 8c146d6f..bbd16a92 100644 --- a/lib/bcoin/coin.js +++ b/lib/bcoin/coin.js @@ -34,12 +34,27 @@ function Coin(options) { if (!(this instanceof Coin)) return new Coin(options); + this.version = null; + this.height = null; + this.value = null; + this.script = null; + this.coinbase = null; + this.hash = null; + this.index = null; + + if (options) + this.fromOptions(options); +} + +utils.inherits(Coin, bcoin.output); + +Coin.prototype.fromOptions = function fromOptions(options) { assert(options, 'Coin data is required.'); this.version = options.version; this.height = options.height; this.value = options.value; - this.script = bcoin.script(options.script, false); + this.script = bcoin.script(options.script); this.coinbase = options.coinbase; this.hash = options.hash; this.index = options.index; @@ -51,9 +66,15 @@ function Coin(options) { assert(typeof this.coinbase === 'boolean'); assert(!this.hash || typeof this.hash === 'string'); assert(!this.index || typeof this.index === 'number'); -} -utils.inherits(Coin, bcoin.output); + return this; +}; + +Coin.fromOptions = function fromOptions(options) { + if (options instanceof Coin) + return options; + return new Coin().fromOptions(options); +}; /** * Calculate number of confirmations since coin was created. @@ -129,32 +150,13 @@ Coin.prototype.toJSON = function toJSON() { version: this.version, height: this.height, value: utils.btc(this.value), - script: this.script.toRaw('hex'), + script: this.script.toRaw().toString('hex'), coinbase: this.coinbase, hash: this.hash ? utils.revHex(this.hash) : null, index: this.index }; }; -/** - * Handle a deserialized JSON coin object. - * @returns {NakedCoin} A "naked" coin (a - * plain javascript object which is suitable - * for passing to the Coin constructor). - */ - -Coin.parseJSON = function parseJSON(json) { - return { - version: json.version, - height: json.height, - value: utils.satoshi(json.value), - script: bcoin.script.parseRaw(json.script, 'hex'), - coinbase: json.coinbase, - hash: json.hash ? utils.revHex(json.hash) : null, - index: json.index - }; -}; - /** * Instantiate an Coin from a jsonified coin object. * @param {Object} json - The jsonified coin object. @@ -162,7 +164,15 @@ Coin.parseJSON = function parseJSON(json) { */ Coin.fromJSON = function fromJSON(json) { - return new Coin(Coin.parseJSON(json)); + return Coin.fromOptions({ + version: json.version, + height: json.height, + value: utils.satoshi(json.value), + script: bcoin.script.fromRaw(new Buffer(json.script, 'hex')), + coinbase: json.coinbase, + hash: json.hash ? utils.revHex(json.hash) : null, + index: json.index + }); }; /** @@ -171,27 +181,38 @@ Coin.fromJSON = function fromJSON(json) { * @returns {Buffer|String} */ -Coin.prototype.toRaw = function toRaw(enc) { - var data = bcoin.protocol.framer.coin(this, false); +Coin.prototype.toRaw = function toRaw(writer) { + var p = bcoin.writer(writer); + var height = this.height; - if (enc === 'hex') - data = data.toString('hex'); + if (height === -1) + height = 0x7fffffff; - return data; + p.writeU32(this.version); + p.writeU32(height); + p.write64(this.value); + p.writeVarBytes(this.script.toRaw()); + p.writeU8(this.coinbase ? 1 : 0); + + if (!writer) + p = p.render(); + + return p; }; -/** - * Parse a serialized coin. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {NakedCoin} A "naked" coin object. - */ +Coin.prototype.fromRaw = function fromRaw(data) { + var p = bcoin.reader(data); -Coin.parseRaw = function parseRaw(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); + this.version = p.readU32(); + this.height = p.readU32(); + this.value = p.read64N(); + this.script = bcoin.script.fromRaw(p.readVarBytes()); + this.coinbase = p.readU8() === 1; - return bcoin.protocol.parser.parseCoin(data, false); + if (this.height === 0x7fffffff) + this.height = -1; + + return this; }; /** @@ -202,7 +223,9 @@ Coin.parseRaw = function parseRaw(data, enc) { */ Coin.fromRaw = function fromRaw(data, enc) { - return new Coin(Coin.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new Coin().fromRaw(data); }; /** @@ -212,27 +235,25 @@ Coin.fromRaw = function fromRaw(data, enc) { * @returns {Buffer|String} */ -Coin.prototype.toExtended = function toExtended(enc) { - var data = bcoin.protocol.framer.coin(this, true); +Coin.prototype.toExtended = function toExtended(writer) { + var p = bcoin.writer(writer); - if (enc === 'hex') - data = data.toString('hex'); + this.toRaw(p); + p.writeHash(this.hash); + p.writeU32(this.index); - return data; + if (!writer) + p = p.render(); + + return p; }; -/** - * Parse an coin in "extended" serialization format. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {NakedCoin} - A "naked" coin object. - */ - -Coin.parseExtended = function parseExtended(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); - - return bcoin.protocol.parser.parseCoin(data, true); +Coin.prototype.fromExtended = function fromExtended(data) { + var p = bcoin.reader(data); + this.fromRaw(p); + this.hash = p.readHash('hex'); + this.index = p.readU32(); + return this; }; /** @@ -244,7 +265,9 @@ Coin.parseExtended = function parseExtended(data, enc) { */ Coin.fromExtended = function fromExtended(data, enc) { - return new Coin(Coin.parseExtended(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new Coin().fromExtended(data); }; /** @@ -254,16 +277,19 @@ Coin.fromExtended = function fromExtended(data, enc) { * @returns {Coin} */ +Coin.prototype.fromTX = function fromTX(tx, index) { + this.version = tx.version; + this.height = tx.height; + this.value = tx.outputs[index].value; + this.script = tx.outputs[index].script; + this.coinbase = tx.isCoinbase(); + this.hash = tx.hash('hex'); + this.index = index; + return this; +}; + Coin.fromTX = function fromTX(tx, index) { - return new Coin({ - version: tx.version, - height: tx.height, - value: tx.outputs[index].value, - script: tx.outputs[index].script, - coinbase: tx.isCoinbase(), - hash: tx.hash('hex'), - index: index - }); + return new Coin().fromTX(tx, index); }; /** diff --git a/lib/bcoin/coins.js b/lib/bcoin/coins.js index bd6af664..0bd25a6e 100644 --- a/lib/bcoin/coins.js +++ b/lib/bcoin/coins.js @@ -30,15 +30,28 @@ function Coins(options) { if (!(this instanceof Coins)) return new Coins(options); - if (!options) - options = {}; + this.version = -1; + this.hash = null; + this.height = -1; + this.coinbase = false; + this.outputs = []; + if (options) + this.fromOptions(options); +} + +Coins.prototype.fromOptions = function fromOptions(options) { this.version = options.version != null ? options.version : -1; this.hash = options.hash || null; this.height = options.height != null ? options.height : -1; this.coinbase = options.coinbase || false; this.outputs = options.outputs || []; -} + return this; +}; + +Coins.fromOptions = function fromOptions(options) { + return new Coins().fromOptions(options); +}; /** * Add a single coin to the collection. @@ -167,7 +180,7 @@ Coins.prototype.toRaw = function toRaw(writer) { if (prefix) p.writeBytes(hash); else - bcoin.protocol.framer.script(output.script, p); + p.writeVarBytes(output.script.toRaw()); p.writeVarint(output.value); } @@ -185,24 +198,22 @@ Coins.prototype.toRaw = function toRaw(writer) { * @returns {Object} A "naked" coins object. */ -Coins.parseRaw = function parseRaw(data, hash, index) { +Coins.prototype.fromRaw = function fromRaw(data, hash, index) { var p = new BufferReader(data); var i = 0; - var version, height, coin, coins, mask, prefix, offset, size; + var version, height, coin, mask, prefix, offset, size; version = p.readVarint(); height = p.readU32(); - coins = { - version: version, - height: height >>> 1, - hash: hash, - coinbase: (height & 1) !== 0, - outputs: [] - }; + this.version = version; + this.height = height >>> 1; + this.hash = hash; + this.coinbase = (height & 1) !== 0; + this.outputs = []; - if (coins.height === 0x7fffffff) - coins.height = -1; + if (this.height === 0x7fffffff) + this.height = -1; while (p.left()) { offset = p.start(); @@ -215,7 +226,7 @@ Coins.parseRaw = function parseRaw(data, hash, index) { i++; continue; } - coins.outputs.push(null); + this.outputs.push(null); i++; continue; } @@ -241,16 +252,16 @@ Coins.parseRaw = function parseRaw(data, hash, index) { coin = new DeferredCoin(offset, size, data); if (index != null) - return coin.toCoin(coins, i); + return coin.toCoin(this, i); - coins.outputs.push(coin); + this.outputs.push(coin); i++; } assert(index == null, 'Bad coin index.'); - return coins; + return this; }; /** @@ -263,7 +274,7 @@ Coins.parseRaw = function parseRaw(data, hash, index) { Coins.parseCoin = function parseCoin(data, hash, index) { assert(index != null, 'Bad coin index.'); - return Coins.parseRaw(data, hash, index); + return new Coins().fromRaw(data, hash, index); }; /** @@ -274,7 +285,7 @@ Coins.parseCoin = function parseCoin(data, hash, index) { */ Coins.fromRaw = function fromRaw(data, hash) { - return new Coins(Coins.parseRaw(data, hash)); + return new Coins().fromRaw(data, hash); }; /** @@ -283,25 +294,27 @@ Coins.fromRaw = function fromRaw(data, hash) { * @returns {Coins} */ -Coins.fromTX = function fromTX(tx) { - var outputs = []; +Coins.prototype.fromTX = function fromTX(tx) { var i; + this.version = tx.version; + this.hash = tx.hash('hex'); + this.height = tx.height; + this.coinbase = tx.isCoinbase(); + for (i = 0; i < tx.outputs.length; i++) { if (tx.outputs[i].script.isUnspendable()) { - outputs.push(null); + this.outputs.push(null); continue; } - outputs.push(bcoin.coin.fromTX(tx, i)); + this.outputs.push(bcoin.coin.fromTX(tx, i)); } - return new Coins({ - version: tx.version, - hash: tx.hash('hex'), - height: tx.height, - coinbase: tx.isCoinbase(), - outputs: outputs - }); + return this; +}; + +Coins.fromTX = function fromTX(tx) { + return new Coins().fromTX(tx); }; /** @@ -349,7 +362,7 @@ DeferredCoin.prototype.toCoin = function toCoin(coins, index) { prefix = p.readU8() & 3; if (prefix === 0) - script = new bcoin.script(bcoin.protocol.parser.parseScript(p)); + script = bcoin.script.fromRaw(p.readVarBytes()); else if (prefix === 1) script = bcoin.script.createPubkeyhash(p.readBytes(20)); else if (prefix === 2) diff --git a/lib/bcoin/env.js b/lib/bcoin/env.js index 3875e91d..51f9944c 100644 --- a/lib/bcoin/env.js +++ b/lib/bcoin/env.js @@ -153,6 +153,7 @@ function Environment(options) { this.witness = this.script.Witness; this.address = require('./address'); this.input = require('./input'); + this.outpoint = this.input.Outpoint; this.output = require('./output'); this.coin = require('./coin'); this.coins = require('./coins'); diff --git a/lib/bcoin/headers.js b/lib/bcoin/headers.js index ff05fbf4..b03e8e6e 100644 --- a/lib/bcoin/headers.js +++ b/lib/bcoin/headers.js @@ -44,8 +44,8 @@ utils.inherits(Headers, bcoin.abstractblock); * @returns {Buffer} */ -Headers.prototype.render = function render() { - return this.getRaw(); +Headers.prototype.render = function render(writer) { + return this.getRaw(writer); }; /** @@ -66,16 +66,9 @@ Headers.prototype._verify = function _verify(ret) { */ Headers.prototype.getSize = function getSize() { - return 80; -}; - -/** - * Get the raw headers serialization. - * @returns {Buffer} - */ - -Headers.prototype.getRaw = function getRaw() { - return this.abbr(); + var writer = new bcoin.writer(); + this.toRaw(writer); + return writer.written; }; /** @@ -107,15 +100,21 @@ Headers.prototype.inspect = function inspect() { * @returns {Buffer|String} */ -Headers.prototype.toRaw = function toRaw(enc) { - var data; +Headers.prototype.toRaw = function toRaw(writer) { + var p = bcoin.writer(writer); - data = this.render(); + p.write32(this.version); + p.writeHash(this.prevBlock); + p.writeHash(this.merkleRoot); + p.writeU32(this.ts); + p.writeU32(this.bits); + p.writeU32(this.nonce); + p.writeVarint(this.totalTX); - if (enc === 'hex') - data = data.toString('hex'); + if (!writer) + p = p.render(); - return data; + return p; }; /** @@ -125,11 +124,18 @@ Headers.prototype.toRaw = function toRaw(enc) { * @returns {NakedBlock} A "naked" headers object. */ -Headers.parseRaw = function parseRaw(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); +Headers.prototype.fromRaw = function fromRaw(data) { + var p = bcoin.reader(data); - return bcoin.protocol.parser.parseBlockHeaders(data); + this.version = p.readU32(); // Technically signed + this.prevBlock = p.readHash('hex'); + this.merkleRoot = p.readHash('hex'); + this.ts = p.readU32(); + this.bits = p.readU32(); + this.nonce = p.readU32(); + this.totalTX = p.readVarint(); + + return this; }; /** @@ -140,7 +146,9 @@ Headers.parseRaw = function parseRaw(data, enc) { */ Headers.fromRaw = function fromRaw(data, enc) { - return new Headers(Headers.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new Headers().fromRaw(data); }; /** diff --git a/lib/bcoin/http/client.js b/lib/bcoin/http/client.js index d98bee56..05c098ef 100644 --- a/lib/bcoin/http/client.js +++ b/lib/bcoin/http/client.js @@ -772,7 +772,7 @@ HTTPClient.prototype.getBlock = function getBlock(hash, callback) { */ HTTPClient.prototype.broadcast = function broadcast(tx, callback) { - var body = { tx: tx.toRaw('hex') }; + var body = { tx: tx.toRaw().toString('hex') }; callback = utils.ensure(callback); @@ -808,7 +808,7 @@ HTTPClient.prototype.walletSend = function walletSend(id, options, callback) { address: output.address && output.address.toBase58 ? output.address.toBase58() : output.address, - script: output.script ? output.script.toRaw('hex') : null + script: output.script ? output.script.toRaw().toString('hex') : null }; }); @@ -855,7 +855,7 @@ HTTPClient.prototype.walletCreate = function walletCreate(id, options, outputs, address: output.address && output.address.toBase58 ? output.address.toBase58() : output.address, - script: output.script ? output.script.toRaw('hex') : null + script: output.script ? output.script.toRaw().toString('hex') : null }; }); @@ -892,7 +892,7 @@ HTTPClient.prototype.walletSign = function walletCreate(id, tx, options, callbac } body = utils.merge({}, options || {}, { - tx: tx.toRaw('hex') + tx: tx.toRaw().toString('hex') }); callback = utils.ensure(callback); @@ -918,7 +918,7 @@ HTTPClient.prototype.walletSign = function walletCreate(id, tx, options, callbac */ HTTPClient.prototype.walletFill = function walletFill(tx, callback) { - var body = { tx: tx.toRaw('hex') }; + var body = { tx: tx.toRaw().toString('hex') }; callback = utils.ensure(callback); diff --git a/lib/bcoin/input.js b/lib/bcoin/input.js index 453dc6fa..0d2a91f1 100644 --- a/lib/bcoin/input.js +++ b/lib/bcoin/input.js @@ -11,8 +11,78 @@ var bcoin = require('./env'); var utils = require('./utils'); var assert = utils.assert; var constants = bcoin.protocol.constants; -var BufferReader = require('./reader'); -var BufferWriter = require('./writer'); + +function Outpoint(hash, index) { + if (!(this instanceof Outpoint)) + return new Outpoint(hash, index); + + this.hash = hash || null; + this.index = index || 0; +} + +Outpoint.prototype.fromOptions = function fromOptions(data) { + this.hash = data.hash; + this.index = data.index; + assert(typeof this.hash === 'string'); + assert(typeof this.index === 'number'); + return this; +}; + +Outpoint.fromOptions = function fromOptions(data) { + if (data instanceof Outpoint) + return data; + return new Outpoint().fromOptions(data); +}; + +Outpoint.prototype.fromRaw = function fromRaw(data) { + var p = bcoin.reader(data); + this.hash = p.readHash('hex'); + this.index = p.readU32(); + return this; +}; + +Outpoint.fromRaw = function fromRaw(data) { + return new Outpoint().fromRaw(data); +}; + +Outpoint.prototype.toRaw = function toRaw(writer) { + var p = bcoin.writer(writer); + + p.writeHash(this.hash); + p.writeU32(this.index); + + if (!writer) + p = p.render(); + + return p; +}; + +Outpoint.prototype.fromJSON = function fromJSON(json) { + this.hash = utils.revHex(json.hash); + this.index = json.index; + return this; +}; + +Outpoint.fromJSON = function fromJSON(json) { + return Outpoint().fromJSON(json); +}; + +Outpoint.prototype.fromTX = function fromTX(tx, i) { + this.hash = tx.hash('hex'); + this.index = i; + return this; +}; + +Outpoint.fromTX = function fromTX(tx, i) { + return Outpoint().fromTX(tx, i); +}; + +Outpoint.prototype.toJSON = function toJSON() { + return { + hash: utils.revHex(this.hash), + index: this.index + }; +}; /** * Represents a transaction input. @@ -36,10 +106,22 @@ function Input(options, mutable) { if (!(this instanceof Input)) return new Input(options, mutable); + this.mutable = false; + this.prevout = null; + this.script = null; + this.sequence = null; + this.witness = null; + this.coin = null; + + if (options) + this.fromOptions(options, mutable); +} + +Input.prototype.fromOptions = function fromOptions(options, mutable) { assert(options, 'Input data is required.'); this.mutable = !!mutable; - this.prevout = options.prevout; + this.prevout = Outpoint.fromOptions(options.prevout); this.script = bcoin.script(options.script); this.sequence = options.sequence == null ? 0xffffffff : options.sequence; this.witness = bcoin.witness(options.witness); @@ -52,7 +134,13 @@ function Input(options, mutable) { assert(typeof this.prevout.hash === 'string'); assert(typeof this.prevout.index === 'number'); assert(typeof this.sequence === 'number'); -} + + return this; +}; + +Input.fromOptions = function fromOptions(options) { + return new Input().fromOptions(options); +}; /** * Get the previous output script type. Will "guess" @@ -268,6 +356,7 @@ Input.prototype.inspect = function inspect() { witness: this.witness, redeem: this.getRedeem(), sequence: this.sequence, + prevout: this.prevout, coin: coin }; }; @@ -282,35 +371,21 @@ Input.prototype.inspect = function inspect() { Input.prototype.toJSON = function toJSON() { return { - prevout: { - hash: utils.revHex(this.prevout.hash), - index: this.prevout.index - }, + prevout: this.prevout.toJSON(), coin: this.coin ? this.coin.toJSON() : null, - script: this.script.toRaw('hex'), - witness: this.witness.toRaw('hex'), + script: this.script.toRaw().toString('hex'), + witness: this.witness.toRaw().toString('hex'), sequence: this.sequence }; }; -/** - * Handle a deserialized JSON input object. - * @returns {NakedInput} A "naked" input (a - * plain javascript object which is suitable - * for passing to the Input constructor). - */ - -Input.parseJSON = function parseJSON(json) { - return { - prevout: { - hash: utils.revHex(json.prevout.hash), - index: json.prevout.index - }, - coin: json.coin ? bcoin.coin.parseJSON(json.coin) : null, - script: bcoin.script.parseRaw(json.script, 'hex'), - witness: bcoin.witness.parseRaw(json.witness, 'hex'), - sequence: json.sequence - }; +Input.prototype.fromJSON = function fromJSON(json) { + this.prevout = Outpoint.fromJSON(json.prevout); + this.coin = json.coin ? bcoin.coin.fromJSON(json.coin) : null; + this.script = bcoin.script.fromRaw(json.script, 'hex'); + this.witness = bcoin.witness.fromRaw(json.witness, 'hex'); + this.sequence = json.sequence; + return this; }; /** @@ -320,7 +395,7 @@ Input.parseJSON = function parseJSON(json) { */ Input.fromJSON = function fromJSON(json) { - return new Input(Input.parseJSON(json)); + return new Input().fromJSON(json); }; /** @@ -329,29 +404,17 @@ Input.fromJSON = function fromJSON(json) { * @returns {Buffer|String} */ -Input.prototype.toRaw = function toRaw(enc) { - var data = bcoin.protocol.framer.input(this); +Input.prototype.toRaw = function toRaw(writer) { + var p = bcoin.writer(writer); - if (enc === 'hex') - data = data.toString('hex'); + this.prevout.toRaw(p); + p.writeVarBytes(this.script.toRaw()); + p.writeU32(this.sequence); - return data; -}; + if (!writer) + p = p.render(); -/** - * Parse a serialized input. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {NakedInput} A "naked" input object. - */ - -Input.parseRaw = function parseRaw(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); - - data = bcoin.protocol.parser.parseInput(data); - - return data; + return p; }; /** @@ -361,8 +424,20 @@ Input.parseRaw = function parseRaw(data, enc) { * @returns {Input} */ +Input.prototype.fromRaw = function fromRaw(data) { + var p = bcoin.reader(data); + + this.prevout = Outpoint.fromRaw(p); + this.script = bcoin.script.fromRaw(p.readVarBytes()); + this.sequence = p.readU32(); + + return this; +}; + Input.fromRaw = function fromRaw(data, enc) { - return new Input(Input.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new Input().fromRaw(data); }; /** @@ -372,19 +447,16 @@ Input.fromRaw = function fromRaw(data, enc) { * @returns {Buffer|String} */ -Input.prototype.toExtended = function toExtended(enc) { - var p = new BufferWriter(); - var data; +Input.prototype.toExtended = function toExtended(writer) { + var p = bcoin.writer(writer); - bcoin.protocol.framer.input(this, p); - bcoin.protocol.framer.witness(this.witness, p); + this.toRaw(p); + this.witness.toRaw(p); - data = p.render(); + if (!writer) + p = p.render(); - if (enc === 'hex') - data = data.toString('hex'); - - return data; + return p; }; /** @@ -394,15 +466,12 @@ Input.prototype.toExtended = function toExtended(enc) { * @returns {NakedInput} - A "naked" input object. */ -Input.parseExtended = function parseExtended(data, enc) { +Input.prototype.fromExtended = function fromExtended(data) { var input, p; - if (enc === 'hex') - data = new Buffer(data, 'hex'); - - p = new BufferReader(data); - input = bcoin.protocol.parser.parseInput(p); - input.witness = bcoin.protocol.parser.parseWitness(p); + p = bcoin.reader(data); + this.fromRaw(p); + this.witness = bcoin.witness.fromRaw(p); return input; }; @@ -416,7 +485,9 @@ Input.parseExtended = function parseExtended(data, enc) { */ Input.fromExtended = function fromExtended(data, enc) { - return new Input(Input.parseExtended(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new Input().fromExtended(data); }; /** @@ -437,4 +508,6 @@ Input.isInput = function isInput(obj) { * Expose */ -module.exports = Input; +exports = Input; +exports.Outpoint = Outpoint; +module.exports = exports; diff --git a/lib/bcoin/keyring.js b/lib/bcoin/keyring.js index 47409117..8ee87733 100644 --- a/lib/bcoin/keyring.js +++ b/lib/bcoin/keyring.js @@ -149,7 +149,7 @@ KeyRing.prototype.getProgram = function getProgram() { hash = utils.ripesha(this.getPublicKey()); program = bcoin.script.createWitnessProgram(0, hash); } else if (this.type === 'multisig') { - hash = utils.sha256(this.getScript().encode()); + hash = utils.sha256(this.getScript().toRaw()); program = bcoin.script.createWitnessProgram(0, hash); } else { assert(false, 'Unknown address type.'); @@ -172,7 +172,7 @@ KeyRing.prototype.getProgramHash = function getProgramHash(enc) { return; if (!this._programHash) - this._programHash = utils.ripesha(this.getProgram().encode()); + this._programHash = utils.ripesha(this.getProgram().toRaw()); return enc === 'hex' ? this._programHash.toString('hex') @@ -222,7 +222,7 @@ KeyRing.prototype.getScriptHash160 = function getScriptHash256(enc) { return; if (!this._scriptHash160) - this._scriptHash160 = utils.ripesha(this.getScript().encode()); + this._scriptHash160 = utils.ripesha(this.getScript().toRaw()); return enc === 'hex' ? this._scriptHash160.toString('hex') @@ -240,7 +240,7 @@ KeyRing.prototype.getScriptHash256 = function getScriptHash256(enc) { return; if (!this._scriptHash256) - this._scriptHash256 = utils.sha256(this.getScript().encode()); + this._scriptHash256 = utils.sha256(this.getScript().toRaw()); return enc === 'hex' ? this._scriptHash256.toString('hex') diff --git a/lib/bcoin/memblock.js b/lib/bcoin/memblock.js index e87fe6cb..cfa89418 100644 --- a/lib/bcoin/memblock.js +++ b/lib/bcoin/memblock.js @@ -55,12 +55,25 @@ function MemBlock(data) { bcoin.abstractblock.call(this, data); this.memory = true; - this.coinbaseHeight = data.coinbaseHeight; - this.raw = data.raw; + this.coinbaseHeight = null; + this.raw = null; + + if (data) + this.fromOptions(data); } utils.inherits(MemBlock, bcoin.abstractblock); +MemBlock.prototype.fromOptions = function fromOptions(data) { + this.coinbaseHeight = data.coinbaseHeight; + this.raw = data.raw; + return this; +}; + +MemBlock.fromOptions = function fromOptions(data) { + return new MemBlock().fromOptions(data); +}; + /** * Get the full block size. * @returns {Number} @@ -92,6 +105,46 @@ MemBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() { return this.coinbaseHeight; }; +MemBlock.prototype.fromRaw = function fromRaw(data) { + var p = bcoin.reader(data); + var height = -1; + var inCount, input; + + this.version = p.readU32(); // Technically signed + this.prevBlock = p.readHash('hex'); + this.merkleRoot = p.readHash('hex'); + this.ts = p.readU32(); + this.bits = p.readU32(); + this.nonce = p.readU32(); + this.totalTX = p.readVarint(); + + if (this.version > 1 && this.totalTX > 0) { + p.readU32(); // Technically signed + inCount = p.readVarint(); + + if (inCount === 0) { + if (p.readU8() !== 0) + inCount = p.readVarint(); + } + + if (inCount > 0) + input = bcoin.input.fromRaw(p); + } + + if (input) + height = bcoin.script.getCoinbaseHeight(input.script.raw); + + this.coinbaseHeight = height; + this.txs = []; + this.raw = p.data; + + return this; +}; + +MemBlock.fromRaw = function fromRaw(data) { + return new MemBlock().fromRaw(data); +}; + /** * Parse the serialized block data and create an actual {@link Block}. * @returns {Block} @@ -99,9 +152,9 @@ MemBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() { */ MemBlock.prototype.toBlock = function toBlock() { - var data = bcoin.protocol.parser.parseBlock(this.raw); + var block = bcoin.block.fromRaw(this.raw); this.raw = null; - return new bcoin.block(data); + return block; }; /** diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index ecda95fc..be55b251 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -1515,7 +1515,7 @@ Mempool.prototype.getConfidence = function getConfidence(hash, callback) { if (hash instanceof bcoin.tx) return callback(null, hash, hash.hash('hex')); - return this.getTX(hash, function(err, tx) { + return self.getTX(hash, function(err, tx) { if (err) return callback(err); return callback(null, tx, hash); @@ -1942,7 +1942,8 @@ MempoolEntry.fromTX = function fromTX(tx, height) { MempoolEntry.prototype.toRaw = function toRaw(writer) { var p = new BufferWriter(writer); - bcoin.protocol.framer.renderTX(this.tx, true, p); + this.tx.toRaw(p); + p.writeU32(this.height); p.writeU32(this.size); p.writeDouble(this.priority); diff --git a/lib/bcoin/merkleblock.js b/lib/bcoin/merkleblock.js index f26e8914..02d078c7 100644 --- a/lib/bcoin/merkleblock.js +++ b/lib/bcoin/merkleblock.js @@ -44,6 +44,24 @@ function MerkleBlock(data) { bcoin.abstractblock.call(this, data); + this.hashes = null; + this.flags = null; + + // List of matched TXs + this.map = {}; + this.matches = []; + this._validPartial = null; + + // TXs that will be pushed on + this.txs = []; + + if (data) + this.fromOptions(data); +} + +utils.inherits(MerkleBlock, bcoin.abstractblock); + +MerkleBlock.prototype.fromOptions = function fromOptions(data) { assert(Array.isArray(data.hashes)); assert(Buffer.isBuffer(data.flags)); @@ -57,17 +75,21 @@ function MerkleBlock(data) { // TXs that will be pushed on this.txs = []; -} -utils.inherits(MerkleBlock, bcoin.abstractblock); + return this; +}; + +MerkleBlock.fromOptions = function fromOptions(data) { + return new MerkleBlock().fromOptions(data); +}; /** * Serialize the merkleblock. * @returns {Buffer} */ -MerkleBlock.prototype.render = function render() { - return this.getRaw(); +MerkleBlock.prototype.render = function render(writer) { + return this.toRaw(writer); }; /** @@ -76,22 +98,9 @@ MerkleBlock.prototype.render = function render() { */ MerkleBlock.prototype.getSize = function getSize() { - if (this._size == null) - this.getRaw(); - return this._size; -}; - -/** - * Get the raw merkleblock serialization. - * @returns {Buffer} - */ - -MerkleBlock.prototype.getRaw = function getRaw() { - if (!this._raw) { - this._raw = bcoin.protocol.framer.merkleBlock(this); - this._size = this._raw.length; - } - return this._raw; + var writer = new bcoin.writer(); + this.toRaw(writer); + return writer.written; }; /** @@ -325,13 +334,29 @@ MerkleBlock.prototype.inspect = function inspect() { * @returns {Buffer|String} */ -MerkleBlock.prototype.toRaw = function toRaw(enc) { - var data = this.render(); +MerkleBlock.prototype.toRaw = function toRaw(writer) { + var p = bcoin.writer(writer); + var i; - if (enc === 'hex') - data = data.toString('hex'); + p.writeU32(this.version); + p.writeHash(this.prevBlock); + p.writeHash(this.merkleRoot); + p.writeU32(this.ts); + p.writeU32(this.bits); + p.writeU32(this.nonce); + p.writeU32(this.totalTX); - return data; + p.writeVarint(this.hashes.length); + + for (i = 0; i < this.hashes.length; i++) + p.writeHash(this.hashes[i]); + + p.writeVarBytes(this.flags); + + if (!writer) + p = p.render(); + + return p; }; /** @@ -341,11 +366,28 @@ MerkleBlock.prototype.toRaw = function toRaw(enc) { * @returns {NakedBlock} A "naked" headers object. */ -MerkleBlock.parseRaw = function parseRaw(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); +MerkleBlock.prototype.fromRaw = function fromRaw(data) { + var p = bcoin.reader(data); + var i, hashCount; - return bcoin.protocol.parser.parseMerkleBlock(data); + this.version = p.readU32(); + this.prevBlock = p.readHash('hex'); + this.merkleRoot = p.readHash('hex'); + this.ts = p.readU32(); + this.bits = p.readU32(); + this.nonce = p.readU32(); + this.totalTX = p.readU32(); + + hashCount = p.readVarint(); + + this.hashes = []; + + for (i = 0; i < hashCount; i++) + this.hashes.push(p.readHash('hex')); + + this.flags = p.readVarBytes(); + + return this; }; /** @@ -356,7 +398,9 @@ MerkleBlock.parseRaw = function parseRaw(data, enc) { */ MerkleBlock.fromRaw = function fromRaw(data, enc) { - return new MerkleBlock(MerkleBlock.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new MerkleBlock().fromRaw(data); }; /** @@ -441,22 +485,20 @@ MerkleBlock.fromBlock = function fromBlock(block, filter) { for (p = 0; p < bits.length; p++) flags[p / 8 | 0] |= bits[p] << (p % 8); - block = new MerkleBlock({ - version: block.version, - prevBlock: block.prevBlock, - merkleRoot: block.merkleRoot, - ts: block.ts, - bits: block.bits, - nonce: block.nonce, - totalTX: totalTX, - height: block.height, - hashes: hashes, - flags: flags - }); + var merkle = new MerkleBlock(); + merkle.version = block.version; + merkle.prevBlock = block.prevBlock; + merkle.merkleRoot = block.merkleRoot; + merkle.ts = block.ts; + merkle.bits = block.bits; + merkle.nonce = block.nonce; + merkle.totalTX = totalTX; + merkle.height = block.height; + merkle.hashes = hashes; + merkle.flags = flags; + merkle.txs = txs; - block.txs = txs; - - return block; + return merkle; }; /** diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index c2aa07e2..a4562f9f 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -122,7 +122,7 @@ Miner.prototype._init = function _init() { this.on('block', function(block) { // Emit the block hex as a failsafe (in case we can't send it) bcoin.debug('Found block: %d (%s).', block.height, block.rhash); - bcoin.debug('Raw: %s', block.toRaw('hex')); + bcoin.debug('Raw: %s', block.toRaw().toString('hex')); }); this.on('status', function(stat) { diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 08766213..be6055ca 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -196,12 +196,12 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) { if (prev.isScripthash()) { if (addr.program && utils.equal(prev.get(1), addr.programHash)) { // Witness program nested in regular P2SH. - redeemScript = addr.program.encode(); + redeemScript = addr.program.toRaw(); vector = input.witness; if (addr.program.isWitnessScripthash()) { // P2WSH nested within pay-to-scripthash // (it had to be this complicated, didn't it?) - witnessScript = addr.script.encode(); + witnessScript = addr.script.toRaw(); prev = addr.script; } else if (addr.program.isWitnessPubkeyhash()) { // P2WPKH nested within pay-to-scripthash. @@ -211,7 +211,7 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) { } } else if (addr.script && utils.equal(prev.get(1), addr.scriptHash160)) { // Regular P2SH. - redeemScript = addr.script.encode(); + redeemScript = addr.script.toRaw(); vector = input.script; prev = addr.script; } else { @@ -226,7 +226,7 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) { if (!addr.script || !utils.equal(prev.get(1), addr.scriptHash256)) return false; - witnessScript = addr.script.encode(); + witnessScript = addr.script.toRaw(); prev = addr.script; } else if (prev.isWitnessPubkeyhash()) { // Bare P2WPKH. @@ -1228,7 +1228,7 @@ MTX.prototype.sortMembers = function sortMembers() { var res = a.value - b.value; if (res !== 0) return res; - return utils.cmp(a.encode(), b.encode()); + return utils.cmp(a.script.toRaw(), b.script.toRaw()); }); if (this.changeIndex !== -1) { @@ -1273,46 +1273,32 @@ MTX.prototype.setLocktime = function setLocktime(locktime) { this.locktime = locktime; }; -/** - * @see TX.parseJSON - */ - -MTX.parseJSON = bcoin.tx.parseJSON; - /** * @see TX.fromJSON */ MTX.fromJSON = function fromJSON(json) { - return new MTX(MTX.parseJSON(json)); + return new MTX().fromJSON(JSON); }; -/** - * @see TX.parseRaw - */ - -MTX.parseRaw = bcoin.tx.parseRaw; - /** * @see TX.fromRaw */ MTX.fromRaw = function fromRaw(data, enc) { - return new MTX(MTX.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new MTX().fromRaw(data); }; -/** - * @see TX.parseExtended - */ - -MTX.parseExtended = bcoin.tx.parseExtended; - /** * @see TX.fromExtended */ MTX.fromExtended = function fromExtended(data, enc) { - return new MTX(MTX.parseExtended(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new MTX().fromExtended(data); }; /** diff --git a/lib/bcoin/output.js b/lib/bcoin/output.js index cda2fbfa..a7f1cd69 100644 --- a/lib/bcoin/output.js +++ b/lib/bcoin/output.js @@ -12,7 +12,6 @@ var utils = require('./utils'); var constants = bcoin.protocol.constants; var assert = utils.assert; var BufferWriter = require('./writer'); -var Framer = bcoin.protocol.framer; /** * Represents a transaction output. @@ -28,11 +27,20 @@ var Framer = bcoin.protocol.framer; */ function Output(options, mutable) { - var value; - if (!(this instanceof Output)) return new Output(options, mutable); + this.mutable = false; + this.value = 0; + this.script = null; + + if (options) + this.fromOptions(options, mutable); +} + +Output.prototype.fromOptions = function fromOptions(options, mutable) { + var value; + assert(options, 'Output data is required.'); value = options.value; @@ -46,7 +54,13 @@ function Output(options, mutable) { assert(typeof this.value === 'number'); assert(!this.mutable || this.value >= 0); -} + + return this; +}; + +Output.fromOptions = function fromOptions(options) { + return new Output().fromOptions(options); +}; /** * Get the script type. @@ -147,7 +161,7 @@ Output.prototype.inspect = function inspect() { Output.prototype.toJSON = function toJSON() { return { value: utils.btc(this.value), - script: this.script.toRaw('hex') + script: this.script.toRaw().toString('hex') }; }; @@ -167,7 +181,7 @@ Output.prototype.getDustThreshold = function getDustThreshold(rate) { if (this.script.isUnspendable()) return 0; - size = Framer.output(this, new BufferWriter()).written; + size = this.toRaw(new BufferWriter()).written; size += 148; return 3 * bcoin.tx.getMinFee(size, rate); @@ -190,11 +204,11 @@ Output.prototype.isDust = function isDust(rate) { * for passing to the Output constructor). */ -Output.parseJSON = function parseJSON(json) { - return { +Output.prototype.fromJSON = function fromJSON(json) { + return Output.fromOptions({ value: utils.satoshi(json.value), - script: bcoin.script.parseRaw(json.script, 'hex') - }; + script: bcoin.script.fromRaw(json.script, 'hex') + }); }; /** @@ -204,7 +218,7 @@ Output.parseJSON = function parseJSON(json) { */ Output.fromJSON = function fromJSON(json) { - return new Output(Output.parseJSON(json)); + return new Output().fromJSON(json); }; /** @@ -213,13 +227,16 @@ Output.fromJSON = function fromJSON(json) { * @returns {Buffer|String} */ -Output.prototype.toRaw = function toRaw(enc) { - var data = Framer.output(this); +Output.prototype.toRaw = function toRaw(writer) { + var p = bcoin.writer(writer); - if (enc === 'hex') - data = data.toString('hex'); + p.write64(this.value); + p.writeVarBytes(this.script.toRaw()); - return data; + if (!writer) + p = p.render(); + + return p; }; /** @@ -229,13 +246,13 @@ Output.prototype.toRaw = function toRaw(enc) { * @returns {NakedOutput} A "naked" output object. */ -Output.parseRaw = function parseRaw(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); +Output.prototype.fromRaw = function fromRaw(data) { + var p = bcoin.reader(data); - data = bcoin.protocol.parser.parseOutput(data); + this.value = p.read64N(); + this.script = bcoin.script.fromRaw(p.readVarBytes()); - return data; + return this; }; /** @@ -246,7 +263,10 @@ Output.parseRaw = function parseRaw(data, enc) { */ Output.fromRaw = function fromRaw(data, enc) { - return new Output(Output.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + + return new Output().fromRaw(data); }; /** diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 7c802180..eefca10c 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -719,11 +719,11 @@ Peer.prototype._onPacket = function onPacket(packet) { case 'filterclear': return this._handleFilterClear(payload); case 'block': - payload = new bcoin.memblock(payload); + // payload = new bcoin.memblock(payload); this.fire(cmd, payload); break; case 'merkleblock': - payload = new bcoin.merkleblock(payload); + // payload = new bcoin.merkleblock(payload); payload.verifyPartial(); this.lastBlock = payload; this.waiting = payload.matches.length; @@ -731,7 +731,7 @@ Peer.prototype._onPacket = function onPacket(packet) { this._flushMerkle(); break; case 'tx': - payload = new bcoin.tx(payload); + // payload = new bcoin.tx(payload); if (this.lastBlock) { if (this.lastBlock.hasTX(payload)) { this.lastBlock.addTX(payload); diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index d54b251b..09924850 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -299,15 +299,8 @@ Framer.prototype.getBlocks = function getBlocks(data) { * @returns {Buffer} tx packet. */ -Framer.prototype.tx = function tx(tx) { - var checksum; - - // Save some time by using the - // cached hash as our checksum. - if (tx.hash) - checksum = tx.hash(); - - return this.packet('tx', Framer.renderTX(tx, false), checksum); +Framer.prototype.tx = function _tx(tx) { + return this.packet('tx', Framer.tx(tx), tx.hash()); }; /** @@ -321,18 +314,16 @@ Framer.prototype.witnessTX = function witnessTX(tx) { // Save some time by using the // cached hash as our checksum. - if (tx.witnessHash) { - if (tx.hasWitness()) { - // We can't use the coinbase - // hash since it is all zeroes. - if (!tx.isCoinbase()) - checksum = tx.witnessHash(); - } else { - checksum = tx.hash(); - } + if (tx.hasWitness()) { + // We can't use the coinbase + // hash since it is all zeroes. + if (!tx.isCoinbase()) + checksum = tx.witnessHash(); + } else { + checksum = tx.hash(); } - return this.packet('tx', Framer.renderTX(tx, true), checksum); + return this.packet('tx', Framer.witnessTX(tx), checksum); }; /** @@ -660,38 +651,6 @@ Framer._getBlocks = function _getBlocks(data, writer, headers) { return p; }; -/** - * Serialize a coin. - * @param {NakedCoin|Coin} coin - * @param {Boolean} extended - Whether to include the hash and index. - * @param {BufferWriter?} writer - A buffer writer to continue writing from. - * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. - */ - -Framer.coin = function _coin(coin, extended, writer) { - var p = new BufferWriter(writer); - var height = coin.height; - - if (height === -1) - height = 0x7fffffff; - - p.writeU32(coin.version); - p.writeU32(height); - p.write64(coin.value); - Framer.script(coin.script, p); - p.writeU8(coin.coinbase ? 1 : 0); - - if (extended) { - p.writeHash(coin.hash); - p.writeU32(coin.index); - } - - if (!writer) - p = p.render(); - - return p; -}; - /** * Serialize transaction without witness. * @param {NakedTX|TX} tx @@ -700,87 +659,7 @@ Framer.coin = function _coin(coin, extended, writer) { */ Framer.tx = function _tx(tx, writer) { - var p = new BufferWriter(writer); - var i; - - p.write32(tx.version); - - p.writeVarint(tx.inputs.length); - for (i = 0; i < tx.inputs.length; i++) - Framer.input(tx.inputs[i], p); - - p.writeVarint(tx.outputs.length); - for (i = 0; i < tx.outputs.length; i++) - Framer.output(tx.outputs[i], p); - - p.writeU32(tx.locktime); - - if (!writer) - p = p.render(); - - p._witnessSize = 0; - - return p; -}; - -/** - * Serialize an outpoint. - * @param {Hash} hash - * @param {Number} index - * @param {BufferWriter?} writer - A buffer writer to continue writing from. - * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. - */ - -Framer.outpoint = function outpoint(hash, index, writer) { - var p = new BufferWriter(writer); - - p.writeHash(hash); - p.writeU32(index); - - if (!writer) - p = p.render(); - - return p; -}; - -/** - * Serialize an input. - * @param {NakedInput|Input} input - * @param {BufferWriter?} writer - A buffer writer to continue writing from. - * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. - */ - -Framer.input = function _input(input, writer) { - var p = new BufferWriter(writer); - - p.writeHash(input.prevout.hash); - p.writeU32(input.prevout.index); - Framer.script(input.script, p); - p.writeU32(input.sequence); - - if (!writer) - p = p.render(); - - return p; -}; - -/** - * Serialize an output. - * @param {NakedOutput|Output} output - * @param {BufferWriter?} writer - A buffer writer to continue writing from. - * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. - */ - -Framer.output = function _output(output, writer) { - var p = new BufferWriter(writer); - - p.write64(output.value); - Framer.script(output.script, p); - - if (!writer) - p = p.render(); - - return p; + return tx.renderNormal(writer); }; /** @@ -792,85 +671,7 @@ Framer.output = function _output(output, writer) { */ Framer.witnessTX = function _witnessTX(tx, writer) { - var p = new BufferWriter(writer); - var witnessSize = 0; - var i, start; - - p.write32(tx.version); - p.writeU8(0); - p.writeU8(tx.flag || 1); - - p.writeVarint(tx.inputs.length); - - for (i = 0; i < tx.inputs.length; i++) - Framer.input(tx.inputs[i], p); - - p.writeVarint(tx.outputs.length); - - for (i = 0; i < tx.outputs.length; i++) - Framer.output(tx.outputs[i], p); - - for (i = 0; i < tx.inputs.length; i++) { - start = p.written; - Framer.witness(tx.inputs[i].witness, p); - witnessSize += p.written - start; - } - - p.writeU32(tx.locktime); - - if (!writer) - p = p.render(); - - p._witnessSize = witnessSize + 2; - - return p; -}; - -/** - * Serialize a script. Note that scripts require - * extra magic since they're so goddamn bizarre. - * Normally in an "encoded" script we don't - * include the varint size because scripthashes - * don't include them. This is why - * script.encode/decode is separate from the - * framer and parser. - * @param {NakedScript|Script} script - * @param {BufferWriter?} writer - A buffer writer to continue writing from. - * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. - */ - -Framer.script = function _script(script, writer) { - var p = new BufferWriter(writer); - var data = script.raw || script; - - p.writeVarBytes(data); - - if (!writer) - p = p.render(); - - return p; -}; - -/** - * Serialize a witness. - * @param {NakedWitness|Witness} witness - * @param {BufferWriter?} writer - A buffer writer to continue writing from. - * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. - */ - -Framer.witness = function _witness(witness, writer) { - var p = new BufferWriter(writer); - var i; - - p.writeVarint(witness.items.length); - - for (i = 0; i < witness.items.length; i++) - p.writeVarBytes(witness.items[i]); - - if (!writer) - p = p.render(); - - return p; + return tx.renderWitness(writer); }; /** @@ -881,7 +682,7 @@ Framer.witness = function _witness(witness, writer) { */ Framer.block = function _block(block, writer) { - return Framer._block(block, false, writer); + return block.renderNormal(block); }; /** @@ -893,152 +694,7 @@ Framer.block = function _block(block, writer) { */ Framer.witnessBlock = function _witnessBlock(block, writer) { - return Framer._block(block, true, writer); -}; - -/** - * Serialize a transaction lazily (use existing raw data if present). - * @param {NakedTX|TX} tx - * @param {Boolean} useWitness - Whether to include witness data. - * @param {BufferWriter?} writer - A buffer writer to continue writing from. - * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. - */ - -Framer.renderTX = function renderTX(tx, useWitness, writer) { - var p = new BufferWriter(writer); - var witnessSize; - - // Cache the serialization if we can. - if (tx.render && !tx.mutable && !tx._raw) - tx.render(); - - // Try the cached raw data first. - if (tx._raw) { - if (useWitness) { - // If we're serializing the witness, - // we can use whatever data getRaw() - // gave us. - p.writeBytes(tx._raw); - witnessSize = tx._witnessSize; - } else { - // We have to use the standard format - // here. Try to grab it from cache. - if (bcoin.protocol.parser.isWitnessTX(tx._raw)) { - Framer.tx(tx, p); - witnessSize = p._witnessSize; - } else { - p.writeBytes(tx._raw); - witnessSize = tx._witnessSize; - } - } - } else { - if (useWitness) { - if (bcoin.tx.prototype.hasWitness.call(tx)) { - Framer.witnessTX(tx, p); - } else { - // Only use the witness serialization if - // we have a witness. This clause isn't - // necessary above since we already - // determined this in getRaw(). - Framer.tx(tx, p); - } - } else { - // Any other case, we use - // the standard serialization. - Framer.tx(tx, p); - } - witnessSize = p._witnessSize; - } - - if (!writer) - p = p.render(); - - p._witnessSize = witnessSize; - - return p; -}; - -/** - * Serialize a transaction to BCoin "extended format". - * This is the serialization format BCoin uses internally - * to store transactions in the database. The extended - * serialization includes the height, block hash, index, - * timestamp, pending-since time, and optionally a vector - * for the serialized coins. - * @param {NakedTX|TX} tx - * @param {Boolean?} saveCoins - Whether to serialize the coins. - * @param {String?} enc - One of `"hex"` or `null`. - * @returns {Buffer} - */ - -Framer.extendedTX = function extendedTX(tx, saveCoins, writer) { - var height = tx.height; - var index = tx.index; - var changeIndex = tx.changeIndex != null ? tx.changeIndex : -1; - var p = new BufferWriter(writer); - var i, input; - - if (height === -1) - height = 0x7fffffff; - - if (index === -1) - index = 0x7fffffff; - - if (changeIndex === -1) - changeIndex = 0x7fffffff; - - Framer.renderTX(tx, true, p); - p.writeU32(height); - p.writeHash(tx.block || constants.ZERO_HASH); - p.writeU32(index); - p.writeU32(tx.ts); - p.writeU32(tx.ps); - // p.writeU32(changeIndex); - - if (saveCoins) { - p.writeVarint(tx.inputs.length); - for (i = 0; i < tx.inputs.length; i++) { - input = tx.inputs[i]; - - if (!input.coin) { - p.writeVarint(0); - continue; - } - - p.writeVarBytes(Framer.coin(input.coin, false)); - } - } - - if (!writer) - p = p.render(); - - return p; -}; - -Framer._block = function _block(block, useWitness, writer) { - var p = new BufferWriter(writer); - var witnessSize = 0; - var i; - - p.write32(block.version); - p.writeHash(block.prevBlock); - p.writeHash(block.merkleRoot); - p.writeU32(block.ts); - p.writeU32(block.bits); - p.writeU32(block.nonce); - p.writeVarint(block.txs.length); - - for (i = 0; i < block.txs.length; i++) { - Framer.renderTX(block.txs[i], useWitness, p); - witnessSize += p._witnessSize; - } - - if (!writer) - p = p.render(); - - p._witnessSize = witnessSize; - - return p; + return block.renderWitness(writer); }; /** @@ -1049,28 +705,7 @@ Framer._block = function _block(block, useWitness, writer) { */ Framer.merkleBlock = function _merkleBlock(block, writer) { - var p = new BufferWriter(writer); - var i; - - p.writeU32(block.version); - p.writeHash(block.prevBlock); - p.writeHash(block.merkleRoot); - p.writeU32(block.ts); - p.writeU32(block.bits); - p.writeU32(block.nonce); - p.writeU32(block.totalTX); - - p.writeVarint(block.hashes.length); - - for (i = 0; i < block.hashes.length; i++) - p.writeHash(block.hashes[i]); - - p.writeVarBytes(block.flags); - - if (!writer) - p = p.render(); - - return p; + return block.toRaw(writer); }; /** @@ -1082,43 +717,12 @@ Framer.merkleBlock = function _merkleBlock(block, writer) { Framer.headers = function _headers(headers, writer) { var p = new BufferWriter(writer); - var i, header; + var i; p.writeVarint(headers.length); - for (i = 0; i < headers.length; i++) { - header = headers[i]; - p.write32(header.version); - p.writeHash(header.prevBlock); - p.writeHash(header.merkleRoot); - p.writeU32(header.ts); - p.writeU32(header.bits); - p.writeU32(header.nonce); - p.writeVarint(header.totalTX); - } - - if (!writer) - p = p.render(); - - return p; -}; - -/** - * Serialize a block header without any transaction count field. - * @param {NakedBlock|Block|MerkleBlock|Headers|ChainEntry} block - * @param {BufferWriter?} writer - A buffer writer to continue writing from. - * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. - */ - -Framer.blockHeaders = function blockHeaders(block, writer) { - var p = new BufferWriter(writer); - - p.write32(block.version); - p.writeHash(block.prevBlock); - p.writeHash(block.merkleRoot); - p.writeU32(block.ts); - p.writeU32(block.bits); - p.writeU32(block.nonce); + for (i = 0; i < headers.length; i++) + headers[i].toRaw(p); if (!writer) p = p.render(); @@ -1149,6 +753,7 @@ Framer.reject = function reject(details, writer) { p.writeVarString(details.message || '', 'ascii'); p.writeU8(ccode); p.writeVarString(details.reason || '', 'ascii'); + if (details.data) p.writeHash(details.data); @@ -1368,7 +973,8 @@ Framer.UTXOs = function UTXOs(data, writer) { p.writeU32(coin.version); p.writeU32(height); - Framer.output(coin, p); + p.write64(coin.value); + p.writeVarBytes(coin.script.toRaw()); } if (!writer) @@ -1388,7 +994,7 @@ Framer.submitOrder = function submitOrder(order, writer) { var p = new BufferWriter(writer); p.writeHash(order.hash); - Framer.renderTX(order.tx, true, p); + order.tx.toRaw(p); if (!writer) p = p.render(); @@ -1494,130 +1100,6 @@ Framer.feeFilter = function feeFilter(data, writer) { return p; }; -/** - * Calculate total block size and - * witness size without serializing. - * @param {NakedBlock|Block} block - * @returns {Object} In the form of `{size: Number, witnessSize: Number}`. - */ - -Framer.block.sizes = function blockSizes(block) { - var writer = new BufferWriter(); - Framer.witnessBlock(block, writer); - return { - size: writer.written, - witnessSize: writer._witnessSize - }; -}; - -/** - * Calculate total transaction size and - * witness size without serializing. - * @param {NakedBlock|Block} block - * @returns {Object} In the form of `{size: Number, witnessSize: Number}`. - */ - -Framer.tx.sizes = function txSizes(tx) { - var writer = new BufferWriter(); - Framer.renderTX(tx, true, writer); - return { - size: writer.written, - witnessSize: writer._witnessSize - }; -}; - -/** - * Calculate block size with witness (if present). - * @param {NakedBlock|Block} block - * @returns {Number} Size. - */ - -Framer.block.witnessSize = function blockWitnessSize(block) { - return Framer.block.sizes(block).size; -}; - -/** - * Calculate transaction size with witness (if present). - * @param {NakedTX|TX} tx - * @returns {Number} Size. - */ - -Framer.tx.witnessSize = function txWitnessSize(tx) { - return Framer.tx.sizes(tx).size; -}; - -/** - * Calculate transaction size without witness. - * @param {NakedBlock|Block} block - * @returns {Number} Size. - */ - -Framer.block.size = function blockSize(block) { - var writer = new BufferWriter(); - Framer.block(block, writer); - return writer.written; -}; - -/** - * Calculate transaction size without witness. - * @param {NakedTX|TX} tx - * @returns {Number} Size. - */ - -Framer.tx.size = function txSize(tx) { - var writer = new BufferWriter(); - Framer.renderTX(tx, false, writer); - return writer.written; -}; - -/** - * Calculate block virtual size. - * @param {NakedBlock|Block} block - * @returns {Number} Virtual size. - */ - -Framer.block.virtualSize = function blockVirtualSize(block) { - var scale = constants.WITNESS_SCALE_FACTOR; - return (Framer.block.cost(block) + scale - 1) / scale | 0; -}; - -/** - * Calculate transaction virtual size. - * @param {NakedTX|TX} tx - * @returns {Number} Virtual size. - */ - -Framer.tx.virtualSize = function txVirtualSize(tx) { - var scale = constants.WITNESS_SCALE_FACTOR; - return (Framer.tx.cost(tx) + scale - 1) / scale | 0; -}; - -/** - * Calculate block cost. - * @param {NakedBlock|Block} block - * @returns {Number} cost - */ - -Framer.block.cost = function blockCost(block) { - var sizes = Framer.block.sizes(block); - var base = sizes.size - sizes.witnessSize; - var scale = constants.WITNESS_SCALE_FACTOR; - return base * (scale - 1) + sizes.size; -}; - -/** - * Calculate transaction cost. - * @param {NakedTX|TX} tx - * @returns {Number} cost - */ - -Framer.tx.cost = function txCost(tx) { - var sizes = Framer.tx.sizes(tx); - var base = sizes.size - sizes.witnessSize; - var scale = constants.WITNESS_SCALE_FACTOR; - return base * (scale - 1) + sizes.size; -}; - /* * Expose */ diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index ca86e450..4d9d5e00 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -384,12 +384,8 @@ Parser.parseGetUTXOs = function parseGetUTXOs(p) { prevout = []; count = p.readVarint(); - for (i = 0; i < count; i++) { - prevout.push({ - hash: p.readHash('hex'), - index: p.readU32() - }); - } + for (i = 0; i < count; i++) + prevout.push(bcoin.outpoint.fromRaw(p)); return { mempool: mempool, @@ -428,7 +424,7 @@ Parser.parseUTXOs = function parseUTXOs(p) { if (height === 0x7fffffff) height = -1; - coin = Parser.parseOutput(p); + coin = bcoin.output.fromRaw(p); coin.version = version; coin.height = height; coins.push(coin); @@ -642,39 +638,7 @@ Parser.parseInv = function parseInv(p) { */ Parser.parseMerkleBlock = function parseMerkleBlock(p) { - var version, prevBlock, merkleRoot, ts, bits, nonce, totalTX; - var i, hashCount, hashes, flags; - - p = new BufferReader(p); - - version = p.readU32(); - prevBlock = p.readHash('hex'); - merkleRoot = p.readHash('hex'); - ts = p.readU32(); - bits = p.readU32(); - nonce = p.readU32(); - totalTX = p.readU32(); - - hashCount = p.readVarint(); - - hashes = []; - - for (i = 0; i < hashCount; i++) - hashes.push(p.readHash('hex')); - - flags = p.readVarBytes(); - - return { - version: version, - prevBlock: prevBlock, - merkleRoot: merkleRoot, - ts: ts, - bits: bits, - nonce: nonce, - totalTX: totalTX, - hashes: hashes, - flags: flags - }; + return bcoin.merkleblock.fromRaw(p); }; /** @@ -691,91 +655,12 @@ Parser.parseHeaders = function parseHeaders(p) { count = p.readVarint(); - for (i = 0; i < count; i++) { - headers.push({ - version: p.readU32(), // Technically signed - prevBlock: p.readHash('hex'), - merkleRoot: p.readHash('hex'), - ts: p.readU32(), - bits: p.readU32(), - nonce: p.readU32(), - totalTX: p.readVarint() - }); - } + for (i = 0; i < count; i++) + headers.push(bcoin.headers.fromRaw(p)); return headers; }; -/** - * Parse headers packet. - * @param {Buffer|BufferReader} p - * @returns {NakedBlock} - */ - -Parser.parseBlockHeaders = function parseBlockHeaders(p) { - p = new BufferReader(p); - - return { - version: p.readU32(), // Technically signed - prevBlock: p.readHash('hex'), - merkleRoot: p.readHash('hex'), - ts: p.readU32(), - bits: p.readU32(), - nonce: p.readU32() - }; -}; - -/** - * Parse a transaction in "extended" serialization format. - * @param {Buffer|BufferReader} p - * @param {Boolean?} saveCoins - If true, the function will - * attempt to parse the coins. - * @param {String?} enc - One of `"hex"` or `null`. - * @returns {NakedTX} - A "naked" transaction object. - */ - -Parser.parseExtendedTX = function parseExtendedTX(p, saveCoins) { - var i, tx, coinCount, coin; - - p = new BufferReader(p); - - tx = Parser.parseTX(p); - - tx.height = p.readU32(); - tx.block = p.readHash('hex'); - tx.index = p.readU32(); - tx.ts = p.readU32(); - tx.ps = p.readU32(); - // tx.changeIndex = p.readU32(); - - if (tx.block === constants.NULL_HASH) - tx.block = null; - - if (tx.height === 0x7fffffff) - tx.height = -1; - - if (tx.index === 0x7fffffff) - tx.index = -1; - - if (tx.changeIndex === 0x7fffffff) - tx.changeIndex = -1; - - if (saveCoins) { - coinCount = p.readVarint(); - for (i = 0; i < coinCount; i++) { - coin = p.readVarBytes(); - if (coin.length === 0) - continue; - coin = Parser.parseCoin(coin, false); - coin.hash = tx.inputs[i].prevout.hash; - coin.index = tx.inputs[i].prevout.index; - tx.inputs[i].coin = coin; - } - } - - return tx; -}; - /** * Parse block packet. * @param {Buffer|BufferReader} p @@ -783,46 +668,7 @@ Parser.parseExtendedTX = function parseExtendedTX(p, saveCoins) { */ Parser.parseBlock = function parseBlock(p) { - var txs = []; - var version, prevBlock, merkleRoot, ts, bits, nonce; - var i, totalTX, tx; - var raw, size, witnessSize; - - p = new BufferReader(p); - - p.start(); - - version = p.readU32(); // Technically signed - prevBlock = p.readHash('hex'); - merkleRoot = p.readHash('hex'); - ts = p.readU32(); - bits = p.readU32(); - nonce = p.readU32(); - totalTX = p.readVarint(); - - witnessSize = 0; - - for (i = 0; i < totalTX; i++) { - tx = Parser.parseTX(p); - witnessSize += tx._witnessSize; - txs.push(tx); - } - - raw = p.endData(); - size = raw.length; - - return { - version: version, - prevBlock: prevBlock, - merkleRoot: merkleRoot, - ts: ts, - bits: bits, - nonce: nonce, - txs: txs, - _raw: raw, - _size: size, - _witnessSize: witnessSize - }; + return bcoin.block.fromRaw(p); }; /** @@ -832,135 +678,7 @@ Parser.parseBlock = function parseBlock(p) { */ Parser.parseMemBlock = function parseMemBlock(p) { - var version, prevBlock, merkleRoot; - var ts, bits, nonce, totalTX, height; - var inCount, input; - - p = new BufferReader(p); - - version = p.readU32(); // Technically signed - prevBlock = p.readHash('hex'); - merkleRoot = p.readHash('hex'); - ts = p.readU32(); - bits = p.readU32(); - nonce = p.readU32(); - - totalTX = p.readVarint(); - - if (version > 1 && totalTX > 0) { - p.readU32(); // Technically signed - inCount = p.readVarint(); - - if (inCount === 0) { - if (p.readU8() !== 0) - inCount = p.readVarint(); - } - - if (inCount > 0) - input = Parser.parseInput(p); - } - - if (input) - height = bcoin.script.getCoinbaseHeight(input.script.raw); - else - height = -1; - - return { - version: version, - prevBlock: prevBlock, - merkleRoot: merkleRoot, - ts: ts, - bits: bits, - nonce: nonce, - totalTX: totalTX, - coinbaseHeight: height, - txs: [], - raw: p.data - }; -}; - -/** - * Parse serialized input. - * @param {Buffer|BufferReader} p - * @returns {NakedInput} - */ - -Parser.parseInput = function parseInput(p) { - var hash, index, script, sequence; - - p = new BufferReader(p); - - hash = p.readHash('hex'); - index = p.readU32(); - script = Parser.parseScript(p); - sequence = p.readU32(); - - return { - prevout: { - hash: hash, - index: index - }, - coin: null, - script: script, - sequence: sequence - }; -}; - -/** - * Parse serialized output. - * @param {Buffer|BufferReader} p - * @returns {NakedOutput} - */ - -Parser.parseOutput = function parseOutput(p) { - var value, script; - - p = new BufferReader(p); - - value = p.read64N(); - script = Parser.parseScript(p); - - return { - value: value, - script: script - }; -}; - -/** - * Parse serialized coin. - * @param {Buffer|BufferReader} p - * @param {Boolean} extended - Whether to parse the hash and index. - * @returns {NakedCoin} - */ - -Parser.parseCoin = function parseCoin(p, extended) { - var version, height, value, script, hash, index, coinbase; - - p = new BufferReader(p); - - version = p.readU32(); - height = p.readU32(); - value = p.read64N(); - script = Parser.parseScript(p); - coinbase = p.readU8() === 1; - - if (extended) { - hash = p.readHash('hex'); - index = p.readU32(); - } - - if (height === 0x7fffffff) - height = -1; - - return { - version: version, - height: height, - value: value, - script: script, - coinbase: coinbase, - hash: hash, - index: index - }; + return bcoin.memblock.fromRaw(p); }; /** @@ -971,180 +689,7 @@ Parser.parseCoin = function parseCoin(p, extended) { */ Parser.parseTX = function parseTX(p) { - var inCount, inputs, input; - var outCount, outputs; - var version, locktime, i; - var raw, size, witnessSize; - - if (Parser.isWitnessTX(p)) - return Parser.parseWitnessTX(p); - - p = new BufferReader(p); - - p.start(); - - version = p.readU32(); // Technically signed - - inCount = p.readVarint(); - inputs = []; - - for (i = 0; i < inCount; i++) { - input = Parser.parseInput(p); - input.witness = { items: [] }; - inputs.push(input); - } - - outCount = p.readVarint(); - outputs = []; - - for (i = 0; i < outCount; i++) - outputs.push(Parser.parseOutput(p)); - - locktime = p.readU32(); - - raw = p.endData(); - size = raw.length; - witnessSize = 0; - - return { - version: version, - flag: 1, - inputs: inputs, - outputs: outputs, - locktime: locktime, - _raw: raw, - _size: size, - _witnessSize: witnessSize - }; -}; - -/** - * Test whether data is a witness transaction. - * @param {Buffer|BufferReader} p - * @returns {Boolean} - */ - -Parser.isWitnessTX = function isWitnessTX(p) { - if (Buffer.isBuffer(p)) { - if (p.length < 12) - return false; - - return p[4] === 0 && p[5] !== 0; - } - - if (p.left() < 12) - return false; - - return p.data[p.offset + 4] === 0 && p.data[p.offset + 5] !== 0; -}; - -/** - * Parse tx packet in witness serialization. - * @param {Buffer|BufferReader} p - * @returns {NakedTX} - */ - -Parser.parseWitnessTX = function parseWitnessTX(p) { - var inCount, inputs, witness; - var outCount, outputs; - var marker, flag; - var version, locktime, i; - var raw, size, witnessSize, hasWitness; - - p = new BufferReader(p); - - p.start(); - - version = p.readU32(); // Technically signed - marker = p.readU8(); - flag = p.readU8(); - - if (marker !== 0) - throw new Error('Invalid witness tx (marker != 0)'); - - if (flag === 0) - throw new Error('Invalid witness tx (flag == 0)'); - - inCount = p.readVarint(); - inputs = []; - - for (i = 0; i < inCount; i++) - inputs.push(Parser.parseInput(p)); - - outCount = p.readVarint(); - outputs = []; - - for (i = 0; i < outCount; i++) - outputs.push(Parser.parseOutput(p)); - - p.start(); - - for (i = 0; i < inCount; i++) { - witness = Parser.parseWitness(p); - inputs[i].witness = witness; - if (witness.items.length > 0) - hasWitness = true; - } - - if (!hasWitness) - throw new Error('Witness tx has an empty witness.'); - - witnessSize = p.end() + 2; - - locktime = p.readU32(); - - raw = p.endData(); - size = raw.length; - - return { - version: version, - flag: flag, - inputs: inputs, - outputs: outputs, - locktime: locktime, - _raw: raw, - _size: size, - _witnessSize: witnessSize - }; -}; - -/** - * Parse serialized script (with varint length). - * @param {Buffer|BufferReader} p - * @returns {NakedScript} - */ - -Parser.parseScript = function parseScript(p) { - var data; - - p = new BufferReader(p); - data = p.readVarBytes(); - - return { - raw: data - }; -}; - -/** - * Parse serialized witness. - * @param {Buffer|BufferReader} p - * @returns {NakedWitness} - */ - -Parser.parseWitness = function parseWitness(p) { - var items = []; - var chunkCount, i; - - p = new BufferReader(p); - - chunkCount = p.readVarint(); - - for (i = 0; i < chunkCount; i++) - items.push(p.readVarBytes()); - - return { - items: items - }; + return bcoin.tx.fromRaw(p); }; /** diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index bf81d72d..2f68ceca 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -32,12 +32,20 @@ var ScriptError = bcoin.errors.ScriptError; */ function Witness(items) { + if (items instanceof Witness) + return items; + if (!(this instanceof Witness)) return new Witness(items); - if (!items) - items = []; + this.items = []; + this.redeem = null; + if (items) + this.fromOptions(items); +} + +Witness.prototype.fromOptions = function fromOptions(items) { if (items.items) items = items.items; @@ -46,7 +54,13 @@ function Witness(items) { this.redeem = null; assert(Array.isArray(this.items)); -} + + return this; +}; + +Witness.fromOptions = function fromOptions(options) { + return new Witness().fromOptions(options); +}; Witness.prototype.toArray = function toArray() { return this.items.slice(); @@ -247,11 +261,19 @@ Witness.prototype.indexOf = function indexOf(data) { * @returns {Buffer|String} Serialized script. */ -Witness.prototype.toRaw = function toRaw(enc) { - var data = bcoin.protocol.framer.witness(this); - if (enc === 'hex') - data = data.toString('hex'); - return data; +Witness.prototype.toRaw = function toRaw(writer) { + var p = bcoin.writer(writer); + var i; + + p.writeVarint(this.items.length); + + for (i = 0; i < this.items.length; i++) + p.writeVarBytes(this.items[i]); + + if (!writer) + p = p.render(); + + return p; }; /** @@ -417,18 +439,15 @@ Witness.encodeItem = function encodeItem(data) { return data; }; -/** - * Create a witness from a serialized buffer. - * @param {Buffer|String} data - Serialized witness. - * @param {String?} enc - Either `"hex"` or `null`. - * @returns {Object} Naked witness object. - */ +Witness.prototype.fromRaw = function fromRaw(data) { + var p = bcoin.reader(data); + var chunkCount = p.readVarint(); + var i; -Witness.parseRaw = function parseRaw(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); + for (i = 0; i < chunkCount; i++) + this.items.push(p.readVarBytes()); - return bcoin.protocol.parser.parseWitness(data); + return this; }; /** @@ -439,7 +458,9 @@ Witness.parseRaw = function parseRaw(data, enc) { */ Witness.fromRaw = function fromRaw(data, enc) { - return new Witness(Witness.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new Witness().fromRaw(data); }; /** @@ -1012,28 +1033,52 @@ function Script(raw) { if (!(this instanceof Script)) return new Script(raw); - if (!raw) - raw = STACK_FALSE; - - if (raw.raw) - raw = raw.raw; - - this.raw = null; - this.code = null; + this.raw = STACK_FALSE; + this.code = []; this.redeem = null; - if (Array.isArray(raw)) { - raw = Script.parseArray(raw); - this.raw = Script.encode(raw); - this.code = raw; + if (raw) + this.fromOptions(raw); +} + +Script.prototype.fromOptions = function fromOptions(options) { + var code, raw; + + if (Buffer.isBuffer(options)) { + raw = options; + } else if (Array.isArray(options)) { + code = options; } else { - this.raw = raw; - this.code = Script.decode(raw); + code = options.code; + raw = options.raw; + } + + if (code) + code = Script.parseArray(code); + + this.raw = raw; + this.code = code; + this.redeem = null; + + if (!this.raw) { + assert(this.code); + this.raw = Script.encode(this.code); + } else if (!this.code) { + assert(this.raw); + this.code = Script.decode(this.raw); } assert(Buffer.isBuffer(this.raw)); assert(Array.isArray(this.code)); -} + + return this; +}; + +Script.fromOptions = function fromOptions(raw) { + if (raw instanceof Script) + return raw; + return new Script().fromOptions(raw); +}; /** * Convert the script to an array of @@ -1101,17 +1146,6 @@ Script.prototype.toASM = function toASM(decode) { return Script.formatASM(this.code, decode); }; -/** - * Encode the script to a Buffer. Note that this - * will _not_ contain the varint size before it. - * This allows it to be hashed for scripthashes. - * @returns {Buffer} Serialized script. - */ - -Script.prototype.encode = function encode() { - return this.raw; -}; - /** * Re-encode the script internally. Useful if you * changed something manually in the `code` array. @@ -1127,11 +1161,12 @@ Script.prototype.compile = function compile() { * @returns {Buffer|String} Serialized script. */ -Script.prototype.toRaw = function toRaw(enc) { - var data = this.encode(); - if (enc === 'hex') - data = data.toString('hex'); - return data; +Script.prototype.toRaw = function toRaw(writer) { + if (writer) { + writer.writeVarBytes(this.raw); + return writer; + } + return this.raw; }; /** @@ -3636,7 +3671,7 @@ Script.prototype.isPushOnly = function isPushOnly() { if (op.data) continue; - if (op.value == -1) + if (op.value === -1) return false; if (op.value > opcodes.OP_16) @@ -4112,25 +4147,6 @@ Script.sign = function sign(msg, key, type) { return p.render(); }; -/** - * Parse a serialized script, returning the "naked" - * representation of a Script object (the same - * properties, but it is not instantiated -- suitable - * as an options object for Script). - * @param {Buffer|String} data - Serialized script. - * @param {String?} enc - Either `"hex"` or `null`. - * @returns {Object} Naked script object. - */ - -Script.parseRaw = function parseRaw(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); - - return { - raw: data - }; -}; - /** * Create a script from a serialized buffer. * @param {Buffer|String} data - Serialized script. @@ -4138,8 +4154,20 @@ Script.parseRaw = function parseRaw(data, enc) { * @returns {Script} */ +Script.prototype.fromRaw = function fromRaw(data) { + if (data instanceof bcoin.reader) + data = data.readVarBytes(); + + this.raw = data; + this.code = Script.decode(data); + + return this; +}; + Script.fromRaw = function fromRaw(data, enc) { - return new Script(Script.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new Script().fromRaw(data); }; /** @@ -4344,8 +4372,8 @@ function Opcode(value, data) { * @returns {Buffer} */ -Opcode.prototype.toRaw = function toRaw() { - return Script.encode([this]); +Opcode.prototype.toRaw = function toRaw(writer) { + return Script.encode([this], writer); }; /** diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index c70a0e64..26932221 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -49,11 +49,41 @@ var BufferWriter = require('./writer'); */ function TX(data) { - var i; - if (!(this instanceof TX)) return new TX(data); + this.version = 1; + this.flag = 1; + this.inputs = []; + this.outputs = []; + this.locktime = 0; + this.ts = 0; + this.block = null; + this.index = -1; + this.ps = utils.now(); + this.height = -1; + this.mutable = false; + + this._hash = null; + this._whash = null; + + this._raw = null; + this._size = null; + this._witnessSize = null; + + this._outputValue = null; + this._inputValue = null; + this._hashPrevouts = null; + this._hashSequence = null; + this._hashOutputs = null; + + if (data) + this.fromOptions(data); +} + +TX.prototype.fromOptions = function fromOptions(data) { + var i; + assert(data, 'TX data is required.'); assert(typeof data.version === 'number'); assert(typeof data.flag === 'number'); @@ -91,7 +121,13 @@ function TX(data) { for (i = 0; i < data.outputs.length; i++) this.outputs.push(new bcoin.output(data.outputs[i])); -} + + return this; +}; + +TX.fromOptions = function fromOptions(data) { + return new TX().fromOptions(data); +}; /** * Clone the transaction. @@ -218,8 +254,14 @@ TX.prototype.witnessHash = function witnessHash(enc) { * @returns {Buffer} Serialized transaction. */ -TX.prototype.render = function render() { - return this.getRaw(); +TX.prototype.render = function render(writer) { + var raw = this.getRaw(); + if (writer) { + writer.writeBytes(raw); + writer._witnessSize = raw._witnessSize; + return writer; + } + return raw; }; /** @@ -229,11 +271,17 @@ TX.prototype.render = function render() { * @returns {Buffer} Serialized transaction. */ -TX.prototype.renderNormal = function renderNormal() { +TX.prototype.renderNormal = function renderNormal(writer) { var raw = this.getRaw(); - if (!bcoin.protocol.parser.isWitnessTX(raw)) + if (!TX.isWitness(raw)) { + if (writer) { + writer.writeBytes(raw); + writer._witnessSize = raw._witnessSize; + return writer; + } return raw; - return bcoin.protocol.framer.tx(this); + } + return this.frameNormal(writer); }; /** @@ -243,11 +291,17 @@ TX.prototype.renderNormal = function renderNormal() { * @returns {Buffer} Serialized transaction. */ -TX.prototype.renderWitness = function renderWitness() { +TX.prototype.renderWitness = function renderWitness(writer) { var raw = this.getRaw(); - if (bcoin.protocol.parser.isWitnessTX(raw)) + if (TX.isWitness(raw)) { + if (writer) { + writer.writeBytes(raw); + writer._witnessSize = raw._witnessSize; + return writer; + } return raw; - return bcoin.protocol.framer.witnessTX(this); + } + return this.frameWitness(writer); }; /** @@ -268,9 +322,9 @@ TX.prototype.getRaw = function getRaw() { } if (this.hasWitness()) - raw = bcoin.protocol.framer.witnessTX(this); + raw = this.frameWitness(); else - raw = bcoin.protocol.framer.tx(this); + raw = this.frameNormal(); if (!this.mutable) { this._raw = raw; @@ -287,8 +341,16 @@ TX.prototype.getRaw = function getRaw() { */ TX.prototype.getSizes = function getSizes() { - if (this.mutable) - return bcoin.protocol.framer.tx.sizes(this); + var writer; + + if (this.mutable) { + writer = new BufferWriter(); + this.toRaw(writer); + return { + size: writer.written, + witnessSize: writer._witnessSize + }; + } this.getRaw(); @@ -401,6 +463,7 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { // Clone the transaction. copy = { version: this.version, + flag: 1, inputs: [], outputs: [], locktime: this.locktime @@ -410,7 +473,6 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { copy.inputs.push({ prevout: this.inputs[i].prevout, script: this.inputs[i].script, - witness: this.inputs[i].witness, sequence: this.inputs[i].sequence }); } @@ -472,7 +534,7 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { } // Render the copy and append the hashtype. - bcoin.protocol.framer.tx(copy, p); + TX(copy).toRaw(p); p.writeU32(type); return utils.dsha256(p.render()); @@ -480,7 +542,7 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, type) { var p = new BufferWriter(); - var i, prevout, hashPrevouts, hashSequence, hashOutputs; + var i, hashPrevouts, hashSequence, hashOutputs; if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -496,11 +558,8 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, type) { hashPrevouts = this._hashPrevouts; } else { hashPrevouts = new BufferWriter(); - for (i = 0; i < this.inputs.length; i++) { - prevout = this.inputs[i].prevout; - hashPrevouts.writeHash(prevout.hash); - hashPrevouts.writeU32(prevout.index); - } + for (i = 0; i < this.inputs.length; i++) + this.inputs[i].prevout.toRaw(hashPrevouts); hashPrevouts = utils.dsha256(hashPrevouts.render()); if (!this.mutable) this._hashPrevouts = hashPrevouts; @@ -533,13 +592,13 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, type) { } else { hashOutputs = new BufferWriter(); for (i = 0; i < this.outputs.length; i++) - bcoin.protocol.framer.output(this.outputs[i], hashOutputs); + this.outputs[i].toRaw(hashOutputs); hashOutputs = utils.dsha256(hashOutputs.render()); if (!this.mutable) this._hashOutputs = hashOutputs; } } else if ((type & 0x1f) === constants.hashType.SINGLE && index < this.outputs.length) { - hashOutputs = bcoin.protocol.framer.output(this.outputs[index]); + hashOutputs = this.outputs[index].toRaw(); hashOutputs = utils.dsha256(hashOutputs); } else { hashOutputs = utils.copy(constants.ZERO_HASH); @@ -550,7 +609,7 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, type) { p.writeBytes(hashSequence); p.writeHash(this.inputs[index].prevout.hash); p.writeU32(this.inputs[index].prevout.index); - p.writeVarBytes(prev.encode()); + p.writeVarBytes(prev.toRaw()); p.write64(this.inputs[index].coin.value); p.writeU32(this.inputs[index].sequence); p.writeBytes(hashOutputs); @@ -1640,7 +1699,7 @@ TX.prototype.getPrevout = function getPrevout() { TX.prototype.isWatched = function isWatched(filter) { var found = false; - var i, input, output, hash, index, outpoint; + var i, input, output, outpoint; if (!filter) return false; @@ -1656,12 +1715,12 @@ TX.prototype.isWatched = function isWatched(filter) { // Test the output script if (output.script.test(filter)) { if (filter.update === constants.filterFlags.ALL) { - outpoint = bcoin.protocol.framer.outpoint(this.hash(), i); - filter.add(outpoint); + outpoint = bcoin.outpoint.fromTX(this, i); + filter.add(outpoint.toRaw()); } else if (filter.update === constants.filterFlags.PUBKEY_ONLY) { if (output.script.isPubkey(true) || output.script.isMultisig()) { - outpoint = bcoin.protocol.framer.outpoint(this.hash(), i); - filter.add(outpoint); + outpoint = bcoin.outpoint.fromTX(this, i); + filter.add(outpoint.toRaw()); } } found = true; @@ -1675,9 +1734,7 @@ TX.prototype.isWatched = function isWatched(filter) { // 4. Test data elements in input scripts for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; - hash = input.prevout.hash; - index = input.prevout.index; - outpoint = bcoin.protocol.framer.outpoint(hash, index); + outpoint = input.prevout.toRaw(); // Test the COutPoint structure if (filter.test(outpoint)) @@ -1835,25 +1892,31 @@ TX.prototype.toJSON = function toJSON() { * for passing to the TX constructor). */ -TX.parseJSON = function fromJSON(json) { +TX.prototype.fromJSON = function fromJSON(json) { assert.equal(json.type, 'tx'); - return { - block: json.block ? utils.revHex(json.block) : null, - height: json.height, - ts: json.ts, - ps: json.ps, - index: json.index, - changeIndex: json.changeIndex || -1, - version: json.version, - flag: json.flag, - inputs: json.inputs.map(function(input) { - return bcoin.input.parseJSON(input); - }), - outputs: json.outputs.map(function(output) { - return bcoin.output.parseJSON(output); - }), - locktime: json.locktime - }; + + this.block = json.block ? utils.revHex(json.block) : null; + this.height = json.height; + this.ts = json.ts; + this.ps = json.ps; + this.index = json.index; + this.changeIndex = json.changeIndex || -1; + + this.version = json.version; + + this.flag = json.flag; + + this.inputs = json.inputs.map(function(input) { + return bcoin.input.fromJSON(input); + }), + + this.outputs = json.outputs.map(function(output) { + return bcoin.output.fromJSON(output); + }), + + this.locktime = json.locktime; + + return this; }; /** @@ -1864,8 +1927,7 @@ TX.parseJSON = function fromJSON(json) { */ TX.fromJSON = function fromJSON(json) { - assert.equal(json.type, 'tx'); - return new TX(TX.parseJSON(json)); + return new TX().fromJSON(json); }; /** @@ -1875,27 +1937,8 @@ TX.fromJSON = function fromJSON(json) { * @returns {Buffer|String} */ -TX.prototype.toRaw = function toRaw(enc) { - var data = this.render(); - - if (enc === 'hex') - data = data.toString('hex'); - - return data; -}; - -/** - * Parse a serialized transaction. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {NakedTX} A "naked" transaction object. - */ - -TX.parseRaw = function parseRaw(data, enc) { - if (enc === 'hex') - data = new Buffer(data, 'hex'); - - return bcoin.protocol.parser.parseTX(data); +TX.prototype.toRaw = function toRaw(writer) { + return this.render(writer); }; /** @@ -1906,7 +1949,207 @@ TX.parseRaw = function parseRaw(data, enc) { */ TX.fromRaw = function fromRaw(data, enc) { - return new bcoin.tx(TX.parseRaw(data, enc)); + if (typeof data === 'string') + data = new Buffer(data, enc); + return new TX().fromRaw(data); +}; + +/** + * Parse tx packet (will automatically switch to witness + * parsing if a witness transaction is detected). + * @param {Buffer|BufferReader} p + * @returns {NakedTX} + */ + +TX.prototype.fromRaw = function fromRaw(data) { + var p, i, inCount, outCount, input; + + if (TX.isWitness(data)) + return this.fromWitness(data); + + p = bcoin.reader(data); + p.start(); + + this.version = p.readU32(); // Technically signed + + inCount = p.readVarint(); + + this.inputs = []; + + for (i = 0; i < inCount; i++) { + input = bcoin.input.fromRaw(p); + input.witness = new bcoin.witness(); + this.inputs.push(input); + } + + outCount = p.readVarint(); + + this.outputs = []; + + for (i = 0; i < outCount; i++) + this.outputs.push(bcoin.output.fromRaw(p)); + + this.locktime = p.readU32(); + + this._raw = p.endData(); + this._size = this._raw.length; + this._witnessSize = 0; + + return this; +}; + +/** + * Parse tx packet in witness serialization. + * @param {Buffer|BufferReader} p + * @returns {NakedTX} + */ + +TX.prototype.fromWitness = function fromWitness(data) { + var p = bcoin.reader(data); + var i, marker, inCount, outCount, witness, hasWitness; + + p.start(); + + this.version = p.readU32(); // Technically signed + marker = p.readU8(); + this.flag = p.readU8(); + + if (marker !== 0) + throw new Error('Invalid witness tx (marker != 0)'); + + if (this.flag === 0) + throw new Error('Invalid witness tx (flag == 0)'); + + inCount = p.readVarint(); + + this.inputs = []; + + for (i = 0; i < inCount; i++) + this.inputs.push(bcoin.input.fromRaw(p)); + + outCount = p.readVarint(); + + this.outputs = []; + + for (i = 0; i < outCount; i++) + this.outputs.push(bcoin.output.fromRaw(p)); + + p.start(); + + for (i = 0; i < inCount; i++) { + witness = bcoin.witness.fromRaw(p); + this.inputs[i].witness = witness; + if (witness.items.length > 0) + hasWitness = true; + } + + if (!hasWitness) + throw new Error('Witness tx has an empty witness.'); + + this._witnessSize = p.end() + 2; + + this.locktime = p.readU32(); + + this._raw = p.endData(); + this._size = this._raw.length; + + return this; +}; + +/** + * Test whether data is a witness transaction. + * @param {Buffer|BufferReader} p + * @returns {Boolean} + */ + +TX.isWitness = function isWitness(p) { + if (Buffer.isBuffer(p)) { + if (p.length < 12) + return false; + + return p[4] === 0 && p[5] !== 0; + } + + if (p.left() < 12) + return false; + + return p.data[p.offset + 4] === 0 && p.data[p.offset + 5] !== 0; +}; + +/** + * Serialize transaction without witness. + * @param {NakedTX|TX} tx + * @param {BufferWriter?} writer - A buffer writer to continue writing from. + * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. + */ + +TX.prototype.frameNormal = function frameNormal(writer) { + var p = bcoin.writer(writer); + var i; + + p.write32(this.version); + + p.writeVarint(this.inputs.length); + + for (i = 0; i < this.inputs.length; i++) + this.inputs[i].toRaw(p); + + p.writeVarint(this.outputs.length); + + for (i = 0; i < this.outputs.length; i++) + this.outputs[i].toRaw(p); + + p.writeU32(this.locktime); + + if (!writer) + p = p.render(); + + p._witnessSize = 0; + + return p; +}; + +/** + * Serialize transaction with witness. Calculates the witness + * size as it is framing (exposed on return value as `_witnessSize`). + * @param {NakedTX|TX} tx + * @param {BufferWriter?} writer - A buffer writer to continue writing from. + * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. + */ + +TX.prototype.frameWitness = function frameWitness(writer) { + var p = bcoin.writer(writer); + var witnessSize = 0; + var i, start; + + p.write32(this.version); + p.writeU8(0); + p.writeU8(this.flag || 1); + + p.writeVarint(this.inputs.length); + + for (i = 0; i < this.inputs.length; i++) + this.inputs[i].toRaw(p); + + p.writeVarint(this.outputs.length); + + for (i = 0; i < this.outputs.length; i++) + this.outputs[i].toRaw(p); + + for (i = 0; i < this.inputs.length; i++) { + start = p.written; + this.inputs[i].witness.toRaw(p); + witnessSize += p.written - start; + } + + p.writeU32(this.locktime); + + if (!writer) + p = p.render(); + + p._witnessSize = witnessSize + 2; + + return p; }; /** @@ -1921,21 +2164,49 @@ TX.fromRaw = function fromRaw(data, enc) { * @returns {Buffer} */ -TX.prototype.toExtended = function toExtended(saveCoins, enc) { - var data, tmp; +TX.prototype.toExtended = function toExtended(saveCoins, writer) { + var height = this.height; + var index = this.index; + var changeIndex = this.changeIndex != null ? this.changeIndex : -1; + var p = bcoin.writer(writer); + var i, input; - if (typeof saveCoins === 'string') { - tmp = saveCoins; - saveCoins = enc; - enc = tmp; + if (height === -1) + height = 0x7fffffff; + + if (index === -1) + index = 0x7fffffff; + + if (changeIndex === -1) + changeIndex = 0x7fffffff; + + this.toRaw(p); + + p.writeU32(height); + p.writeHash(this.block || constants.ZERO_HASH); + p.writeU32(index); + p.writeU32(this.ts); + p.writeU32(this.ps); + // p.writeU32(changeIndex); + + if (saveCoins) { + p.writeVarint(this.inputs.length); + for (i = 0; i < this.inputs.length; i++) { + input = this.inputs[i]; + + if (!input.coin) { + p.writeVarint(0); + continue; + } + + p.writeVarBytes(input.coin.toRaw()); + } } - data = bcoin.protocol.framer.extendedTX(this, saveCoins); + if (!writer) + p = p.render(); - if (enc === 'hex') - data = data.toString('hex'); - - return data; + return p; }; /** @@ -1947,19 +2218,45 @@ TX.prototype.toExtended = function toExtended(saveCoins, enc) { * @returns {NakedTX} - A "naked" transaction object. */ -TX.parseExtended = function parseExtended(data, saveCoins, enc) { - var tmp; +TX.prototype.fromExtended = function fromExtended(data, saveCoins) { + var p = bcoin.reader(data); + var i, coinCount, coin; - if (typeof saveCoins === 'string') { - tmp = saveCoins; - saveCoins = enc; - enc = tmp; + this.fromRaw(p); + + this.height = p.readU32(); + this.block = p.readHash('hex'); + this.index = p.readU32(); + this.ts = p.readU32(); + this.ps = p.readU32(); + // this.changeIndex = p.readU32(); + + if (this.block === constants.NULL_HASH) + this.block = null; + + if (this.height === 0x7fffffff) + this.height = -1; + + if (this.index === 0x7fffffff) + this.index = -1; + + if (this.changeIndex === 0x7fffffff) + this.changeIndex = -1; + + if (saveCoins) { + coinCount = p.readVarint(); + for (i = 0; i < coinCount; i++) { + coin = p.readVarBytes(); + if (coin.length === 0) + continue; + coin = bcoin.coin.fromRaw(coin); + coin.hash = this.inputs[i].prevout.hash; + coin.index = this.inputs[i].prevout.index; + this.inputs[i].coin = coin; + } } - if (enc === 'hex') - data = new Buffer(data, 'hex'); - - return bcoin.protocol.parser.parseExtendedTX(data, saveCoins); + return this; }; /** @@ -1973,7 +2270,13 @@ TX.parseExtended = function parseExtended(data, saveCoins, enc) { */ TX.fromExtended = function fromExtended(data, saveCoins, enc) { - return new TX(TX.parseExtended(data, saveCoins, enc)); + if (typeof saveCoins === 'string') { + enc = saveCoins; + saveCoins = false; + } + if (typeof data === 'string') + data = new Buffer(data, enc); + return new TX().fromExtended(data, saveCoins); }; /** diff --git a/lib/bcoin/workers.js b/lib/bcoin/workers.js index 60bdcace..93c2d62a 100644 --- a/lib/bcoin/workers.js +++ b/lib/bcoin/workers.js @@ -817,13 +817,13 @@ Framer.item = function _item(item, p) { } else { if (item instanceof bcoin.block) { p.writeU8(40); - bcoin.protocol.framer.witnessBlock(item, p); + item.render(p); } else if (item instanceof bcoin.tx) { p.writeU8(41); - bcoin.protocol.framer.extendedTX(item, true, p); + item.toExtended(true, p); } else if (item instanceof bcoin.coin) { p.writeU8(42); - bcoin.protocol.framer.coin(item, true, p); + item.toExtended(p); } else if (item instanceof bcoin.chainentry) { p.writeU8(43); item.toRaw(p); diff --git a/test/block-test.js b/test/block-test.js index c9522a38..f4942964 100644 --- a/test/block-test.js +++ b/test/block-test.js @@ -32,7 +32,7 @@ describe('Block', function() { ], flags: new Buffer([245, 122, 0]) }); - var raw = mblock.toRaw('hex'); + var raw = mblock.toRaw().toString('hex'); var block; var raw2 = '02000000d1831d4411bdfda89d9d8c842b541beafd1437fc560dbe5c0' diff --git a/test/tx-test.js b/test/tx-test.js index 4c0d577b..947dd17d 100644 --- a/test/tx-test.js +++ b/test/tx-test.js @@ -29,7 +29,7 @@ function parseTX(file) { function parseExtended(file) { file = fs.readFileSync(__dirname + '/' + file, 'utf8').trim(); - return bcoin.tx.fromExtended(file, 'hex', true); + return bcoin.tx.fromExtended(file, true, 'hex'); } function clearCache(tx, nocache) { @@ -152,7 +152,7 @@ describe('TX', function() { assert(coolest.verify(constants.flags.VERIFY_NONE)); }); - it('should parse witness tx properly', function() { + it('should parse witness tx properly' + suffix, function() { clearCache(wtx, nocache); assert.equal(wtx.inputs.length, 5); assert.equal(wtx.outputs.length, 1980); @@ -503,14 +503,14 @@ describe('TX', function() { assert(tx.outputs[0].value.bitLength() === 56); var raw = tx.toRaw() assert.throws(function() { - tx.fromRaw(raw); + bcoin.tx.fromRaw(raw); }); delete tx._raw; tx.outputs[0].value = new bn('00ffffffffffffff', 'hex').ineg(); assert(tx.outputs[0].value.bitLength() === 56); var raw = tx.toRaw() assert.throws(function() { - tx.fromRaw(raw); + bcoin.tx.fromRaw(raw); }); });