From 119c259041300fa9513d6eaec1995c5f372dc3cb Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Sun, 13 Aug 2017 20:46:42 +0200 Subject: [PATCH 1/7] Removing default lengths from main code --- accumulative.js | 4 ++-- blackjack.js | 6 +++--- break.js | 5 ++--- index.js | 6 +++--- split.js | 8 ++++---- utils.js | 22 +++++++++------------- 6 files changed, 23 insertions(+), 28 deletions(-) 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 3795aa1..54897c3 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/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) From 268c20d79a22eb38ffe42526bc0d36feb8547e3e Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Sun, 13 Aug 2017 20:47:14 +0200 Subject: [PATCH 2/7] Adding lengths to tests --- test/_utils.js | 48 +++++++++++-- test/accumulative.js | 17 +++-- test/break.js | 13 ++-- test/fixtures/accumulative.json | 124 ++++++++++++++++++++++++-------- test/fixtures/break.json | 60 ++++++++++++---- test/fixtures/index.json | 116 ++++++++++++++++++++++-------- test/fixtures/split.json | 52 ++++++++++---- test/index.js | 17 +++-- test/split.js | 17 +++-- 9 files changed, 348 insertions(+), 116 deletions(-) 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() From 5c68309fb0b9bf04df72fc6c98adc014bc874c0b Mon Sep 17 00:00:00 2001 From: itsMikeLowrey Date: Sat, 21 Mar 2020 21:38:35 -0400 Subject: [PATCH 3/7] Fixed formatting standard issues --- stats/index.js | 85 +++++++++++++++++++++++++++++++++++---------- stats/simulation.js | 74 ++++++++++++++++----------------------- stats/strategies.js | 28 +++++++-------- test/_utils.js | 4 +-- utils.js | 6 ++-- 5 files changed, 115 insertions(+), 82 deletions(-) diff --git a/stats/index.js b/stats/index.js index 6885d5f..af9d91a 100644 --- a/stats/index.js +++ b/stats/index.js @@ -1,34 +1,81 @@ -let Simulation = require('./simulation') -let modules = require('./strategies') -let min = 14226 // 0.1 USD -let max = 142251558 // 1000 USD -let feeRate = 56 * 100 -let results = [] +const Simulation = require('./simulation') +const modules = require('./strategies') +const min = 14226 // 0.1 USD +const max = 142251558 // 1000 USD +const feeRate = 56 * 100 +const results = [] + +// switch between two modes +// true - failed payments are discarded +// false - failed payments are queued until there is enough balance +const discardFailed = false +const walletType = 'p2pkh' // set to either p2pkh or p2sh + +// data from blockchaing from ~august 2015 - august 2017 +// see https://gist.github.com/runn1ng/8e2881e5bb44e01748e46b3d1c549038 +// these are 2-of-3 lengths, most common p2sh (66%), percs changed so they add to 100 +const scripthashScriptLengthData = { + prob: 0.16, + inLengthPercs: { + 252: 25.29, + 253: 49.77, + 254: 24.94 + }, + outLength: 23 +} + +// these are compressed key length (90% of p2pkh inputs) +// percs changed so they add to 100 +const pubkeyhashScriptLengthData = { + prob: 0.84, + inLengthPercs: { + 105: 0.4, + 106: 50.7, + 107: 48.81, + 108: 0.09 + }, + outLength: 25 +} + +const selectedData = walletType === 'p2pkh' ? pubkeyhashScriptLengthData : scripthashScriptLengthData +const inLengthProbs = selectedData.inLengthPercs + +const outLengthProbs = {}; +[scripthashScriptLengthData, pubkeyhashScriptLengthData].forEach(({ prob, outLength }) => { + outLengthProbs[outLength] = prob +}) // n samples for (var j = 0; j < 100; ++j) { if (j % 200 === 0) console.log('Iteration', j) - let stages = [] + const stages = [] for (var i = 1; i < 4; ++i) { - let utxos = Simulation.generateTxos(20 / i, min, max) - let txos = Simulation.generateTxos(80 / i, min, max / 3) - + const utxos = Simulation.generateTxos(20 / i, min, max, inLengthProbs) + const txos = Simulation.generateTxos(80 / i, min, max / 3, outLengthProbs) stages.push({ utxos, txos }) } // for each strategy for (var name in modules) { - let f = modules[name] - let simulation = new Simulation(name, f, feeRate) + const f = modules[name] + const simulation = new Simulation(name, f, feeRate) stages.forEach((stage) => { // supplement our UTXOs - stage.utxos.forEach(x => simulation.addUTXO(x)) + stage.utxos.forEach(x => { + simulation.addUTXO(x) + + // if discardFailed == true, this should do nothing + simulation.run(discardFailed) + }) // now, run stage.txos.length transactions - stage.txos.forEach((txo) => simulation.runQueued([txo])) + stage.txos.forEach((txo) => { + simulation.plan([txo]) + simulation.run(discardFailed) + }) }) simulation.finish() @@ -37,16 +84,16 @@ for (var j = 0; j < 100; ++j) { } function merge (results) { - let resultMap = {} + const resultMap = {} results.forEach(({ stats }) => { - let result = resultMap[stats.name] + const result = resultMap[stats.name] if (result) { result.inputs += stats.inputs result.outputs += stats.outputs result.transactions += stats.transactions - result.failed += stats.failed + result.plannedTransactions += stats.plannedTransactions result.fees += stats.fees result.bytes += stats.bytes result.utxos += stats.utxos @@ -76,8 +123,8 @@ merge(results).sort((a, b) => { // top 20 only }).slice(0, 20).forEach((x, i) => { - let { stats } = x - let DNF = stats.failed / (stats.transactions + stats.failed) + const { stats } = x + const DNF = 1 - stats.transactions / stats.plannedTransactions console.log( pad(i), diff --git a/stats/simulation.js b/stats/simulation.js index 35ebd1b..1658250 100644 --- a/stats/simulation.js +++ b/stats/simulation.js @@ -1,3 +1,5 @@ +var weighted = require('weighted') + function rayleight (a, b) { return a + b * Math.sqrt(-Math.log(uniform(0, 1))) } @@ -18,27 +20,26 @@ function Simulation (name, algorithm, feeRate) { this.utxoMap = {} this.stats = { name, + plannedTransactions: 0, transactions: 0, inputs: 0, outputs: 0, fees: 0, - bytes: 0, - failed: 0 + bytes: 0 } - this.queue = [] + this.planned = [] // used for tracking UTXOs (w/o transaction ids) this.k = 0 } -Simulation.generateTxos = function (n, min, max) { - let txos = [] +Simulation.generateTxos = function (n, min, max, scriptSizes) { + const txos = [] for (let i = 0; i < n; ++i) { - let v = rayleight(min, max) >>> 0 + const v = rayleight(min, max) >>> 0 - let s = 106 - if (Math.random() > 0.9) s = 300 + const s = parseInt(weighted.select(scriptSizes)) txos.push({ address: randomAddress(), @@ -51,15 +52,12 @@ Simulation.generateTxos = function (n, min, max) { return txos } -Simulation.prototype.addUTXO = function (utxo, change) { - let k = this.k + 1 +Simulation.prototype.addUTXO = function (utxo) { + const k = this.k + 1 if (this.utxoMap[k] !== undefined) throw new Error('Bad UTXO') this.utxoMap[k] = Object.assign({}, utxo, { id: k }) this.k = k - if (!change) { - this.tryQueue() - } } Simulation.prototype.useUTXO = function (utxo) { @@ -69,49 +67,37 @@ Simulation.prototype.useUTXO = function (utxo) { } Simulation.prototype.getUTXOs = function () { - let utxos = [] - for (let k in this.utxoMap) utxos.push(this.utxoMap[k]) + const utxos = [] + for (const k in this.utxoMap) utxos.push(this.utxoMap[k]) return utxos } -Simulation.prototype.runQueued = function (outputs) { - this.queue.push(outputs) - this.tryQueue() +Simulation.prototype.plan = function (outputs) { + this.stats.plannedTransactions += 1 + this.planned.push(outputs) } -Simulation.prototype.tryQueue = function () { - while (this.queue.length > 0) { - let outputs = this.queue[0] - let utxos = this.getUTXOs() +Simulation.prototype.run = function (discardFailed) { + while (this.planned.length > 0) { + const outputs = this.planned[0] + const utxos = this.getUTXOs() - let { inputs, outputs: outputs2, fee } = this.algorithm(utxos, outputs, this.feeRate) + const { inputs, outputs: outputs2, fee } = this.algorithm(utxos, outputs, this.feeRate) if (!inputs) { + if (discardFailed) { + this.planned.shift() + } return } - this.queue.shift() - - this.useAlgorithmResult(inputs, outputs2, fee) - } -} - -Simulation.prototype.run = function (outputs) { - let utxos = this.getUTXOs() - - let { inputs, outputs: outputs2, fee } = this.algorithm(utxos, outputs, this.feeRate) + this.planned.shift() - if (!inputs) { - this.stats.failed += 1 - return + this.useResult(inputs, outputs2, fee) } - - this.useAlgorithmResult(inputs, outputs2, fee) - - return true } -Simulation.prototype.useAlgorithmResult = function (inputs, outputs, fee) { +Simulation.prototype.useResult = function (inputs, outputs, fee) { this.stats.transactions += 1 this.stats.inputs += inputs.length this.stats.outputs += outputs.length @@ -130,14 +116,14 @@ Simulation.prototype.useAlgorithmResult = function (inputs, outputs, fee) { outputs.filter(x => x.script === undefined).forEach((x) => { // assign it a random address x.address = randomAddress() - this.addUTXO(x, true) + this.addUTXO(x) }) } Simulation.prototype.finish = function () { - let utxos = this.getUTXOs() + const utxos = this.getUTXOs() this.stats.utxos = utxos.length - let costToEmpty = utils.transactionBytes(utxos, []) * this.feeRate // output cost is negligible + const costToEmpty = utils.transactionBytes(utxos, []) * this.feeRate // output cost is negligible this.stats.totalCost = this.stats.fees + costToEmpty } diff --git a/stats/strategies.js b/stats/strategies.js index bf86280..4fcd4c3 100644 --- a/stats/strategies.js +++ b/stats/strategies.js @@ -1,16 +1,16 @@ -let accumulative = require('../accumulative') -let blackjack = require('../blackjack') -let shuffle = require('fisher-yates') -let shuffleInplace = require('fisher-yates/inplace') -let coinSelect = require('../') -let utils = require('../utils') +const accumulative = require('../accumulative') +const blackjack = require('../blackjack') +const shuffle = require('fisher-yates') +const shuffleInplace = require('fisher-yates/inplace') +const coinSelect = require('../') +const utils = require('../utils') function blackmax (utxos, outputs, feeRate) { // order by ascending value utxos = utxos.concat().sort((a, b) => a.value - b.value) // attempt to use the blackjack strategy first (no change output) - let base = blackjack(utxos, outputs, feeRate) + const base = blackjack(utxos, outputs, feeRate) if (base.inputs) return base // else, try the accumulative strategy @@ -22,7 +22,7 @@ function blackmin (utxos, outputs, feeRate) { utxos = utxos.concat().sort((a, b) => b.value - a.value) // attempt to use the blackjack strategy first (no change output) - let base = blackjack(utxos, outputs, feeRate) + const base = blackjack(utxos, outputs, feeRate) if (base.inputs) return base // else, try the accumulative strategy @@ -33,7 +33,7 @@ function blackrand (utxos, outputs, feeRate) { utxos = shuffle(utxos) // attempt to use the blackjack strategy first (no change output) - let base = blackjack(utxos, outputs, feeRate) + const base = blackjack(utxos, outputs, feeRate) if (base.inputs) return base // else, try the accumulative strategy @@ -62,8 +62,8 @@ function proximal (utxos, outputs, feeRate) { const outAccum = outputs.reduce((a, x) => a + x.value, 0) utxos = utxos.concat().sort((a, b) => { - let aa = a.value - outAccum - let bb = b.value - outAccum + const aa = a.value - outAccum + const bb = b.value - outAccum return aa - bb }) @@ -80,13 +80,13 @@ function random (utxos, outputs, feeRate) { function bestof (utxos, outputs, feeRate) { let n = 100 - let utxosCopy = utxos.concat() + const utxosCopy = utxos.concat() let best = { fee: Infinity } while (n) { shuffleInplace(utxosCopy) - let result = accumulative(utxos, outputs, feeRate) + const result = accumulative(utxos, outputs, feeRate) if (result.inputs && result.fee < best.fee) { best = result } @@ -102,7 +102,7 @@ function utxoScore (x, feeRate) { } function privet (utxos, outputs, feeRate) { - let txosMap = {} + const txosMap = {} utxos.forEach((txo) => { if (!txosMap[txo.address]) { txosMap[txo.address] = [] diff --git a/test/_utils.js b/test/_utils.js index e59b270..bf5a6e4 100644 --- a/test/_utils.js +++ b/test/_utils.js @@ -14,7 +14,7 @@ function addScriptLengthToExpected (expected, inputLength, outputLength) { newExpected.inputs = expected.inputs.map(function (input) { var newInput = Object.assign({}, input) if (newInput.script == null) { - newInput.script = {length: inputLength} + newInput.script = { length: inputLength } } return newInput }) @@ -24,7 +24,7 @@ function addScriptLengthToExpected (expected, inputLength, outputLength) { newExpected.outputs = expected.outputs.map(function (output) { var newOutput = Object.assign({}, output) if (newOutput.script == null) { - newOutput.script = {length: outputLength} + newOutput.script = { length: outputLength } } return newOutput }) diff --git a/utils.js b/utils.js index 786a648..ef5e038 100644 --- a/utils.js +++ b/utils.js @@ -12,7 +12,7 @@ function outputBytes (output) { } function dustThreshold (feeRate, inputLenghtEstimate) { - return inputBytes({script: {length: inputLenghtEstimate}}) * feeRate + return inputBytes({ script: { length: inputLenghtEstimate } }) * feeRate } function transactionBytes (inputs, outputs) { @@ -39,13 +39,13 @@ function sumOrNaN (range) { function finalize (inputs, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { var bytesAccum = transactionBytes(inputs, outputs) - var blankOutputBytes = outputBytes({script: {length: changeOutputLength}}) + 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, changeInputLengthEstimate)) { - outputs = outputs.concat({ value: remainderAfterExtraOutput, script: {length: changeOutputLength} }) + outputs = outputs.concat({ value: remainderAfterExtraOutput, script: { length: changeOutputLength } }) } var fee = sumOrNaN(inputs) - sumOrNaN(outputs) From de4b75d46c4eb29dc599f08c39fb85b01464c8d3 Mon Sep 17 00:00:00 2001 From: itsMikeLowrey Date: Sat, 21 Mar 2020 21:59:16 -0400 Subject: [PATCH 4/7] Added default options --- accumulative.js | 7 +- blackjack.js | 9 +-- break.js | 9 +-- defaultOpts.js | 5 ++ index.js | 9 +-- split.js | 11 +-- test/accumulative.js | 10 +-- test/break.js | 8 ++- test/fixtures/accumulative.json | 124 ++++++++------------------------ test/fixtures/break.json | 60 ++++------------ test/fixtures/index.json | 117 ++++++++---------------------- test/fixtures/split.json | 52 ++++---------- test/index.js | 13 ++-- test/split.js | 10 +-- 14 files changed, 141 insertions(+), 303 deletions(-) create mode 100644 defaultOpts.js diff --git a/accumulative.js b/accumulative.js index 9903129..43d53fb 100644 --- a/accumulative.js +++ b/accumulative.js @@ -1,8 +1,9 @@ var utils = require('./utils') - +var defaultOpts = require('./defaultOpts') +var defaultOptsObj = defaultOpts.defaultOpts // add inputs until we reach or surpass the target value (or deplete) // worst-case: O(n) -module.exports = function accumulative (utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { +module.exports = function accumulative (utxos, outputs, feeRate, options = defaultOptsObj) { if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes([], outputs) @@ -31,7 +32,7 @@ module.exports = function accumulative (utxos, outputs, feeRate, changeInputLeng // go again? if (inAccum < outAccum + fee) continue - return utils.finalize(inputs, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) + return utils.finalize(inputs, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength) } return { fee: feeRate * bytesAccum } diff --git a/blackjack.js b/blackjack.js index 7e27234..3e099ef 100644 --- a/blackjack.js +++ b/blackjack.js @@ -1,8 +1,9 @@ var utils = require('./utils') - +var defaultOpts = require('./defaultOpts') +var defaultOptsObj = defaultOpts.defaultOpts // 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, changeInputLengthEstimate, changeOutputLength) { +module.exports = function blackjack (utxos, outputs, feeRate, options = defaultOptsObj) { if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes([], outputs) @@ -10,7 +11,7 @@ module.exports = function blackjack (utxos, outputs, feeRate, changeInputLengthE var inAccum = 0 var inputs = [] var outAccum = utils.sumOrNaN(outputs) - var threshold = utils.dustThreshold(feeRate, changeInputLengthEstimate) + var threshold = utils.dustThreshold(feeRate, options.changeInputLengthEstimate) for (var i = 0; i < utxos.length; ++i) { var input = utxos[i] @@ -28,7 +29,7 @@ module.exports = function blackjack (utxos, outputs, feeRate, changeInputLengthE // go again? if (inAccum < outAccum + fee) continue - return utils.finalize(inputs, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) + return utils.finalize(inputs, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength) } return { fee: feeRate * bytesAccum } diff --git a/break.js b/break.js index 54897c3..5122662 100644 --- a/break.js +++ b/break.js @@ -1,7 +1,8 @@ var utils = require('./utils') - -// break utxos into the maximum number of output possible -module.exports = function broken (utxos, output, feeRate, changeInputLengthEstimate, changeOutputLength) { +var defaultOpts = require('./defaultOpts') +var defaultOptsObj = defaultOpts.defaultOpts +// break utxos into the maximum number of 'output' possible +module.exports = function broken (utxos, output, feeRate, options = defaultOptsObj) { if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes(utxos, []) @@ -29,5 +30,5 @@ module.exports = function broken (utxos, output, feeRate, changeInputLengthEstim outAccum += value outputs.push(output) } - return utils.finalize(utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) + return utils.finalize(utxos, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength) } diff --git a/defaultOpts.js b/defaultOpts.js new file mode 100644 index 0000000..dc7bac1 --- /dev/null +++ b/defaultOpts.js @@ -0,0 +1,5 @@ +const defaultOpts = { + changeInputLengthEstimate: 107, + changeOutputLength: 25 +} +exports.defaultOpts = defaultOpts diff --git a/index.js b/index.js index c8e9fe4..f43cf95 100644 --- a/index.js +++ b/index.js @@ -1,21 +1,22 @@ var accumulative = require('./accumulative') var blackjack = require('./blackjack') var utils = require('./utils') - +var defaultOpts = require('./defaultOpts') +var defaultOptsObj = defaultOpts.defaultOpts // order by descending value, minus the inputs approximate fee function utxoScore (x, feeRate) { return x.value - (feeRate * utils.inputBytes(x)) } -module.exports = function coinSelect (utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { +module.exports = function coinSelect (utxos, outputs, feeRate, options = defaultOptsObj) { 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, changeInputLengthEstimate, changeOutputLength) + var base = blackjack(utxos, outputs, feeRate, options) if (base.inputs) return base // else, try the accumulative strategy - return accumulative(utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) + return accumulative(utxos, outputs, feeRate, options) } diff --git a/split.js b/split.js index b1a4710..329407c 100644 --- a/split.js +++ b/split.js @@ -1,7 +1,8 @@ var utils = require('./utils') - +var defaultOpts = require('./defaultOpts') +var defaultOptsObj = defaultOpts.defaultOpts // split utxos between each output, ignores outputs with .value defined -module.exports = function split (utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { +module.exports = function split (utxos, outputs, feeRate, options = defaultOptsObj) { if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes(utxos, outputs) @@ -17,7 +18,7 @@ module.exports = function split (utxos, outputs, feeRate, changeInputLengthEstim return a + !isFinite(x.value) }, 0) - if (remaining === 0 && unspecified === 0) return utils.finalize(utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) + if (remaining === 0 && unspecified === 0) return utils.finalize(utxos, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength) var splitOutputsCount = outputs.reduce(function (a, x) { return a + !x.value @@ -26,7 +27,7 @@ module.exports = function split (utxos, outputs, feeRate, changeInputLengthEstim // ensure every output is either user defined, or over the threshold if (!outputs.every(function (x) { - return x.value !== undefined || (splitValue > utils.dustThreshold(feeRate, changeInputLengthEstimate)) + return x.value !== undefined || (splitValue > utils.dustThreshold(feeRate, options.changeInputLengthEstimate)) })) return { fee: fee } // assign splitValue to outputs not user defined @@ -40,5 +41,5 @@ module.exports = function split (utxos, outputs, feeRate, changeInputLengthEstim return y }) - return utils.finalize(utxos, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) + return utils.finalize(utxos, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength) } diff --git a/test/accumulative.js b/test/accumulative.js index 33a8695..063c3e9 100644 --- a/test/accumulative.js +++ b/test/accumulative.js @@ -2,21 +2,23 @@ var coinAccum = require('../accumulative') var fixtures = require('./fixtures/accumulative') var tape = require('tape') var utils = require('./_utils') +var defaultOpts = require('../defaultOpts') +var defaultOptsObj = defaultOpts.defaultOpts fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputLength = f.inputLength - var outputLength = f.outputLength + var inputLength = defaultOptsObj.changeInputLengthEstimate + var outputLength = defaultOptsObj.changeOutputLength 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) + var actual = coinAccum(inputs, outputs, f.feeRate) t.same(actual, expected) if (actual.inputs) { - var feedback = coinAccum(actual.inputs, actual.outputs, f.feeRate, inputLength, outputLength) + var feedback = coinAccum(actual.inputs, actual.outputs, f.feeRate) t.same(feedback, expected) } diff --git a/test/break.js b/test/break.js index f1109d6..a091fb6 100644 --- a/test/break.js +++ b/test/break.js @@ -2,17 +2,19 @@ var coinBreak = require('../break') var fixtures = require('./fixtures/break') var tape = require('tape') var utils = require('./_utils') +var defaultOpts = require('../defaultOpts') +var defaultOptsObj = defaultOpts.defaultOpts fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputLength = f.inputLength - var outputLength = f.outputLength + var inputLength = defaultOptsObj.changeInputLengthEstimate + var outputLength = defaultOptsObj.changeOutputLength 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) + var actual = coinBreak(inputs, outputs[0], f.feeRate) t.same(actual, expected) t.end() diff --git a/test/fixtures/accumulative.json b/test/fixtures/accumulative.json index 7ebb093..23505d5 100644 --- a/test/fixtures/accumulative.json +++ b/test/fixtures/accumulative.json @@ -21,9 +21,7 @@ } ], "fee": 2001 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, change expected", @@ -50,9 +48,7 @@ } ], "fee": 1130 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, change expected, value > 2^32", @@ -79,9 +75,7 @@ } ], "fee": 1130 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, sub-optimal inputs (if re-ordered), direct possible", @@ -107,9 +101,7 @@ } ], "fee": 2300 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee", @@ -135,9 +127,7 @@ } ], "fee": 3200 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected", @@ -166,9 +156,7 @@ } ], "fee": 1130 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, passes, skipped detrimental input", @@ -207,9 +195,7 @@ "value": 4000 } ] - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, fails, skips (and finishes on) detrimental input", @@ -227,9 +213,7 @@ ], "expected": { "fee": 18700 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, passes, poor ordering causing high fee", @@ -275,9 +259,7 @@ } ], "fee": 5000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, passes, improved ordering causing low fee, no waste", @@ -316,9 +298,7 @@ } ], "fee": 2000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, optimal inputs, no change", @@ -342,9 +322,7 @@ } ], "fee": 2300 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, no fee, change expected", @@ -396,9 +374,7 @@ } ], "fee": 0 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, script provided, no change", @@ -430,9 +406,7 @@ } ], "fee": 5000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, script provided, change expected", @@ -467,9 +441,7 @@ } ], "fee": 4010 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, 2 inputs (related), no change", @@ -501,9 +473,7 @@ } ], "fee": 2000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "many outputs, no change", @@ -549,9 +519,7 @@ } ], "fee": 6221 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "many outputs, change expected", @@ -600,9 +568,7 @@ } ], "fee": 6240 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "many outputs, no fee, change expected", @@ -658,9 +624,7 @@ } ], "fee": 0 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "no outputs, no change", @@ -678,9 +642,7 @@ ], "outputs": [], "fee": 1900 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "no outputs, change expected", @@ -702,9 +664,7 @@ } ], "fee": 1920 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "inputs used in order of DESCENDING", @@ -749,9 +709,7 @@ "value": 7850 } ] - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "not enough funds, empty result", @@ -764,9 +722,7 @@ ], "expected": { "fee": 1920 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "not enough funds (w/ fee), empty result", @@ -779,9 +735,7 @@ ], "expected": { "fee": 1920 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "not enough funds (no inputs), empty result", @@ -790,9 +744,7 @@ "outputs": [], "expected": { "fee": 100 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "not enough funds (no inputs), empty result (>1KiB)", @@ -831,9 +783,7 @@ ], "expected": { "fee": 9960 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 outputs, some with missing value (NaN)", @@ -847,9 +797,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "input with float values (NaN)", @@ -863,9 +811,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 outputs, with float values (NaN)", @@ -879,9 +825,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 outputs, string values (NaN)", @@ -899,9 +843,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -912,9 +854,7 @@ "outputs": [ 10000 ], - "expected": {}, - "inputLength": 107, - "outputLength": 25 + "expected": {} }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -925,8 +865,6 @@ "outputs": [ 10000 ], - "expected": {}, - "inputLength": 107, - "outputLength": 25 + "expected": {} } ] diff --git a/test/fixtures/break.json b/test/fixtures/break.json index 8d7b1c1..71483a3 100644 --- a/test/fixtures/break.json +++ b/test/fixtures/break.json @@ -18,9 +18,7 @@ } ], "fee": 1920 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1:1", @@ -45,9 +43,7 @@ "value": 10000 } ] - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1:1, w/ change", @@ -71,9 +67,7 @@ } ], "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1:2, strange output type", @@ -108,9 +102,7 @@ } ], "fee": 7000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1:4", @@ -140,9 +132,7 @@ } ], "fee": 4000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2:5", @@ -179,9 +169,7 @@ } ], "fee": 5000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2:5, no fee", @@ -218,9 +206,7 @@ } ], "fee": 0 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2:2 (+1), w/ change", @@ -247,9 +233,7 @@ } ], "fee": 1820 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2:3 (+1), no fee, w/ change", @@ -283,9 +267,7 @@ } ], "fee": 0 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "not enough funds", @@ -297,9 +279,7 @@ "output": 40000, "expected": { "fee": 3400 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "no inputs", @@ -308,9 +288,7 @@ "output": 2000, "expected": { "fee": 440 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "invalid output (NaN)", @@ -319,9 +297,7 @@ "output": {}, "expected": { "fee": 100 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "input with float values (NaN)", @@ -332,9 +308,7 @@ "output": 5000, "expected": { "fee": 1580 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -343,9 +317,7 @@ 20000 ], "output": 10000, - "expected": {}, - "inputLength": 107, - "outputLength": 25 + "expected": {} }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -354,8 +326,6 @@ 20000 ], "output": 10000, - "expected": {}, - "inputLength": 107, - "outputLength": 25 + "expected": {} } ] diff --git a/test/fixtures/index.json b/test/fixtures/index.json index 604fac8..cb2a720 100644 --- a/test/fixtures/index.json +++ b/test/fixtures/index.json @@ -21,9 +21,7 @@ } ], "fee": 2001 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, change expected", @@ -50,9 +48,7 @@ } ], "fee": 1130 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, sub-optimal inputs (if re-ordered), direct possible", @@ -78,9 +74,7 @@ } ], "fee": 2300 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee", @@ -106,9 +100,7 @@ } ], "fee": 3200 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected", @@ -137,9 +129,7 @@ } ], "fee": 1130 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, passes, skipped detrimental input", @@ -178,9 +168,7 @@ "value": 4000 } ] - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, passes, poor ordering but still good", @@ -219,9 +207,7 @@ } ], "fee": 2000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, passes, improved ordering causing low fee, no waste", @@ -260,9 +246,7 @@ } ], "fee": 2000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, optimal inputs, no change", @@ -286,9 +270,7 @@ } ], "fee": 2300 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, no fee, change expected", @@ -340,9 +322,7 @@ } ], "fee": 0 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, script provided, no change", @@ -374,9 +354,7 @@ } ], "fee": 5000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, script provided, change expected", @@ -411,9 +389,7 @@ } ], "fee": 4010 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 output, 2 inputs (related), no change", @@ -445,9 +421,7 @@ } ], "fee": 2000 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "many outputs, no change", @@ -493,9 +467,7 @@ } ], "fee": 6221 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "many outputs, change expected", @@ -544,9 +516,7 @@ } ], "fee": 6240 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "many outputs, no fee, change expected", @@ -602,9 +572,7 @@ } ], "fee": 0 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "no outputs, no change", @@ -622,9 +590,7 @@ ], "outputs": [], "fee": 1900 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "no outputs, change expected", @@ -646,9 +612,7 @@ } ], "fee": 1920 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "inputs used in order of DESCENDING", @@ -683,9 +647,7 @@ "value": 25000 } ] - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "not enough funds, empty result", @@ -698,9 +660,7 @@ ], "expected": { "fee": 1920 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "not enough funds (w/ fee), empty result", @@ -713,9 +673,7 @@ ], "expected": { "fee": 1920 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "not enough funds (no inputs), empty result", @@ -724,9 +682,7 @@ "outputs": [], "expected": { "fee": 100 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "not enough funds (no inputs), empty result (>1KiB)", @@ -765,9 +721,7 @@ ], "expected": { "fee": 9960 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 outputs, some with missing value (NaN)", @@ -781,9 +735,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "input with float values (NaN)", @@ -797,9 +749,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 outputs, with float values (NaN)", @@ -813,9 +763,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 outputs, string values (NaN)", @@ -833,9 +781,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -846,9 +792,7 @@ "outputs": [ 10000 ], - "expected": {}, - "inputLength": 107, - "outputLength": 25 + "expected": {} }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -859,9 +803,6 @@ "outputs": [ 10000 ], - "expected": {}, - "inputLength": 107, - "outputLength": 25 + "expected": {} } ] - diff --git a/test/fixtures/split.json b/test/fixtures/split.json index f971327..30c1dd2 100644 --- a/test/fixtures/split.json +++ b/test/fixtures/split.json @@ -28,9 +28,7 @@ } ], "fee": 2601 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "5 to 2", @@ -73,9 +71,7 @@ } ], "fee": 8180 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "3 to 1", @@ -106,9 +102,7 @@ } ], "fee": 4880 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "3 to 3 (1 output pre-defined)", @@ -154,9 +148,7 @@ } ], "fee": 5560 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 to 0 (no result)", @@ -168,9 +160,7 @@ "outputs": [], "expected": { "fee": 3060 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "0 to 2 (no result)", @@ -182,9 +172,7 @@ ], "expected": { "fee": 780 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "1 to 2, output is dust (no result)", @@ -197,9 +185,7 @@ ], "expected": { "fee": 1920 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 outputs, some with missing value (NaN)", @@ -228,9 +214,7 @@ } ], "fee": 2486 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 outputs, some with float values (NaN)", @@ -246,9 +230,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "2 outputs, string values (NaN)", @@ -266,9 +248,7 @@ ], "expected": { "fee": 2486 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "input with float values (NaN)", @@ -282,9 +262,7 @@ ], "expected": { "fee": 2260 - }, - "inputLength": 107, - "outputLength": 25 + } }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -295,9 +273,7 @@ "outputs": [ {} ], - "expected": {}, - "inputLength": 107, - "outputLength": 25 + "expected": {} }, { "description": "inputs and outputs, bad feeRate (NaN)", @@ -308,8 +284,6 @@ "outputs": [ {} ], - "expected": {}, - "inputLength": 107, - "outputLength": 25 + "expected": {} } ] diff --git a/test/index.js b/test/index.js index 28d482a..fe62fb9 100644 --- a/test/index.js +++ b/test/index.js @@ -2,21 +2,20 @@ var coinSelect = require('../') var fixtures = require('./fixtures') var tape = require('tape') var utils = require('./_utils') - +var defaultOpts = require('../defaultOpts') +var defaultOptsObj = defaultOpts.defaultOpts fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputLength = f.inputLength - var outputLength = f.outputLength - + var inputLength = defaultOptsObj.changeInputLengthEstimate + var outputLength = defaultOptsObj.changeOutputLength 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) + var actual = coinSelect(inputs, outputs, f.feeRate) t.same(actual, expected) if (actual.inputs) { - var feedback = coinSelect(actual.inputs, actual.outputs, f.feeRate, inputLength, outputLength) + var feedback = coinSelect(actual.inputs, actual.outputs, f.feeRate) t.same(feedback, expected) } diff --git a/test/split.js b/test/split.js index d1db686..322c4e0 100644 --- a/test/split.js +++ b/test/split.js @@ -2,21 +2,23 @@ var coinSplit = require('../split') var fixtures = require('./fixtures/split') var tape = require('tape') var utils = require('./_utils') +var defaultOpts = require('../defaultOpts') +var defaultOptsObj = defaultOpts.defaultOpts fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputLength = f.inputLength - var outputLength = f.outputLength + var inputLength = defaultOptsObj.changeInputLengthEstimate + var outputLength = defaultOptsObj.changeOutputLength 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) + var actual = coinSplit(inputs, outputs, f.feeRate) t.same(actual, expected) if (actual.inputs) { - var feedback = coinSplit(inputs, actual.outputs, f.feeRate, inputLength, outputLength) + var feedback = coinSplit(inputs, actual.outputs, f.feeRate) t.same(feedback, expected) } From 35008ad871f57e8257329c0b44b56d6033769dc6 Mon Sep 17 00:00:00 2001 From: itsMikeLowrey Date: Sat, 21 Mar 2020 22:02:14 -0400 Subject: [PATCH 5/7] Added options to utls.finalize --- accumulative.js | 2 +- blackjack.js | 2 +- break.js | 2 +- split.js | 2 +- utils.js | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/accumulative.js b/accumulative.js index 43d53fb..1a46e6e 100644 --- a/accumulative.js +++ b/accumulative.js @@ -32,7 +32,7 @@ module.exports = function accumulative (utxos, outputs, feeRate, options = defau // go again? if (inAccum < outAccum + fee) continue - return utils.finalize(inputs, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength) + return utils.finalize(inputs, outputs, feeRate, options) } return { fee: feeRate * bytesAccum } diff --git a/blackjack.js b/blackjack.js index 3e099ef..f0319b0 100644 --- a/blackjack.js +++ b/blackjack.js @@ -29,7 +29,7 @@ module.exports = function blackjack (utxos, outputs, feeRate, options = defaultO // go again? if (inAccum < outAccum + fee) continue - return utils.finalize(inputs, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength) + return utils.finalize(inputs, outputs, feeRate, options) } return { fee: feeRate * bytesAccum } diff --git a/break.js b/break.js index 5122662..bf19754 100644 --- a/break.js +++ b/break.js @@ -30,5 +30,5 @@ module.exports = function broken (utxos, output, feeRate, options = defaultOptsO outAccum += value outputs.push(output) } - return utils.finalize(utxos, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength) + return utils.finalize(utxos, outputs, feeRate, options) } diff --git a/split.js b/split.js index 329407c..e88b7eb 100644 --- a/split.js +++ b/split.js @@ -41,5 +41,5 @@ module.exports = function split (utxos, outputs, feeRate, options = defaultOptsO return y }) - return utils.finalize(utxos, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength) + return utils.finalize(utxos, outputs, feeRate, options) } diff --git a/utils.js b/utils.js index ef5e038..7f037ee 100644 --- a/utils.js +++ b/utils.js @@ -37,15 +37,15 @@ function sumOrNaN (range) { return range.reduce(function (a, x) { return a + uintOrNaN(x.value) }, 0) } -function finalize (inputs, outputs, feeRate, changeInputLengthEstimate, changeOutputLength) { +function finalize (inputs, outputs, feeRate, options) { var bytesAccum = transactionBytes(inputs, outputs) - var blankOutputBytes = outputBytes({ script: { length: changeOutputLength } }) + var blankOutputBytes = outputBytes({ script: { length: options.changeOutputLength } }) var feeAfterExtraOutput = feeRate * (bytesAccum + blankOutputBytes) var remainderAfterExtraOutput = sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput) // is it worth a change output? - if (remainderAfterExtraOutput > dustThreshold(feeRate, changeInputLengthEstimate)) { - outputs = outputs.concat({ value: remainderAfterExtraOutput, script: { length: changeOutputLength } }) + if (remainderAfterExtraOutput > dustThreshold(feeRate, options.changeInputLengthEstimate)) { + outputs = outputs.concat({ value: remainderAfterExtraOutput, script: { length: options.changeOutputLength } }) } var fee = sumOrNaN(inputs) - sumOrNaN(outputs) From 3b101edd27d1359f00e8bc236b524290cac6addd Mon Sep 17 00:00:00 2001 From: itsMikeLowrey Date: Sat, 21 Mar 2020 23:35:48 -0400 Subject: [PATCH 6/7] added options proccessing --- accumulative.js | 7 ++++--- blackjack.js | 7 ++++--- break.js | 7 ++++--- defaultOpts.js | 11 +++++++++-- index.js | 7 ++++--- split.js | 7 ++++--- test/accumulative.js | 9 +++++---- test/break.js | 9 +++++---- test/index.js | 10 ++++++---- test/split.js | 9 +++++---- 10 files changed, 50 insertions(+), 33 deletions(-) diff --git a/accumulative.js b/accumulative.js index 1a46e6e..c58dd44 100644 --- a/accumulative.js +++ b/accumulative.js @@ -1,9 +1,10 @@ var utils = require('./utils') -var defaultOpts = require('./defaultOpts') -var defaultOptsObj = defaultOpts.defaultOpts +var processOptions = require('./defaultOpts') +var processOptionsFunc = processOptions.processOptions // add inputs until we reach or surpass the target value (or deplete) // worst-case: O(n) -module.exports = function accumulative (utxos, outputs, feeRate, options = defaultOptsObj) { +module.exports = function accumulative (utxos, outputs, feeRate, options) { + options = processOptionsFunc(options) if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes([], outputs) diff --git a/blackjack.js b/blackjack.js index f0319b0..90d265c 100644 --- a/blackjack.js +++ b/blackjack.js @@ -1,9 +1,10 @@ var utils = require('./utils') -var defaultOpts = require('./defaultOpts') -var defaultOptsObj = defaultOpts.defaultOpts +var processOptions = require('./defaultOpts') +var processOptionsFunc = processOptions.processOptions // 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, options = defaultOptsObj) { +module.exports = function blackjack (utxos, outputs, feeRate, options) { + options = processOptionsFunc() if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes([], outputs) diff --git a/break.js b/break.js index bf19754..ac4f67f 100644 --- a/break.js +++ b/break.js @@ -1,8 +1,9 @@ var utils = require('./utils') -var defaultOpts = require('./defaultOpts') -var defaultOptsObj = defaultOpts.defaultOpts +var processOptions = require('./defaultOpts') +var processOptionsFunc = processOptions.processOptions // break utxos into the maximum number of 'output' possible -module.exports = function broken (utxos, output, feeRate, options = defaultOptsObj) { +module.exports = function broken (utxos, output, feeRate, options) { + options = processOptionsFunc(options) if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes(utxos, []) diff --git a/defaultOpts.js b/defaultOpts.js index dc7bac1..03d9a54 100644 --- a/defaultOpts.js +++ b/defaultOpts.js @@ -1,5 +1,12 @@ const defaultOpts = { changeInputLengthEstimate: 107, - changeOutputLength: 25 + changeOutputLength: 25, + lowRSig: false } -exports.defaultOpts = defaultOpts + +function processOptions (options) { + const mergerdOptions = Object.assign(defaultOpts, options) + return mergerdOptions +} + +exports.processOptions = processOptions diff --git a/index.js b/index.js index f43cf95..40aeb9b 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,15 @@ var accumulative = require('./accumulative') var blackjack = require('./blackjack') var utils = require('./utils') -var defaultOpts = require('./defaultOpts') -var defaultOptsObj = defaultOpts.defaultOpts +var processOptions = require('./defaultOpts') +var processOptionsFunc = processOptions.processOptions // order by descending value, minus the inputs approximate fee function utxoScore (x, feeRate) { return x.value - (feeRate * utils.inputBytes(x)) } -module.exports = function coinSelect (utxos, outputs, feeRate, options = defaultOptsObj) { +module.exports = function coinSelect (utxos, outputs, feeRate, options) { + options = processOptionsFunc() utxos = utxos.concat().sort(function (a, b) { return utxoScore(b, feeRate) - utxoScore(a, feeRate) }) diff --git a/split.js b/split.js index e88b7eb..da197e3 100644 --- a/split.js +++ b/split.js @@ -1,8 +1,9 @@ var utils = require('./utils') -var defaultOpts = require('./defaultOpts') -var defaultOptsObj = defaultOpts.defaultOpts +var processOptions = require('./defaultOpts') +var processOptionsFunc = processOptions.processOptions // split utxos between each output, ignores outputs with .value defined -module.exports = function split (utxos, outputs, feeRate, options = defaultOptsObj) { +module.exports = function split (utxos, outputs, feeRate, options) { + options = processOptionsFunc() if (!isFinite(utils.uintOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes(utxos, outputs) diff --git a/test/accumulative.js b/test/accumulative.js index 063c3e9..e176032 100644 --- a/test/accumulative.js +++ b/test/accumulative.js @@ -2,13 +2,14 @@ var coinAccum = require('../accumulative') var fixtures = require('./fixtures/accumulative') var tape = require('tape') var utils = require('./_utils') -var defaultOpts = require('../defaultOpts') -var defaultOptsObj = defaultOpts.defaultOpts +var processOptions = require('../defaultOpts') +var processOptionsFunc = processOptions.processOptions fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputLength = defaultOptsObj.changeInputLengthEstimate - var outputLength = defaultOptsObj.changeOutputLength + const options = processOptionsFunc() + var inputLength = options.changeInputLengthEstimate + var outputLength = options.changeOutputLength var inputs = utils.expand(f.inputs, true, inputLength) var outputs = utils.expand(f.outputs, false, outputLength) diff --git a/test/break.js b/test/break.js index a091fb6..778e1e1 100644 --- a/test/break.js +++ b/test/break.js @@ -2,13 +2,14 @@ var coinBreak = require('../break') var fixtures = require('./fixtures/break') var tape = require('tape') var utils = require('./_utils') -var defaultOpts = require('../defaultOpts') -var defaultOptsObj = defaultOpts.defaultOpts +var processOptions = require('../defaultOpts') +var processOptionsFunc = processOptions.processOptions fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputLength = defaultOptsObj.changeInputLengthEstimate - var outputLength = defaultOptsObj.changeOutputLength + const options = processOptionsFunc() + var inputLength = options.changeInputLengthEstimate + var outputLength = options.changeOutputLength var inputs = utils.expand(f.inputs, false, inputLength) var outputs = utils.expand([f.output], false, outputLength) diff --git a/test/index.js b/test/index.js index fe62fb9..da727c8 100644 --- a/test/index.js +++ b/test/index.js @@ -2,12 +2,14 @@ var coinSelect = require('../') var fixtures = require('./fixtures') var tape = require('tape') var utils = require('./_utils') -var defaultOpts = require('../defaultOpts') -var defaultOptsObj = defaultOpts.defaultOpts +var processOptions = require('../defaultOpts') +var processOptionsFunc = processOptions.processOptions + fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputLength = defaultOptsObj.changeInputLengthEstimate - var outputLength = defaultOptsObj.changeOutputLength + const options = processOptionsFunc() + var inputLength = options.changeInputLengthEstimate + var outputLength = options.changeOutputLength var inputs = utils.expand(f.inputs, true, inputLength) var outputs = utils.expand(f.outputs, false, outputLength) var expected = utils.addScriptLengthToExpected(f.expected, inputLength, outputLength) diff --git a/test/split.js b/test/split.js index 322c4e0..fbfe21d 100644 --- a/test/split.js +++ b/test/split.js @@ -2,13 +2,14 @@ var coinSplit = require('../split') var fixtures = require('./fixtures/split') var tape = require('tape') var utils = require('./_utils') -var defaultOpts = require('../defaultOpts') -var defaultOptsObj = defaultOpts.defaultOpts +var processOptions = require('../defaultOpts') +var processOptionsFunc = processOptions.processOptions fixtures.forEach(function (f) { tape(f.description, function (t) { - var inputLength = defaultOptsObj.changeInputLengthEstimate - var outputLength = defaultOptsObj.changeOutputLength + const options = processOptionsFunc() + var inputLength = options.changeInputLengthEstimate + var outputLength = options.changeOutputLength var inputs = utils.expand(f.inputs, false, inputLength) var outputs = utils.expand(f.outputs.concat(), false, outputLength) From f6dbdbcd635a70d74a14e8d013b05db652d35931 Mon Sep 17 00:00:00 2001 From: itsMikeLowrey Date: Sat, 21 Mar 2020 23:36:15 -0400 Subject: [PATCH 7/7] lowRSig beyond scope of this pr --- defaultOpts.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/defaultOpts.js b/defaultOpts.js index 03d9a54..9dc39fd 100644 --- a/defaultOpts.js +++ b/defaultOpts.js @@ -1,7 +1,6 @@ const defaultOpts = { changeInputLengthEstimate: 107, - changeOutputLength: 25, - lowRSig: false + changeOutputLength: 25 } function processOptions (options) {