From d53050993ca9849b400fa6ae1220c6742f6712f9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 28 Feb 2016 10:23:39 -0800 Subject: [PATCH] better witnessSize. --- lib/bcoin/block.js | 71 +++++++-------- lib/bcoin/mtx.js | 70 ++++++++++++--- lib/bcoin/protocol/framer.js | 12 +-- lib/bcoin/tx.js | 167 +++++++++++++++++------------------ 4 files changed, 176 insertions(+), 144 deletions(-) diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index ba7cbdea..76655bbe 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -42,61 +42,58 @@ function Block(data) { utils.inherits(Block, bcoin.abstractblock); Block.prototype.render = function render() { - if (this._raw) { - if (this._witnessSize === 0) - return this._raw; - return bcoin.protocol.framer.block(this); - } + return this.getRaw(); +}; - if (this.hasWitness()) - return bcoin.protocol.framer.block(this); +Block.prototype.renderNormal = function renderNormal() { + if (!this._witnessSize) + return this._raw; - this._raw = bcoin.protocol.framer.block(this); - this._size = this._raw.length; - this._witnessSize = 0; - - return this._raw; + return bcoin.protocol.framer.block(this); }; Block.prototype.renderWitness = function renderWitness() { + this.getRaw(); + + if (this._witnessSize) + return this._raw; + + return bcoin.protocol.framer.witnessBlock(this); +}; + +Block.prototype.getRaw = function getRaw() { if (this._raw) { - if (this._witnessSize > 0) - return this._raw; - return bcoin.protocol.framer.witnessBlock(this); + assert(this._size > 0); + assert(this._witnessSize >= 0); + return this._raw; } - if (!this.hasWitness()) - return bcoin.protocol.framer.witnessBlock(this); + if (this.hasWitness()) + raw = bcoin.protocol.framer.witnessBlock(this); + else + raw = bcoin.protocol.framer.block(this); - this._raw = bcoin.protocol.framer.witnessBlock(this); - this._size = this._raw.length; - this._witnessSize = this._raw._witnessSize; + this._raw = raw; + this._size = raw.length; + this._witnessSize = raw._witnessSize; return this._raw; }; -Block.prototype.getBlockSize = function getBlockSize() { - var size = this._size; - var witnessSize = this._witnessSize; - var raw, base; +Block.prototype.getVirtualSize = function getVirtualSize() { + var size, witnessSize, base; - if (!this._size) { - raw = this.renderWitness(); - size = raw.length; - witnessSize = raw._witnessSize; - } + this.getRaw(); - // Virtual size: - if (witnessSize > 0) { - base = size - witnessSize; - return (base * 4 + witnessSize + 3) / 4 | 0; - } + size = this._size; + witnessSize = this._witnessSize; + base = size - witnessSize; - return size; + return (base * 4 + witnessSize + 3) / 4 | 0; }; Block.prototype.getSize = function getSize() { - return this._raw.length; + return this.getRaw().length; }; Block.prototype.hasWitness = function hasWitness() { @@ -186,7 +183,7 @@ Block.prototype._verify = function _verify() { // Size can't be bigger than MAX_BLOCK_SIZE if (this.txs.length > constants.block.maxSize - || this.getBlockSize() > constants.block.maxSize) { + || this.getVirtualSize() > constants.block.maxSize) { utils.debug('Block is too large: %s', this.rhash); return false; } diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 0b9dca4d..2b6ab1ab 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -40,6 +40,7 @@ function MTX(options) { this._raw = null; this._size = 0; this._offset = 0; + this._witnessSize = 0; this.height = -1; @@ -78,16 +79,32 @@ MTX.prototype.clone = function clone() { }; MTX.prototype.hash = function hash(enc) { - var hash = utils.dsha256(this.render()); + var hash = utils.dsha256(this.renderNormal()); return enc === 'hex' ? utils.toHex(hash) : hash; }; -MTX.prototype.witnessHash = function hash(enc) { - var hash = utils.dsha256(this.renderWitness()); +MTX.prototype.witnessHash = function witnessHash(enc) { + var raw, hash; + + if (this.isCoinbase()) { + return enc === 'hex' + ? utils.toHex(constants.zeroHash) + : new Buffer(constants.zeroHash); + } + + if (!this.hasWitness()) + return this.hash(enc); + + hash = utils.dsha256(this.renderWitness()); + return enc === 'hex' ? utils.toHex(hash) : hash; }; MTX.prototype.render = function render() { + return this.getRaw(); +}; + +MTX.prototype.renderNormal = function renderNormal() { return bcoin.protocol.framer.tx(this); }; @@ -95,8 +112,24 @@ MTX.prototype.renderWitness = function renderWitness() { return bcoin.protocol.framer.witnessTX(this); }; +MTX.prototype.getRaw = function getRaw() { + if (this.hasWitness()) + return bcoin.protocol.framer.witnessTX(this); + + return bcoin.protocol.framer.tx(this); +}; + MTX.prototype.getSize = function getSize() { - return this.render().length; + return this.getRaw().length; +}; + +MTX.prototype.getVirtualSize = function getVirtualSize() { + var raw = this.getRaw(); + var size = raw.length; + var witnessSize = raw._witnessSize; + var base = size - witnessSize; + + return (base * 4 + witnessSize + 3) / 4 | 0; }; MTX.prototype.addInput = function addInput(options, index) { @@ -648,6 +681,7 @@ MTX.prototype.scriptOutput = function scriptOutput(index, options) { MTX.prototype.maxSize = function maxSize(maxM, maxN) { var copy = this.clone(); var i, j, input, total, size, prev, m, n; + var witness; // Create copy with 0-script inputs for (i = 0; i < copy.inputs.length; i++) { @@ -661,6 +695,7 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) { for (i = 0; i < copy.inputs.length; i++) { input = copy.inputs[i]; size = 0; + witness = false; assert(input.output); @@ -670,18 +705,22 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) { // If we have access to the redeem script, // we can use it to calculate size much easier. if (this.inputs[i].script.length && bcoin.script.isScripthash(prev)) { - prev = bcoin.script.getRedeem(this.inputs[i].script); // Need to add the redeem script size // here since it will be ignored by // the isMultisig clause. // OP_PUSHDATA2 [redeem] - size += 3 + bcoin.script.getSize(prev); + prev = this.inputs[i].script[this.inputs[i].script.length - 1]; + size += utils.sizeIntv(prev.length) + prev.length; + prev = bcoin.script.decode(prev); } - if (this.inputs[i].witness.length) { - if (bcoin.script.isWitnessScripthash(prev)) { - prev = bcoin.script.getRedeem(this.inputs[i].witness); - size += 3 + bcoin.script.getSize(prev); + if (bcoin.script.isWitnessProgram(prev)) { + witness = true; + size *= 4; + if (this.inputs[i].witness.length && bcoin.script.isWitnessScripthash(prev)) { + prev = this.inputs[i].witness[this.inputs[i].witness.length - 1]; + size += utils.sizeIntv(prev.length) + prev.length; + prev = bcoin.script.decode(prev); } else if (bcoin.script.isWitnessPubkeyhash(prev)) { prev = bcoin.script.createPubkeyhash(prev[1]); } @@ -744,6 +783,9 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN) { // Byte for varint size of input script size += utils.sizeIntv(size); + if (witness) + size = (size + 3) / 4 | 0; + total += size; } @@ -1027,17 +1069,19 @@ MTX.prototype.setLocktime = function setLocktime(locktime) { }; MTX.prototype.increaseFee = function increaseFee(unspent, address, fee) { - var i, input, result; + var i, input; this.inputs.length = 0; - if (this.changeIndex !== -1) + if (this.changeIndex !== -1) { this.outputs.splice(this.changeIndex, 1); + this.changeIndex = -1; + } if (!fee) fee = this.getFee().add(new bn(10000)); - result = this.fill(unspent, address, fee); + this.fill(unspent, address, fee); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 5249ebf3..6f059a92 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -507,6 +507,7 @@ Framer.witnessTX = function _witnessTX(tx) { + 2 + utils.sizeIntv(tx.inputs.length) + inputSize + utils.sizeIntv(tx.outputs.length) + outputSize + + utils.sizeIntv(witnesses.length) + witnessSize + 4); off += utils.write32(p, tx.version, off); @@ -609,23 +610,18 @@ Framer.witnessBlock = function _witnessBlock(block) { return Framer._block(block, true); }; -Framer._block = function _block(block, witness) { +Framer._block = function _block(block, useWitness) { var off = 0; var txSize = 0; var witnessSize = 0; var txs = []; - var hasWitness; var i, tx, p; for (i = 0; i < block.txs.length; i++) { - if (witness) { - hasWitness = block.txs[i].hasWitness - ? block.txs[i].hasWitness() - : block.txs[i]._witness; - } - tx = hasWitness + tx = useWitness && bcoin.tx.prototype.hasWitness.call(block.txs[i]) ? Framer.witnessTX(block.txs[i]) : Framer.tx(block.txs[i]); + txs.push(tx); txSize += tx.length; witnessSize += tx._witnessSize; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index d1f89ae2..c16ee965 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -37,6 +37,7 @@ function TX(data, block, index) { this._raw = data._raw || null; this._size = data._size || 0; this._offset = data._offset || 0; + this._witnessSize = data._witnessSize || 0; this.height = data.height != null ? data.height : -1; @@ -72,41 +73,18 @@ TX.prototype.setBlock = function setBlock(block, index) { TX.prototype.clone = function clone() { var tx = new TX(this); - - // tx.inputs = tx.inputs.map(function(input) { - // var _raw = input.script._raw; - // input.script = input.script.slice(); - // if (_raw) - // utils.hidden(input.script, '_raw', _raw); - // return input; - // }); - - // tx.outputs = tx.outputs.map(function(output) { - // var _raw = output.script._raw; - // output.script = output.script.slice(); - // if (_raw) - // utils.hidden(output.script, '_raw', _raw); - // return output; - // }); - delete tx._raw; delete tx._size; delete tx._offset; delete tx._hash; delete tx._whash; - + delete tx._witnessSize; return tx; }; -TX.prototype.txid = function txid(enc) { - if (this.hasWitness()) - return this.witnessHash(enc); - return this.hash(enc); -}; - TX.prototype.hash = function hash(enc) { if (!this._hash) - this._hash = utils.dsha256(this.render()); + this._hash = utils.dsha256(this.renderNormal()); return enc === 'hex' ? utils.toHex(this._hash) : this._hash; }; @@ -127,6 +105,61 @@ TX.prototype.witnessHash = function witnessHash(enc) { return enc === 'hex' ? utils.toHex(this._whash) : this._whash; }; +TX.prototype.render = function render() { + return this.getRaw(); +}; + +TX.prototype.renderNormal = function renderNormal() { + var raw = this.getRaw(); + if (!bcoin.protocol.parser.isWitnessTX(raw)) + return raw; + return bcoin.protocol.framer.tx(this); +}; + +TX.prototype.renderWitness = function renderWitness() { + var raw = this.getRaw(); + if (bcoin.protocol.parser.isWitnessTX(raw)) + return raw; + return bcoin.protocol.framer.witnessTX(this); +}; + +TX.prototype.getRaw = function getRaw() { + var raw; + + if (this._raw) { + assert(this._size > 0); + assert(this._witnessSize >= 0); + return this._raw; + } + + if (this.hasWitness()) + raw = bcoin.protocol.framer.witnessTX(this); + else + raw = bcoin.protocol.framer.tx(this); + + this._raw = raw; + this._size = raw.length; + this._witnessSize = raw._witnessSize; + + return this._raw; +}; + +TX.prototype.getVirtualSize = function getVirtualSize() { + var size, witnessSize, base; + + this.getRaw(); + + size = this._size; + witnessSize = this._witnessSize; + base = size - witnessSize; + + return (base * 4 + witnessSize + 3) / 4 | 0; +}; + +TX.prototype.getSize = function getSize() { + return this.getRaw().length; +}; + TX.prototype.hasWitness = function hasWitness() { var i; @@ -138,46 +171,6 @@ TX.prototype.hasWitness = function hasWitness() { return false; }; -TX.prototype.render = function render() { - if (this._raw) { - if (!bcoin.protocol.parser.isWitnessTX(this._raw)) - return this._raw; - return bcoin.protocol.framer.tx(this); - } - - if (this.hasWitness()) - return bcoin.protocol.framer.tx(this); - - this._raw = bcoin.protocol.framer.tx(this); - this._size = this._raw.length; - - return this._raw; -}; - -TX.prototype.renderWitness = function renderWitness() { - if (this._raw) { - if (bcoin.protocol.parser.isWitnessTX(this._raw)) - return this._raw; - // We probably shouldn't even render it - // as a witness tx if it doesn't have a witness. - return bcoin.protocol.framer.witnessTX(this); - } - - // We probably shouldn't even render it - // as a witness tx if it doesn't have a witness. - if (!this.hasWitness()) - return bcoin.protocol.framer.witnessTX(this); - - this._raw = bcoin.protocol.framer.witnessTX(this); - this._size = this._raw.length; - - return this._raw; -}; - -TX.prototype.getSize = function getSize() { - return this._raw.length; -}; - TX.prototype._inputIndex = function _inputIndex(hash, index) { var i, ex; @@ -212,9 +205,31 @@ TX.prototype.signatureHash = function signatureHash(index, s, type, version) { }; TX.prototype.signatureHashV0 = function signatureHashV0(index, s, type) { - var copy = this.clone(); var i, msg, hash; + var copy = { + version: this.version, + inputs: [], + outputs: [], + locktime: this.locktime + }; + + for (i = 0; i < this.inputs.length; i++) { + copy.inputs.push({ + prevout: this.inputs[i].prevout, + script: this.inputs[i].script.slice(), + witness: this.inputs[i].witness.slice(), + sequence: this.inputs[i].sequence + }); + } + + for (i = 0; i < this.outputs.length; i++) { + copy.outputs.push({ + value: this.outputs[i].value, + script: this.outputs[i].script.slice() + }); + } + if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -379,26 +394,6 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, s, type) { return hash; }; -TX.prototype.normalizedHash = function normalizedHash(enc, force) { - var copy = this.clone(); - var i; - - if (this.isCoinbase()) - return this.hash(enc); - - if (!this._nhash || force) { - for (i = 0; i < copy.inputs.length; i++) - copy.inputs[i].script = []; - - copy = bcoin.protocol.framer.tx(copy); - this._nhash = utils.dsha256(copy); - } - - return enc === 'hex' - ? utils.toHex(this._nhash) - : this._nhash.slice(); -}; - TX.prototype.verify = function verify(index, force, flags) { // Valid if included in block if (!force && this.ts !== 0)