diff --git a/accumulative.js b/accumulative.js index 42fd28f..9903129 100644 --- a/accumulative.js +++ b/accumulative.js @@ -2,7 +2,7 @@ var utils = require('./utils') // add inputs until we reach or surpass the target value (or deplete) // worst-case: O(n) -module.exports = function accumulative (utxos, outputs, feeRate) { +module.exports = function accumulative (utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes([], outputs) @@ -31,7 +31,7 @@ module.exports = function accumulative (utxos, outputs, feeRate) { // go again? if (inAccum < outAccum + fee) continue - return utils.finalize(inputs, outputs, feeRate) + return utils.finalize(inputs, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) } return { fee: feeRate * bytesAccum } diff --git a/blackjack.js b/blackjack.js index 316568b..7e27234 100644 --- a/blackjack.js +++ b/blackjack.js @@ -2,7 +2,7 @@ var utils = require('./utils') // only add inputs if they don't bust the target value (aka, exact match) // worst-case: O(n) -module.exports = function blackjack (utxos, outputs, feeRate) { +module.exports = function blackjack (utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes([], outputs) @@ -10,7 +10,7 @@ module.exports = function blackjack (utxos, outputs, feeRate) { var inAccum = 0 var inputs = [] var outAccum = utils.sumOrNaN(outputs) - var threshold = utils.dustThreshold({}, feeRate) + var threshold = utils.dustThreshold(feeRate, changeInputLengthEstimate) for (var i = 0; i < utxos.length; ++i) { var input = utxos[i] @@ -28,7 +28,7 @@ module.exports = function blackjack (utxos, outputs, feeRate) { // go again? if (inAccum < outAccum + fee) continue - return utils.finalize(inputs, outputs, feeRate) + return utils.finalize(inputs, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) } return { fee: feeRate * bytesAccum } diff --git a/break.js b/break.js index 91dd72c..9fbf38a 100644 --- a/break.js +++ b/break.js @@ -1,7 +1,7 @@ var utils = require('./utils') // break utxos into the maximum number of 'output' possible -module.exports = function broken (utxos, output, feeRate) { +module.exports = function broken (utxos, output, feeRate, changeInputLengthEstimate, changeOutputLength) { if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes(utxos, []) @@ -29,6 +29,5 @@ module.exports = function broken (utxos, output, feeRate) { outAccum += value outputs.push(output) } - - return utils.finalize(utxos, outputs, feeRate) + return utils.finalize(utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) } diff --git a/index.js b/index.js index 19aa484..c8e9fe4 100644 --- a/index.js +++ b/index.js @@ -7,15 +7,15 @@ function utxoScore (x, feeRate) { return x.value - (feeRate * utils.inputBytes(x)) } -module.exports = function coinSelect (utxos, outputs, feeRate) { +module.exports = function coinSelect (utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { utxos = utxos.concat().sort(function (a, b) { return utxoScore(b, feeRate) - utxoScore(a, feeRate) }) // attempt to use the blackjack strategy first (no change output) - var base = blackjack(utxos, outputs, feeRate) + var base = blackjack(utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) if (base.inputs) return base // else, try the accumulative strategy - return accumulative(utxos, outputs, feeRate) + return accumulative(utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) } diff --git a/split.js b/split.js index 4d3ce5d..b1a4710 100644 --- a/split.js +++ b/split.js @@ -1,7 +1,7 @@ var utils = require('./utils') // split utxos between each output, ignores outputs with .value defined -module.exports = function split (utxos, outputs, feeRate) { +module.exports = function split (utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes(utxos, outputs) @@ -17,7 +17,7 @@ module.exports = function split (utxos, outputs, feeRate) { return a + !isFinite(x.value) }, 0) - if (remaining === 0 && unspecified === 0) return utils.finalize(utxos, outputs, feeRate) + if (remaining === 0 && unspecified === 0) return utils.finalize(utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) var splitOutputsCount = outputs.reduce(function (a, x) { return a + !x.value @@ -26,7 +26,7 @@ module.exports = function split (utxos, outputs, feeRate) { // ensure every output is either user defined, or over the threshold if (!outputs.every(function (x) { - return x.value !== undefined || (splitValue > utils.dustThreshold(x, feeRate)) + return x.value !== undefined || (splitValue > utils.dustThreshold(feeRate, changeInputLengthEstimate)) })) return { fee: fee } // assign splitValue to outputs not user defined @@ -40,5 +40,5 @@ module.exports = function split (utxos, outputs, feeRate) { return y }) - return utils.finalize(utxos, outputs, feeRate) + return utils.finalize(utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) } diff --git a/test/_utils.js b/test/_utils.js index 4c3739d..e59b270 100644 --- a/test/_utils.js +++ b/test/_utils.js @@ -1,17 +1,52 @@ -function expand (values, indices) { +function addScriptLength (values, scriptLength) { + return values.map(function (x) { + if (x.script === undefined) { + x.script = { length: scriptLength } + } + return x + }) +} + +function addScriptLengthToExpected (expected, inputLength, outputLength) { + var newExpected = Object.assign({}, expected) + + if (expected.inputs != null) { + newExpected.inputs = expected.inputs.map(function (input) { + var newInput = Object.assign({}, input) + if (newInput.script == null) { + newInput.script = {length: inputLength} + } + return newInput + }) + } + + if (expected.outputs != null) { + newExpected.outputs = expected.outputs.map(function (output) { + var newOutput = Object.assign({}, output) + if (newOutput.script == null) { + newOutput.script = {length: outputLength} + } + return newOutput + }) + } + + return newExpected +} + +function expand (values, indices, scriptLength) { if (indices) { - return values.map(function (x, i) { + return addScriptLength(values.map(function (x, i) { if (typeof x === 'number') return { i: i, value: x } var y = { i: i } for (var k in x) y[k] = x[k] return y - }) + }), scriptLength) } - return values.map(function (x, i) { + return addScriptLength(values.map(function (x, i) { return typeof x === 'object' ? x : { value: x } - }) + }), scriptLength) } function testValues (t, actual, expected) { @@ -35,5 +70,6 @@ function testValues (t, actual, expected) { module.exports = { expand: expand, - testValues: testValues + testValues: testValues, + addScriptLengthToExpected: addScriptLengthToExpected } diff --git a/test/accumulative.js b/test/accumulative.js index 6f6edd7..33a8695 100644 --- a/test/accumulative.js +++ b/test/accumulative.js @@ -5,14 +5,19 @@ var utils = require('./_utils') fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputs = utils.expand(f.inputs, true) - var outputs = utils.expand(f.outputs) - var actual = coinAccum(inputs, outputs, f.feeRate) + var inputLength = f.inputLength + var outputLength = f.outputLength - t.same(actual, f.expected) + var inputs = utils.expand(f.inputs, true, inputLength) + var outputs = utils.expand(f.outputs, false, outputLength) + var expected = utils.addScriptLengthToExpected(f.expected, inputLength, outputLength) + + var actual = coinAccum(inputs, outputs, f.feeRate, inputLength, outputLength) + + t.same(actual, expected) if (actual.inputs) { - var feedback = coinAccum(actual.inputs, actual.outputs, f.feeRate) - t.same(feedback, f.expected) + var feedback = coinAccum(actual.inputs, actual.outputs, f.feeRate, inputLength, outputLength) + t.same(feedback, expected) } t.end() diff --git a/test/break.js b/test/break.js index 9748b6c..f1109d6 100644 --- a/test/break.js +++ b/test/break.js @@ -5,11 +5,16 @@ var utils = require('./_utils') fixtures.forEach(function (f) { tape(f.description, function (t) { - var finputs = utils.expand(f.inputs) - var foutputs = utils.expand([f.output]) - var actual = coinBreak(finputs, foutputs[0], f.feeRate) + var inputLength = f.inputLength + var outputLength = f.outputLength - t.same(actual, f.expected) + var inputs = utils.expand(f.inputs, false, inputLength) + var outputs = utils.expand([f.output], false, outputLength) + var expected = utils.addScriptLengthToExpected(f.expected, inputLength, outputLength) + + var actual = coinBreak(inputs, outputs[0], f.feeRate, inputLength, outputLength) + + t.same(actual, expected) t.end() }) }) diff --git a/test/fixtures/accumulative.json b/test/fixtures/accumulative.json index 23505d5..7ebb093 100644 --- a/test/fixtures/accumulative.json +++ b/test/fixtures/accumulative.json @@ -21,7 +21,9 @@ } ], "fee": 2001 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, change expected", @@ -48,7 +50,9 @@ } ], "fee": 1130 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, change expected, value > 2^32", @@ -75,7 +79,9 @@ } ], "fee": 1130 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, sub-optimal inputs (if re-ordered), direct possible", @@ -101,7 +107,9 @@ } ], "fee": 2300 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee", @@ -127,7 +135,9 @@ } ], "fee": 3200 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected", @@ -156,7 +166,9 @@ } ], "fee": 1130 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, passes, skipped detrimental input", @@ -195,7 +207,9 @@ "value": 4000 } ] - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, fails, skips (and finishes on) detrimental input", @@ -213,7 +227,9 @@ ], "expected": { "fee": 18700 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, passes, poor ordering causing high fee", @@ -259,7 +275,9 @@ } ], "fee": 5000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, passes, improved ordering causing low fee, no waste", @@ -298,7 +316,9 @@ } ], "fee": 2000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, optimal inputs, no change", @@ -322,7 +342,9 @@ } ], "fee": 2300 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, no fee, change expected", @@ -374,7 +396,9 @@ } ], "fee": 0 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, script provided, no change", @@ -406,7 +430,9 @@ } ], "fee": 5000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, script provided, change expected", @@ -441,7 +467,9 @@ } ], "fee": 4010 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, 2 inputs (related), no change", @@ -473,7 +501,9 @@ } ], "fee": 2000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "many outputs, no change", @@ -519,7 +549,9 @@ } ], "fee": 6221 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "many outputs, change expected", @@ -568,7 +600,9 @@ } ], "fee": 6240 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "many outputs, no fee, change expected", @@ -624,7 +658,9 @@ } ], "fee": 0 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "no outputs, no change", @@ -642,7 +678,9 @@ ], "outputs": [], "fee": 1900 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "no outputs, change expected", @@ -664,7 +702,9 @@ } ], "fee": 1920 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs used in order of DESCENDING", @@ -709,7 +749,9 @@ "value": 7850 } ] - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "not enough funds, empty result", @@ -722,7 +764,9 @@ ], "expected": { "fee": 1920 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "not enough funds (w/ fee), empty result", @@ -735,7 +779,9 @@ ], "expected": { "fee": 1920 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "not enough funds (no inputs), empty result", @@ -744,7 +790,9 @@ "outputs": [], "expected": { "fee": 100 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "not enough funds (no inputs), empty result (>1KiB)", @@ -783,7 +831,9 @@ ], "expected": { "fee": 9960 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 outputs, some with missing value (NaN)", @@ -797,7 +847,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "input with float values (NaN)", @@ -811,7 +863,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 outputs, with float values (NaN)", @@ -825,7 +879,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 outputs, string values (NaN)", @@ -843,7 +899,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -854,7 +912,9 @@ "outputs": [ 10000 ], - "expected": {} + "expected": {}, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -865,6 +925,8 @@ "outputs": [ 10000 ], - "expected": {} + "expected": {}, + "inputLength": 107, + "outputLength": 25 } ] diff --git a/test/fixtures/break.json b/test/fixtures/break.json index 71483a3..8d7b1c1 100644 --- a/test/fixtures/break.json +++ b/test/fixtures/break.json @@ -18,7 +18,9 @@ } ], "fee": 1920 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1:1", @@ -43,7 +45,9 @@ "value": 10000 } ] - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1:1, w/ change", @@ -67,7 +71,9 @@ } ], "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1:2, strange output type", @@ -102,7 +108,9 @@ } ], "fee": 7000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1:4", @@ -132,7 +140,9 @@ } ], "fee": 4000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2:5", @@ -169,7 +179,9 @@ } ], "fee": 5000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2:5, no fee", @@ -206,7 +218,9 @@ } ], "fee": 0 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2:2 (+1), w/ change", @@ -233,7 +247,9 @@ } ], "fee": 1820 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2:3 (+1), no fee, w/ change", @@ -267,7 +283,9 @@ } ], "fee": 0 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "not enough funds", @@ -279,7 +297,9 @@ "output": 40000, "expected": { "fee": 3400 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "no inputs", @@ -288,7 +308,9 @@ "output": 2000, "expected": { "fee": 440 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "invalid output (NaN)", @@ -297,7 +319,9 @@ "output": {}, "expected": { "fee": 100 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "input with float values (NaN)", @@ -308,7 +332,9 @@ "output": 5000, "expected": { "fee": 1580 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -317,7 +343,9 @@ 20000 ], "output": 10000, - "expected": {} + "expected": {}, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -326,6 +354,8 @@ 20000 ], "output": 10000, - "expected": {} + "expected": {}, + "inputLength": 107, + "outputLength": 25 } ] diff --git a/test/fixtures/index.json b/test/fixtures/index.json index 9ebcdbf..604fac8 100644 --- a/test/fixtures/index.json +++ b/test/fixtures/index.json @@ -21,7 +21,9 @@ } ], "fee": 2001 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, change expected", @@ -48,7 +50,9 @@ } ], "fee": 1130 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, sub-optimal inputs (if re-ordered), direct possible", @@ -74,7 +78,9 @@ } ], "fee": 2300 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee", @@ -100,7 +106,9 @@ } ], "fee": 3200 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected", @@ -129,7 +137,9 @@ } ], "fee": 1130 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, passes, skipped detrimental input", @@ -168,7 +178,9 @@ "value": 4000 } ] - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, passes, poor ordering but still good", @@ -207,7 +219,9 @@ } ], "fee": 2000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, passes, improved ordering causing low fee, no waste", @@ -246,7 +260,9 @@ } ], "fee": 2000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, optimal inputs, no change", @@ -270,7 +286,9 @@ } ], "fee": 2300 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, no fee, change expected", @@ -322,7 +340,9 @@ } ], "fee": 0 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, script provided, no change", @@ -354,7 +374,9 @@ } ], "fee": 5000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, script provided, change expected", @@ -389,7 +411,9 @@ } ], "fee": 4010 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 output, 2 inputs (related), no change", @@ -421,7 +445,9 @@ } ], "fee": 2000 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "many outputs, no change", @@ -467,7 +493,9 @@ } ], "fee": 6221 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "many outputs, change expected", @@ -516,7 +544,9 @@ } ], "fee": 6240 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "many outputs, no fee, change expected", @@ -572,7 +602,9 @@ } ], "fee": 0 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "no outputs, no change", @@ -590,7 +622,9 @@ ], "outputs": [], "fee": 1900 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "no outputs, change expected", @@ -612,7 +646,9 @@ } ], "fee": 1920 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs used in order of DESCENDING", @@ -647,7 +683,9 @@ "value": 25000 } ] - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "not enough funds, empty result", @@ -660,7 +698,9 @@ ], "expected": { "fee": 1920 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "not enough funds (w/ fee), empty result", @@ -673,7 +713,9 @@ ], "expected": { "fee": 1920 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "not enough funds (no inputs), empty result", @@ -682,7 +724,9 @@ "outputs": [], "expected": { "fee": 100 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "not enough funds (no inputs), empty result (>1KiB)", @@ -721,7 +765,9 @@ ], "expected": { "fee": 9960 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 outputs, some with missing value (NaN)", @@ -735,7 +781,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "input with float values (NaN)", @@ -749,7 +797,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 outputs, with float values (NaN)", @@ -763,7 +813,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 outputs, string values (NaN)", @@ -781,7 +833,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -792,7 +846,9 @@ "outputs": [ 10000 ], - "expected": {} + "expected": {}, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -803,7 +859,9 @@ "outputs": [ 10000 ], - "expected": {} + "expected": {}, + "inputLength": 107, + "outputLength": 25 } ] diff --git a/test/fixtures/split.json b/test/fixtures/split.json index 30c1dd2..f971327 100644 --- a/test/fixtures/split.json +++ b/test/fixtures/split.json @@ -28,7 +28,9 @@ } ], "fee": 2601 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "5 to 2", @@ -71,7 +73,9 @@ } ], "fee": 8180 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "3 to 1", @@ -102,7 +106,9 @@ } ], "fee": 4880 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "3 to 3 (1 output pre-defined)", @@ -148,7 +154,9 @@ } ], "fee": 5560 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 to 0 (no result)", @@ -160,7 +168,9 @@ "outputs": [], "expected": { "fee": 3060 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "0 to 2 (no result)", @@ -172,7 +182,9 @@ ], "expected": { "fee": 780 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "1 to 2, output is dust (no result)", @@ -185,7 +197,9 @@ ], "expected": { "fee": 1920 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 outputs, some with missing value (NaN)", @@ -214,7 +228,9 @@ } ], "fee": 2486 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 outputs, some with float values (NaN)", @@ -230,7 +246,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "2 outputs, string values (NaN)", @@ -248,7 +266,9 @@ ], "expected": { "fee": 2486 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "input with float values (NaN)", @@ -262,7 +282,9 @@ ], "expected": { "fee": 2260 - } + }, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -273,7 +295,9 @@ "outputs": [ {} ], - "expected": {} + "expected": {}, + "inputLength": 107, + "outputLength": 25 }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -284,6 +308,8 @@ "outputs": [ {} ], - "expected": {} + "expected": {}, + "inputLength": 107, + "outputLength": 25 } ] diff --git a/test/index.js b/test/index.js index 3554363..28d482a 100644 --- a/test/index.js +++ b/test/index.js @@ -5,14 +5,19 @@ var utils = require('./_utils') fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputs = utils.expand(f.inputs, true) - var outputs = utils.expand(f.outputs) - var actual = coinSelect(inputs, outputs, f.feeRate) + var inputLength = f.inputLength + var outputLength = f.outputLength - t.same(actual, f.expected) + var inputs = utils.expand(f.inputs, true, inputLength) + var outputs = utils.expand(f.outputs, false, outputLength) + var expected = utils.addScriptLengthToExpected(f.expected, inputLength, outputLength) + + var actual = coinSelect(inputs, outputs, f.feeRate, inputLength, outputLength) + + t.same(actual, expected) if (actual.inputs) { - var feedback = coinSelect(actual.inputs, actual.outputs, f.feeRate) - t.same(feedback, f.expected) + var feedback = coinSelect(actual.inputs, actual.outputs, f.feeRate, inputLength, outputLength) + t.same(feedback, expected) } t.end() diff --git a/test/split.js b/test/split.js index 0cd8c4b..d1db686 100644 --- a/test/split.js +++ b/test/split.js @@ -5,14 +5,19 @@ var utils = require('./_utils') fixtures.forEach(function (f) { tape(f.description, function (t) { - var finputs = utils.expand(f.inputs) - var foutputs = f.outputs.concat() - var actual = coinSplit(finputs, foutputs, f.feeRate) + var inputLength = f.inputLength + var outputLength = f.outputLength - t.same(actual, f.expected) + var inputs = utils.expand(f.inputs, false, inputLength) + var outputs = utils.expand(f.outputs.concat(), false, outputLength) + var expected = utils.addScriptLengthToExpected(f.expected, inputLength, outputLength) + + var actual = coinSplit(inputs, outputs, f.feeRate, inputLength, outputLength) + + t.same(actual, expected) if (actual.inputs) { - var feedback = coinSplit(finputs, actual.outputs, f.feeRate) - t.same(feedback, f.expected) + var feedback = coinSplit(inputs, actual.outputs, f.feeRate, inputLength, outputLength) + t.same(feedback, expected) } t.end() diff --git a/utils.js b/utils.js index 687fe66..786a648 100644 --- a/utils.js +++ b/utils.js @@ -1,21 +1,18 @@ // baseline estimates, used to improve performance var TX_EMPTY_SIZE = 4 + 1 + 1 + 4 var TX_INPUT_BASE = 32 + 4 + 1 + 4 -var TX_INPUT_PUBKEYHASH = 107 var TX_OUTPUT_BASE = 8 + 1 -var TX_OUTPUT_PUBKEYHASH = 25 function inputBytes (input) { - return TX_INPUT_BASE + (input.script ? input.script.length : TX_INPUT_PUBKEYHASH) + return TX_INPUT_BASE + input.script.length } function outputBytes (output) { - return TX_OUTPUT_BASE + (output.script ? output.script.length : TX_OUTPUT_PUBKEYHASH) + return TX_OUTPUT_BASE + output.script.length } -function dustThreshold (output, feeRate) { - /* ... classify the output for input estimate */ - return inputBytes({}) * feeRate +function dustThreshold (feeRate, inputLenghtEstimate) { + return inputBytes({script: {length: inputLenghtEstimate}}) * feeRate } function transactionBytes (inputs, outputs) { @@ -40,16 +37,15 @@ function sumOrNaN (range) { return range.reduce(function (a, x) { return a + uintOrNaN(x.value) }, 0) } -var BLANK_OUTPUT = outputBytes({}) - -function finalize (inputs, outputs, feeRate) { +function finalize (inputs, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { var bytesAccum = transactionBytes(inputs, outputs) - var feeAfterExtraOutput = feeRate * (bytesAccum + BLANK_OUTPUT) + var blankOutputBytes = outputBytes({script: {length: changeOutputLength}}) + var feeAfterExtraOutput = feeRate * (bytesAccum + blankOutputBytes) var remainderAfterExtraOutput = sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput) // is it worth a change output? - if (remainderAfterExtraOutput > dustThreshold({}, feeRate)) { - outputs = outputs.concat({ value: remainderAfterExtraOutput }) + if (remainderAfterExtraOutput > dustThreshold(feeRate, changeInputLengthEstimate)) { + outputs = outputs.concat({ value: remainderAfterExtraOutput, script: {length: changeOutputLength} }) } var fee = sumOrNaN(inputs) - sumOrNaN(outputs)