From 6005f0f7504c2f9b3d87668a5846503b58a58f28 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 18 May 2016 04:23:05 -0700 Subject: [PATCH] whole bunch of refactoring. --- lib/bcoin/abstractblock.js | 38 +++++---- lib/bcoin/block.js | 104 ++++++++++++------------ lib/bcoin/hd.js | 3 +- lib/bcoin/headers.js | 12 +-- lib/bcoin/merkleblock.js | 25 +----- lib/bcoin/miner.js | 3 +- lib/bcoin/mtx.js | 162 +++---------------------------------- lib/bcoin/tx.js | 77 ++++++++++++------ lib/bcoin/wallet.js | 12 ++- 9 files changed, 157 insertions(+), 279 deletions(-) diff --git a/lib/bcoin/abstractblock.js b/lib/bcoin/abstractblock.js index 1edd761f..590e2999 100644 --- a/lib/bcoin/abstractblock.js +++ b/lib/bcoin/abstractblock.js @@ -52,10 +52,12 @@ function AbstractBlock(data) { this.height = data.height != null ? data.height : -1; this.txs = null; - this.valid = null; + this.mutable = !!data.mutable; + this._valid = null; this._hash = null; - this._size = data._size || 0; + this._size = null; + this._witnessSize = null; } /** @@ -65,10 +67,15 @@ function AbstractBlock(data) { */ AbstractBlock.prototype.hash = function hash(enc) { - if (!this._hash) - this._hash = utils.dsha256(this.abbr()); + var hash = this._hash; - return enc === 'hex' ? this._hash.toString('hex') : this._hash; + if (!hash) { + hash = utils.dsha256(this.abbr()); + if (!this.mutable) + this._hash = hash; + } + + return enc === 'hex' ? hash.toString('hex') : hash; }; /** @@ -80,15 +87,6 @@ AbstractBlock.prototype.abbr = function abbr() { return bcoin.protocol.framer.blockHeaders(this); }; -/** - * Get the full block size (this may be cached). - * @returns {Number} - */ - -AbstractBlock.prototype.getSize = function getSize() { - return this._size || this.render().length; -}; - /** * Verify the block. * @param {Object?} ret - Return object, may be @@ -97,9 +95,15 @@ AbstractBlock.prototype.getSize = function getSize() { */ AbstractBlock.prototype.verify = function verify(ret) { - if (this.valid == null) - this.valid = this._verify(ret); - return this.valid; + var valid = this._valid; + + if (valid == null) { + valid = this._verify(ret); + if (!this.mutable) + this._valid = valid; + } + + return valid; }; /** diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 894f1194..ebdc2ddb 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -39,7 +39,7 @@ var BufferReader = require('./reader'); */ function Block(data) { - var self = this; + var i, tx; if (!(this instanceof Block)) return new Block(data); @@ -47,19 +47,18 @@ function Block(data) { bcoin.abstractblock.call(this, data); this.type = 'block'; - - this._witnessSize = data._witnessSize || 0; - this.txs = data.txs || []; - this._cbHeight = null; + this._commitmentHash = null; - this.txs = this.txs.map(function(data, i) { - if (data instanceof bcoin.tx) - return data; + for (i = 0; i < this.txs.length; i++) { + tx = this.txs[i]; - return bcoin.tx(data, self, i); - }); + if (tx instanceof bcoin.tx) + continue; + + this.txs[i] = new bcoin.tx(tx, this, i); + } } utils.inherits(Block, bcoin.abstractblock); @@ -105,79 +104,77 @@ Block.prototype.getRaw = function getRaw() { else raw = bcoin.protocol.framer.block(this); - this._size = raw.length; - this._witnessSize = raw._witnessSize; + if (!this.mutable) { + this._size = raw.length; + this._witnessSize = raw._witnessSize; + } return raw; }; -Block.prototype._getSize = function _getSize() { - var sizes = bcoin.protocol.framer.block.sizes(this); - this._size = sizes.size; - this._witnessSize = sizes.witnessSize; +/** + * Calculate real size and size of the witness bytes. + * @returns {Object} Contains `size` and `witnessSize`. + */ + +Block.prototype.getSizes = function getSizes() { + var sizes; + + if (this._size != null) { + return { + size: this._size, + witnessSize: this._witnessSize + }; + } + + sizes = bcoin.protocol.framer.block.sizes(this); + + if (!this.mutable) { + this._size = sizes.size; + this._witnessSize = sizes.witnessSize; + } + + return sizes; }; /** * Calculate virtual block size. - * @param {Boolean?} force - If true, always recalculate. * @returns {Number} Virtual size. */ -Block.prototype.getVirtualSize = function getVirtualSize(force) { +Block.prototype.getVirtualSize = function getVirtualSize() { var scale = constants.WITNESS_SCALE_FACTOR; return (this.getCost() + scale - 1) / scale | 0; }; /** * Calculate block cost. - * @param {Boolean?} force - If true, always recalculate. * @returns {Number} cost */ -Block.prototype.getCost = function getCost(force) { - var size, witnessSize, base; - - size = this.getSize(force); - witnessSize = this.getWitnessSize(force); - base = size - witnessSize; - - return base * (constants.WITNESS_SCALE_FACTOR - 1) + size; +Block.prototype.getCost = function getCost() { + var sizes = this.getSizes(); + var base = sizes.size - sizes.witnessSize; + return base * (constants.WITNESS_SCALE_FACTOR - 1) + sizes.size; }; /** * Get real block size. - * @param {Boolean?} force - If true, always recalculate. * @returns {Number} size */ -Block.prototype.getSize = function getSize(force) { - if (force || this._size === 0) - this._getSize(); - return this._size; +Block.prototype.getSize = function getSize() { + return this.getSizes().size; }; /** * Get base block size (without witness). - * @param {Boolean?} force - If true, always recalculate. * @returns {Number} size */ -Block.prototype.getBaseSize = function getBaseSize(force) { - if (force || this._size === 0) - this._getSize(); - return this._size - this._witnessSize; -}; - -/** - * Get the total size of the witnesses. - * @param {Boolean?} force - If true, always recalculate. - * @returns {Number} witness size - */ - -Block.prototype.getWitnessSize = function getWitnessSize(force) { - if (force || this._size === 0) - this._getSize(); - return this._witnessSize; +Block.prototype.getBaseSize = function getBaseSize() { + var sizes = this.getSizes(); + return sizes.size - sizes.witnessSize; }; /** @@ -314,8 +311,10 @@ Block.prototype.__defineGetter__('commitmentHash', function() { } } - if (commitmentHash) - this._commitmentHash = commitmentHash.toString('hex'); + if (!this.mutable) { + if (commitmentHash) + this._commitmentHash = commitmentHash.toString('hex'); + } return this._commitmentHash; }); @@ -420,7 +419,8 @@ Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() { height = coinbase.inputs[0].script.getCoinbaseHeight(); - this._cbHeight = height; + if (!this.mutable) + this._cbHeight = height; return height; }; diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 13be978a..5ebf0a45 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -510,6 +510,7 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) { return child; hardened = index >= constants.hd.HARDENED ? true : hardened; + if (index < constants.hd.HARDENED && hardened) index += constants.hd.HARDENED; @@ -565,7 +566,7 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) { HDPrivateKey.prototype.deriveAccount44 = function deriveAccount44(accountIndex) { var coinType; - assert(utils.isNumber(accountIndex), 'accountIndex must be a number.'); + assert(utils.isNumber(accountIndex), 'Account index must be a number.'); if (this instanceof HDPublicKey) { assert(this.isAccount44(accountIndex), 'Cannot derive account index.'); diff --git a/lib/bcoin/headers.js b/lib/bcoin/headers.js index b60548ae..679394fa 100644 --- a/lib/bcoin/headers.js +++ b/lib/bcoin/headers.js @@ -64,9 +64,7 @@ Headers.prototype._verify = function _verify(ret) { */ Headers.prototype.getSize = function getSize() { - if (this._size == null) - this.getRaw(); - return this._size; + return 80; }; /** @@ -75,11 +73,7 @@ Headers.prototype.getSize = function getSize() { */ Headers.prototype.getRaw = function getRaw() { - if (!this._raw) { - this._raw = bcoin.protocol.framer.headers([this]); - this._size = this._raw.length; - } - return this._raw; + return this.abbr(); }; /** @@ -127,7 +121,7 @@ Headers.parseRaw = function parseRaw(data, enc) { if (enc === 'hex') data = new Buffer(data, 'hex'); - return bcoin.protocol.parser.parseHeaders(data)[0]; + return bcoin.protocol.parser.parseBlockHeaders(data); }; /** diff --git a/lib/bcoin/merkleblock.js b/lib/bcoin/merkleblock.js index c7db120b..5da01172 100644 --- a/lib/bcoin/merkleblock.js +++ b/lib/bcoin/merkleblock.js @@ -68,24 +68,6 @@ MerkleBlock.prototype.render = function render() { return this.getRaw(); }; -/** - * Serialize the merkleblock. - * @returns {Buffer} - */ - -MerkleBlock.prototype.renderNormal = function renderNormal() { - return this.getRaw(); -}; - -/** - * Serialize the merkleblock. - * @returns {Buffer} - */ - -MerkleBlock.prototype.renderWitness = function renderWitness() { - return this.getRaw(); -}; - /** * Get merkleblock size. * @returns {Number} Size. @@ -152,7 +134,7 @@ MerkleBlock.prototype.verifyPartial = function verifyPartial() { height++; function visit(depth) { - var flag, left, right; + var hash, flag, left, right; if (i === flags.length * 8 || j === hashes.length) return null; @@ -162,8 +144,9 @@ MerkleBlock.prototype.verifyPartial = function verifyPartial() { if (flag === 0 || depth === height) { if (depth === height) { - tx.push(hashes[j].toString('hex')); - txMap[tx[tx.length - 1]] = true; + hash = hashes[j].toString('hex'); + tx.push(hash); + txMap[hash] = true; } return hashes[j++]; } diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index 3a78c942..ec40b199 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -397,6 +397,7 @@ function MinerBlock(options) { // Create our block this.block = new bcoin.block({ + mutable: true, version: options.version, prevBlock: this.tip.hash, merkleRoot: constants.NULL_HASH, @@ -476,7 +477,7 @@ MinerBlock.prototype.addTX = function addTX(tx) { if (tx.mutable) tx = tx.toTX(); - cost = this.block.getCost(true) + tx.getCost(); + cost = this.block.getCost() + tx.getCost(); if (cost > constants.block.MAX_COST) return false; diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 5161dbb1..afa2efe3 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -78,8 +78,13 @@ function MTX(options) { this._hash = null; this._whash = null; this._raw = null; - this._size = 0; - this._witnessSize = 0; + this._size = null; + this._witnessSize = null; + this._outputValue = null; + this._inputValue = null; + this._hashPrevouts = null; + this._hashSequence = null; + this._hashOutputs = null; if (options.inputs) { for (i = 0; i < options.inputs.length; i++) @@ -110,128 +115,6 @@ MTX.prototype.clone = function clone() { return tx; }; -/** - * Hash the transaction with the non-witness serialization. - * Note that this is not cached. - * @param {String?} enc - Can be `'hex'` or `null`. - * @returns {Hash|Buffer} hash - */ - -MTX.prototype.hash = function hash(enc) { - var hash = utils.dsha256(this.renderNormal()); - return enc === 'hex' ? hash.toString('hex') : hash; -}; - -/** - * Hash the transaction with the witness - * serialization, return the wtxid (normal - * hash if no witness is present, all zeroes - * if coinbase). Note that this is not cached. - * @param {String?} enc - Can be `'hex'` or `null`. - * @returns {Hash|Buffer} hash - */ - -MTX.prototype.witnessHash = function witnessHash(enc) { - var hash; - - if (this.isCoinbase()) { - return enc === 'hex' - ? constants.NULL_HASH - : utils.slice(constants.ZERO_HASH); - } - - if (!this.hasWitness()) - return this.hash(enc); - - hash = utils.dsha256(this.renderWitness()); - - return enc === 'hex' ? hash.toString('hex') : hash; -}; - -/** - * Serialize the transaction. Note - * that this is _not_ cached. This will use - * the witness serialization if a - * witness is present. - * @returns {Buffer} Serialized transaction. - */ - -MTX.prototype.render = function render() { - return this.getRaw(); -}; - -/** - * Serialize the transaction without the - * witness vector, regardless of whether it - * is a witness transaction or not. Note - * that this is _not_ cached. - * @returns {Buffer} Serialized transaction. - */ - -MTX.prototype.renderNormal = function renderNormal() { - return bcoin.protocol.framer.tx(this); -}; - -/** - * Serialize the transaction with the - * witness vector, regardless of whether it - * is a witness transaction or not. Note that - * this is _not_ cached. - * @returns {Buffer} Serialized transaction. - */ - -MTX.prototype.renderWitness = function renderWitness() { - return bcoin.protocol.framer.witnessTX(this); -}; - -/** - * Serialize the transaction. Note - * that this is cached. This will use - * the witness serialization if a - * witness is present. Note that this - * is _not_ cached. - * @returns {Buffer} Serialized transaction. - */ - -MTX.prototype.getRaw = function getRaw() { - if (this.hasWitness()) - return bcoin.protocol.framer.witnessTX(this); - - return bcoin.protocol.framer.tx(this); -}; - -/** - * Calculate the real size of the transaction - * with the witness included. Note that this - * is _not_ cached. - * @returns {Number} size - */ - -MTX.prototype.getSize = function getSize() { - return bcoin.protocol.framer.tx.witnessSize(this); -}; - -/** - * Calculate the size of the transaction - * with the witness excluded. Note that this - * is _not_ cached. - * @returns {Number} size - */ - -MTX.prototype.getBaseSize = function getBaseSize() { - return bcoin.protocol.framer.tx.size(this); -}; - -/** - * Calculate the virtual size of the transaction. - * Note that this is _not_ cached. - * @returns {Number} vsize - */ - -MTX.prototype.getVirtualSize = function getVirtualSize() { - return bcoin.protocol.framer.tx.virtualSize(this); -}; - /** * Add an input to the transaction. * @example @@ -246,8 +129,6 @@ MTX.prototype.getVirtualSize = function getVirtualSize() { MTX.prototype.addInput = function addInput(options, index) { var input; - assert(this.ts === 0, 'Cannot modify a confirmed tx.'); - if (options instanceof bcoin.tx) options = bcoin.coin(options, index); @@ -282,8 +163,6 @@ MTX.prototype.addInput = function addInput(options, index) { MTX.prototype.scriptInput = function scriptInput(index, addr) { var input, prev, n, i, redeemScript, witnessScript, vector, dummy; - assert(this.ts === 0, 'Cannot modify a confirmed tx.'); - if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -487,8 +366,6 @@ MTX.prototype.signInput = function signInput(index, addr, type) { var input, prev, signature, index, signatures, i; var len, m, n, keys, vector, dummy, version; - assert(this.ts === 0, 'Cannot modify a confirmed tx.'); - if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -790,8 +667,6 @@ MTX.prototype.sign = function sign(index, addr, type) { MTX.prototype.addOutput = function addOutput(address, value) { var options, output; - assert(this.ts === 0, 'Cannot modify a confirmed tx.'); - if ((address instanceof bcoin.wallet) || (address instanceof bcoin.keyring)) address = address.getAddress(); @@ -822,8 +697,6 @@ MTX.prototype.addOutput = function addOutput(address, value) { MTX.prototype.scriptOutput = function scriptOutput(index, options) { var output; - assert(this.ts === 0, 'Cannot modify a confirmed tx.'); - if (options instanceof bcoin.output) return; @@ -897,7 +770,7 @@ MTX.prototype.maxSize = function maxSize(options, force) { wallet = options.wallet; // Calculate the size, minus the input scripts. - total = bcoin.protocol.framer.tx.size(this); + total = this.getBaseSize(); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; @@ -1157,7 +1030,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { // Calculate max possible size after signing. size = tx.maxSize(options, true); - if (tryFree && options.height > 0) { + if (tryFree && options.height >= 0) { // Note that this will only work // if the mempool's rolling reject // fee is zero (i.e. the mempool is @@ -1204,7 +1077,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { min = fee + output.getDustThreshold(); - if (output.value < 0) + if (output.value < min) throw new Error('Could not subtract fee.'); output.value -= fee; @@ -1241,7 +1114,6 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { MTX.prototype.fill = function fill(coins, options) { var result, i, change; - assert(this.ts === 0, 'Cannot modify a confirmed tx.'); assert(this.inputs.length === 0, 'TX is already filled.'); assert(options, '`options` are required.'); assert(options.changeAddress, '`changeAddress` is required.'); @@ -1282,8 +1154,6 @@ MTX.prototype.fill = function fill(coins, options) { MTX.prototype.sortMembers = function sortMembers() { var changeOutput; - assert(this.ts === 0, 'Cannot modify a confirmed tx.'); - if (this.changeIndex !== -1) { changeOutput = this.outputs[this.changeIndex]; assert(changeOutput); @@ -1338,8 +1208,6 @@ MTX.prototype.avoidFeeSniping = function avoidFeeSniping(height) { MTX.prototype.setLocktime = function setLocktime(locktime) { var i, input; - assert(this.ts === 0, 'Cannot modify a confirmed tx.'); - for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; if (input.sequence === 0xffffffff) @@ -1391,16 +1259,6 @@ MTX.fromExtended = function fromExtended(data, enc) { return new MTX(MTX.parseExtended(data, enc)); }; -/** - * Convert a TX to an MTX. - * @param {TX} tx - * @returns {MTX} - */ - -MTX.fromTX = function fromTX(tx) { - return new MTX(tx); -}; - /** * Convert the MTX to a TX. * @returns {TX} diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 55160cf6..d4089fcc 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -74,14 +74,18 @@ function TX(data, block, index) { this.index = data.index != null ? data.index : -1; this.ps = this.ts === 0 ? (data.ps != null ? data.ps : utils.now()) : 0; this.height = data.height != null ? data.height : -1; + this.mutable = false; this._hash = null; this._whash = null; - this._raw = data._raw || null; - this._size = data._size || 0; - this._witnessSize = data._witnessSize || 0; + this._raw = null; + this._size = null; + this._witnessSize = null; this._outputValue = null; this._inputValue = null; + this._hashPrevouts = null; + this._hashSequence = null; + this._hashOutputs = null; for (i = 0; i < data.inputs.length; i++) this.inputs.push(new bcoin.input(data.inputs[i])); @@ -182,11 +186,16 @@ TX.prototype.unsetBlock = function unsetBlock() { * @returns {Hash|Buffer} hash */ -TX.prototype.hash = function hash(enc) { - if (!this._hash) - this._hash = utils.dsha256(this.renderNormal()); +TX.prototype.hash = function _hash(enc) { + var hash = this._hash; - return enc === 'hex' ? this._hash.toString('hex') : this._hash; + if (!hash) { + hash = utils.dsha256(this.renderNormal()); + if (!this.mutable) + this._hash = hash; + } + + return enc === 'hex' ? hash.toString('hex') : hash; }; /** @@ -199,6 +208,8 @@ TX.prototype.hash = function hash(enc) { */ TX.prototype.witnessHash = function witnessHash(enc) { + var hash = this._whash; + if (this.isCoinbase()) { return enc === 'hex' ? constants.NULL_HASH @@ -208,10 +219,13 @@ TX.prototype.witnessHash = function witnessHash(enc) { if (!this.hasWitness()) return this.hash(enc); - if (!this._whash) - this._whash = utils.dsha256(this.renderWitness()); + if (!hash) { + hash = utils.dsha256(this.renderWitness()); + if (!this.mutable) + this._whash = hash; + } - return enc === 'hex' ? this._whash.toString('hex') : this._whash; + return enc === 'hex' ? hash.toString('hex') : hash; }; /** @@ -276,13 +290,32 @@ TX.prototype.getRaw = function getRaw() { else raw = bcoin.protocol.framer.tx(this); - this._raw = raw; - this._size = raw.length; - this._witnessSize = raw._witnessSize; + if (!this.mutable) { + this._raw = raw; + this._size = raw.length; + this._witnessSize = raw._witnessSize; + } return raw; }; +/** + * Calculate real size and size of the witness bytes. + * @returns {Object} Contains `size` and `witnessSize`. + */ + +TX.prototype.getSizes = function getSizes() { + if (this.mutable) + return bcoin.protocol.framer.tx.sizes(this); + + this.getRaw(); + + return { + size: this._size, + witnessSize: this._witnessSize + }; +}; + /** * Calculate the virtual size of the transaction. * Note that this is cached. @@ -301,15 +334,9 @@ TX.prototype.getVirtualSize = function getVirtualSize() { */ TX.prototype.getCost = function getCost() { - var size, witnessSize, base; - - this.getRaw(); - - size = this._size; - witnessSize = this._witnessSize; - base = size - witnessSize; - - return base * (constants.WITNESS_SCALE_FACTOR - 1) + size; + var sizes = this.getSizes(); + var base = sizes.size - sizes.witnessSize; + return base * (constants.WITNESS_SCALE_FACTOR - 1) + sizes.size; }; /** @@ -319,7 +346,7 @@ TX.prototype.getCost = function getCost() { */ TX.prototype.getSize = function getSize() { - return this.getRaw().length; + return this.getSizes().size; }; /** @@ -330,8 +357,8 @@ TX.prototype.getSize = function getSize() { */ TX.prototype.getBaseSize = function getBaseSize() { - this.getRaw(); - return this._size - this._witnessSize; + var sizes = this.getSizes(); + return sizes.size - sizes.witnessSize; }; /** diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 35a07517..cc43fe84 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -779,6 +779,7 @@ Wallet.prototype.getTX = function getTX(hash, callback) { Wallet.prototype.createTX = function createTX(options, outputs, callback) { var self = this; + var height = 0xffffffff; var tx, i; if (typeof outputs === 'function') { @@ -794,6 +795,9 @@ Wallet.prototype.createTX = function createTX(options, outputs, callback) { if (!Array.isArray(outputs)) outputs = [outputs]; + if (options.height >= 0) + height = options.height; + // Create mutable tx tx = bcoin.mtx(); @@ -819,7 +823,13 @@ Wallet.prototype.createTX = function createTX(options, outputs, callback) { // if (options.locktime != null) // tx.setLocktime(options.locktime); // else - // tx.avoidFeeSniping(); + // tx.avoidFeeSniping(options.height); + + if (!tx.isSane()) + return callback(new Error('CheckTransaction failed.')); + + if (!tx.checkInputs(height)) + return callback(new Error('CheckInputs failed.')); // Sign the transaction if (!self.sign(tx))