From 018ce138152b3dd33e849fcb27d6eb6c9160a66d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 31 Mar 2016 06:33:28 -0700 Subject: [PATCH] even more size calculation. --- lib/bcoin/mtx.js | 68 +++++++++++++++++++++++++++++++++------------ lib/bcoin/utils.js | 13 +++++++++ lib/bcoin/wallet.js | 33 ++++++++++++++++++++++ 3 files changed, 97 insertions(+), 17 deletions(-) diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 7523900b..2015be97 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -696,12 +696,22 @@ MTX.prototype.isScripted = function isScripted() { }); }; -MTX.prototype.maxSize = function maxSize(maxM, maxN, force) { - var i, j, input, total, size, prev, m, n, witness; +MTX.prototype.maxSize = function maxSize(options, force) { + var i, j, input, total, size, prev, m, n; + var witness, hadWitness, redeem, wallet; if (!force && this.isScripted()) return this.getVirtualSize(); + if (!options) + options = {}; + + if (options instanceof bcoin.wallet) + options = { wallet: options, m: options.m, n: options.n }; + + if (options.wallet) + wallet = options.wallet; + // Calculate the size, minus the input scripts. total = bcoin.protocol.framer.tx.size(this); @@ -718,6 +728,8 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN, force) { size = 0; witness = false; + // We're out of luck here. + // Just assume it's a p2pkh. if (!input.coin) { total += 110; continue; @@ -728,24 +740,46 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN, force) { // If we have access to the redeem script, // we can use it to calculate size much easier. - if (input.script.code.length && prev.isScripthash()) { + if (prev.isScripthash()) { // Need to add the redeem script size // here since it will be ignored by // the isMultisig clause. // OP_PUSHDATA2 [redeem] - prev = input.script.getRedeem(); - size += 3 + prev.getSize(); + redeem = wallet + ? wallet.getRedeem(prev.code[1]) + : input.script.getRedeem(); + + if (redeem) { + prev = redeem; + size += utils.sizePush(prev.getSize()); + size += prev.getSize(); + } } if (prev.isWitnessProgram()) { witness = true; + // Now calculating vsize. The regular // redeem script (if there was one) // is now worth 4 points. size *= 4; - if (input.witness.items.length && prev.isWitnessScripthash()) { - prev = input.witness.getRedeem(); - size += 3 + prev.getSize(); + + // Add 2 bytes for flag and marker. + if (!hadWitness) + size += 2; + + hadWitness = true; + + if (prev.isWitnessScripthash()) { + redeem = wallet + ? wallet.getRedeem(prev.code[1]) + : input.witness.getRedeem(); + + if (redeem) { + prev = redeem; + size += utils.sizePush(prev.getSize()); + size += prev.getSize(); + } } else if (prev.isWitnessPubkeyhash()) { prev = Script.createPubkeyhash(prev.code[1]); } @@ -769,7 +803,7 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN, force) { size += 1; // OP_PUSHDATA0 [signature] ... size += (1 + 73) * m; - } else if (prev.isScripthash()) { + } else if (prev.isScripthash() || prev.isWitnessScripthash()) { // P2SH Multisig // This technically won't work well for other // kinds of P2SH. It will also over-estimate @@ -780,9 +814,9 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN, force) { // simply add more of the fee to the change // output. // m value - m = maxM || 15; + m = options.m || 15; // n value - n = maxN || 15; + n = options.n || 15; // OP_0 size += 1; // OP_PUSHDATA0 [signature] ... @@ -805,12 +839,15 @@ MTX.prototype.maxSize = function maxSize(maxM, maxN, force) { } } - // Byte for varint size of input script. + // Byte for varint size of input script or witness. size += utils.sizeVarint(size); // Calculate vsize if we're a witness program. - if (witness) + if (witness) { + // Add one byte back for the 0-byte input script. + size += 1 * 4; size = (size + 3) / 4 | 0; + } total += size; } @@ -871,9 +908,6 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { chosen.push(coins[i]); lastAdded++; - if (options.wallet) - options.wallet.scriptInputs(tx, index); - if (options.selection === 'all') continue; @@ -905,7 +939,7 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) { // bytes (10000 satoshi for every 1024 bytes). do { // Calculate max possible size after signing. - size = tx.maxSize(options.m, options.n, true); + size = tx.maxSize(options, true); if (tryFree) { if (tx.isFree(network.height + 1, size)) { diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 76684f36..9508ec89 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -1557,6 +1557,19 @@ utils.sizeVarint = function sizeVarint(num) { return 9; }; +utils.sizePush = function sizePush(num) { + if (num <= 0x4b) + return 1; + + if (num <= 0xff) + return 2; + + if (num <= 0xffff) + return 3; + + return 5; +}; + utils.cmp = function(a, b) { var len, i; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index ff39d551..6c2e97f8 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -59,6 +59,8 @@ function Wallet(options) { this.m = options.m || 1; this.n = options.n || 1; + this.cache = new bcoin.lru(20); + if (this.n > 1) this.type = 'multisig'; @@ -364,6 +366,9 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { }; } + if (this.cache.has(data.path)) + return this.cache.get(data.path); + key = this.accountKey.derive(data.path); options = { @@ -401,6 +406,8 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { this.emit('add address', address); + this.cache.set(data.path, address); + return address; }; @@ -699,6 +706,32 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx) { return res; }; +Wallet.prototype.getRedeem = function getRedeem(hash, prefix) { + var addr, address; + + if (!prefix) { + if (hash.length === 20) + prefix = 'scripthash'; + else if (hash.length === 32) + prefix = 'witnessscripthash'; + else + assert(false, 'Unknown hash length.'); + } + + addr = bcoin.address.compileHash(hash, prefix); + address = this.deriveAddress(addr); + + if (!address) + return; + + if (address.program && hash.length === 20) { + if (utils.isEqual(hash, address.programHash)) + return address.program; + } + + return address.script; +}; + Wallet.prototype.scan = function scan(txByAddress, callback) { var self = this; var res = false;