From dbd555acda47be7869c394026023ab40a06c6128 Mon Sep 17 00:00:00 2001 From: robertsimoes Date: Wed, 17 Apr 2019 10:01:06 -0700 Subject: [PATCH 1/3] Convert to BN --- accumulative.js | 32 +- blackjack.js | 27 +- bn-extensions.js | 74 ++++ break.js | 27 +- index.js | 4 +- package.json | 4 +- split.js | 27 +- stats/index.js | 2 +- test/_utils.js | 18 +- test/accumulative.js | 11 +- test/fixtures/accumulative.js | 589 +++++++++++++++++++++++++++++ test/fixtures/accumulative.json | 634 -------------------------------- test/fixtures/break.js | 274 ++++++++++++++ test/fixtures/break.json | 296 --------------- test/fixtures/index.js | 548 +++++++++++++++++++++++++++ test/fixtures/index.json | 590 ----------------------------- test/fixtures/split.js | 264 +++++++++++++ test/fixtures/split.json | 289 --------------- test/index.js | 2 + test/utils.js | 21 +- utils.js | 58 +-- 21 files changed, 1899 insertions(+), 1892 deletions(-) create mode 100644 bn-extensions.js create mode 100644 test/fixtures/accumulative.js delete mode 100644 test/fixtures/accumulative.json create mode 100644 test/fixtures/break.js delete mode 100644 test/fixtures/break.json create mode 100644 test/fixtures/index.js delete mode 100644 test/fixtures/index.json create mode 100644 test/fixtures/split.js delete mode 100644 test/fixtures/split.json diff --git a/accumulative.js b/accumulative.js index 42fd28f..3fc0537 100644 --- a/accumulative.js +++ b/accumulative.js @@ -1,38 +1,48 @@ var utils = require('./utils') +var ext = require('./bn-extensions') // add inputs until we reach or surpass the target value (or deplete) // worst-case: O(n) module.exports = function accumulative (utxos, outputs, feeRate) { - if (!isFinite(utils.uintOrNaN(feeRate))) return {} + if (!isFinite(utils.bnOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes([], outputs) - var inAccum = 0 + var inAccum = ext.BN_ZERO var inputs = [] var outAccum = utils.sumOrNaN(outputs) for (var i = 0; i < utxos.length; ++i) { var utxo = utxos[i] var utxoBytes = utils.inputBytes(utxo) - var utxoFee = feeRate * utxoBytes - var utxoValue = utils.uintOrNaN(utxo.value) + var utxoFee = ext.mul(feeRate, utxoBytes) + var utxoValue = utils.bnOrNaN(utxo.value) // skip detrimental input - if (utxoFee > utxo.value) { - if (i === utxos.length - 1) return { fee: feeRate * (bytesAccum + utxoBytes) } + var feeIsMoreThanValue = ext.gt(utxoFee, utxoValue) + // utxoFee > utxoValue + if (feeIsMoreThanValue) { + if (i === utxos.length - 1) { + var bytesSum = ext.add(bytesAccum, utxoBytes) + return { + fee: ext.mul(feeRate, bytesSum) + } + } continue } - bytesAccum += utxoBytes - inAccum += utxoValue + bytesAccum = ext.add(bytesAccum, utxoBytes) + inAccum = ext.add(inAccum, utxoValue) inputs.push(utxo) - var fee = feeRate * bytesAccum + var fee = ext.mul(feeRate, bytesAccum) // go again? - if (inAccum < outAccum + fee) continue + if (ext.lt(inAccum, ext.add(outAccum, fee))) continue return utils.finalize(inputs, outputs, feeRate) } - return { fee: feeRate * bytesAccum } + return { + fee: feeRate.mul(bytesAccum) + } } diff --git a/blackjack.js b/blackjack.js index 316568b..60fdca2 100644 --- a/blackjack.js +++ b/blackjack.js @@ -1,13 +1,14 @@ var utils = require('./utils') +var ext = require('./bn-extensions') // 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) { - if (!isFinite(utils.uintOrNaN(feeRate))) return {} + if (!isFinite(utils.bnOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes([], outputs) - var inAccum = 0 + var inAccum = ext.BN_ZERO var inputs = [] var outAccum = utils.sumOrNaN(outputs) var threshold = utils.dustThreshold({}, feeRate) @@ -15,21 +16,27 @@ module.exports = function blackjack (utxos, outputs, feeRate) { for (var i = 0; i < utxos.length; ++i) { var input = utxos[i] var inputBytes = utils.inputBytes(input) - var fee = feeRate * (bytesAccum + inputBytes) - var inputValue = utils.uintOrNaN(input.value) + var fee = ext.mul(feeRate, ext.add(bytesAccum, inputBytes)) + var inputValue = utils.bnOrNaN(input.value) // would it waste value? - if ((inAccum + inputValue) > (outAccum + fee + threshold)) continue + var totalInputs = ext.add(inAccum, inputValue) + var outputsAndFee = ext.add(outAccum, fee) + var totalOutputs = ext.add(outputsAndFee, threshold) + var inputsAreGreaterThanOutputs = ext.gt(totalInputs, totalOutputs) - bytesAccum += inputBytes - inAccum += inputValue + if (inputsAreGreaterThanOutputs) continue + + bytesAccum = ext.add(bytesAccum, inputBytes) + inAccum = ext.add(inAccum, inputValue) inputs.push(input) // go again? - if (inAccum < outAccum + fee) continue + if (ext.lt(inAccum, outputsAndFee)) continue return utils.finalize(inputs, outputs, feeRate) } - - return { fee: feeRate * bytesAccum } + return { + fee: feeRate.mul(bytesAccum) + } } diff --git a/bn-extensions.js b/bn-extensions.js new file mode 100644 index 0000000..83fb4d7 --- /dev/null +++ b/bn-extensions.js @@ -0,0 +1,74 @@ +var BN = require('bn.js') + +var BN_ZERO = new BN(0) +var BN_ONE = new BN(1) + +function mul (multiplicand, multiplier) { + if (!BN.isBN(multiplicand) || !BN.isBN(multiplier)) return NaN + + return multiplicand.mul(multiplier) +} + +function div (dividend, divisor) { + if (!BN.isBN(dividend) || !BN.isBN(divisor)) return NaN + if (divisor.cmp(BN_ZERO) === 0) return Infinity + + return dividend.div(divisor) +} + +function add (arg1, arg2, arg3) { + // Add two items + if (!BN.isBN(arg1) || !BN.isBN(arg2)) return NaN + if (typeof arg3 === 'undefined') return arg1.add(arg2) + + // Add three items + if (!BN.isBN(arg3)) return NaN + return arg1.add(arg2).add(arg3) +} + +function sub (arg1, arg2, arg3) { + // Subtract two items + if (!BN.isBN(arg1) || !BN.isBN(arg2)) return NaN + if (typeof arg3 === 'undefined') return arg1.sub(arg2) + + // Subtract three items + if (!BN.isBN(arg3)) return NaN + return arg1.sub(arg2).sub(arg3) +} + +function shrn (argument, shiftBy) { + if (!BN.isBN(argument)) return NaN + if (BN.isBN(shiftBy)) shiftBy = shiftBy.toNumber() + if (typeof shiftBy !== 'number') return NaN + + return argument.shrn(shiftBy) +} + +function isZero (argument) { + if (!BN.isBN(argument)) return false + if (argument.cmp(BN_ZERO) === 0) return true + return false +} + +function lt (subject, argument) { + if (!BN.isBN(argument) || !BN.isBN(subject)) return false + return subject.lt(argument) +} + +function gt (subject, argument) { + if (!BN.isBN(argument) || !BN.isBN(subject)) return false + return subject.gt(argument) +} + +module.exports = { + mul: mul, + div: div, + add: add, + sub: sub, + shrn: shrn, + isZero: isZero, + lt: lt, + gt: gt, + BN_ZERO: BN_ZERO, + BN_ONE: BN_ONE +} diff --git a/break.js b/break.js index 91dd72c..f910bf9 100644 --- a/break.js +++ b/break.js @@ -1,32 +1,39 @@ var utils = require('./utils') +var ext = require('./bn-extensions') // break utxos into the maximum number of 'output' possible module.exports = function broken (utxos, output, feeRate) { - if (!isFinite(utils.uintOrNaN(feeRate))) return {} + if (!isFinite(utils.bnOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes(utxos, []) - var value = utils.uintOrNaN(output.value) + var value = utils.bnOrNaN(output.value) var inAccum = utils.sumOrNaN(utxos) + if (!isFinite(value) || - !isFinite(inAccum)) return { fee: feeRate * bytesAccum } + !isFinite(inAccum)) return { fee: feeRate.mul(bytesAccum) } var outputBytes = utils.outputBytes(output) - var outAccum = 0 + var outAccum = ext.BN_ZERO var outputs = [] while (true) { - var fee = feeRate * (bytesAccum + outputBytes) + // feeRate * (bytesAccum + outputBytes) + var fee = ext.mul(feeRate, ext.add(bytesAccum, outputBytes)) // did we bust? - if (inAccum < (outAccum + fee + value)) { + if (ext.lt(inAccum, ext.add(outAccum, fee, value))) { + var isZero = ext.isZero(outAccum) // premature? - if (outAccum === 0) return { fee: fee } - + if (isZero) { + return { + fee: fee + } + } break } - bytesAccum += outputBytes - outAccum += value + bytesAccum = ext.add(bytesAccum, outputBytes) + outAccum = ext.add(outAccum, value) outputs.push(output) } diff --git a/index.js b/index.js index 19aa484..dd096dd 100644 --- a/index.js +++ b/index.js @@ -4,12 +4,12 @@ var utils = require('./utils') // order by descending value, minus the inputs approximate fee function utxoScore (x, feeRate) { - return x.value - (feeRate * utils.inputBytes(x)) + return x.value.sub((feeRate.mul(utils.inputBytes(x)))) } module.exports = function coinSelect (utxos, outputs, feeRate) { utxos = utxos.concat().sort(function (a, b) { - return utxoScore(b, feeRate) - utxoScore(a, feeRate) + return utxoScore(b, feeRate).sub(utxoScore(a, feeRate)) }) // attempt to use the blackjack strategy first (no change output) diff --git a/package.json b/package.json index e8504a7..e220e60 100644 --- a/package.json +++ b/package.json @@ -46,5 +46,7 @@ "standard": "*", "tape": "^4.5.1" }, - "dependencies": {} + "dependencies": { + "bn.js": "^4.11.8" + } } diff --git a/split.js b/split.js index 4d3ce5d..f2bd436 100644 --- a/split.js +++ b/split.js @@ -1,33 +1,42 @@ var utils = require('./utils') +var BN = require('bn.js') +var ext = require('./bn-extensions') // split utxos between each output, ignores outputs with .value defined module.exports = function split (utxos, outputs, feeRate) { - if (!isFinite(utils.uintOrNaN(feeRate))) return {} + if (!isFinite(utils.bnOrNaN(feeRate))) return {} var bytesAccum = utils.transactionBytes(utxos, outputs) - var fee = feeRate * bytesAccum + var fee = ext.mul(feeRate, bytesAccum) if (outputs.length === 0) return { fee: fee } var inAccum = utils.sumOrNaN(utxos) var outAccum = utils.sumForgiving(outputs) - var remaining = inAccum - outAccum - fee + var remaining = ext.sub(inAccum, outAccum, fee) if (!isFinite(remaining) || remaining < 0) return { fee: fee } var unspecified = outputs.reduce(function (a, x) { return a + !isFinite(x.value) }, 0) - if (remaining === 0 && unspecified === 0) return utils.finalize(utxos, outputs, feeRate) + if (ext.isZero(remaining) && unspecified === 0) return utils.finalize(utxos, outputs, feeRate) - var splitOutputsCount = outputs.reduce(function (a, x) { + // Counts the number of split outputs left + var splitOutputsCount = new BN(outputs.reduce(function (a, x) { return a + !x.value - }, 0) - var splitValue = (remaining / splitOutputsCount) >>> 0 + }, 0)) + + // any number / 0 = infinity (shift right = 0) + var splitValue = ext.shrn(ext.div(remaining, splitOutputsCount), 0) // 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 { fee: fee } + return x.value !== undefined || ext.gt(splitValue, utils.dustThreshold(x, feeRate)) + })) { + return { + fee: fee + } + } // assign splitValue to outputs not user defined outputs = outputs.map(function (x) { diff --git a/stats/index.js b/stats/index.js index ea99dfc..4a17411 100644 --- a/stats/index.js +++ b/stats/index.js @@ -41,7 +41,7 @@ let selectedData = walletType === 'p2pkh' ? pubkeyhashScriptLengthData : scripth let inLengthProbs = selectedData.inLengthPercs let outLengthProbs = {}; -[scripthashScriptLengthData, pubkeyhashScriptLengthData].forEach(({prob, outLength}) => { +[scripthashScriptLengthData, pubkeyhashScriptLengthData].forEach(({ prob, outLength }) => { outLengthProbs[outLength] = prob }) diff --git a/test/_utils.js b/test/_utils.js index 4c3739d..9375893 100644 --- a/test/_utils.js +++ b/test/_utils.js @@ -1,16 +1,26 @@ +var BN = require('bn.js') + function expand (values, indices) { if (indices) { return values.map(function (x, i) { - if (typeof x === 'number') return { i: i, value: x } - - var y = { i: i } + if (BN.isBN(x)) { + return { + i: i, + value: x + } + } + var y = { + i: i + } for (var k in x) y[k] = x[k] return y }) } return values.map(function (x, i) { - return typeof x === 'object' ? x : { value: x } + return (typeof x === 'object' && !BN.isBN(x)) ? x : { + value: x + } }) } diff --git a/test/accumulative.js b/test/accumulative.js index 6f6edd7..5279295 100644 --- a/test/accumulative.js +++ b/test/accumulative.js @@ -9,10 +9,17 @@ fixtures.forEach(function (f) { var outputs = utils.expand(f.outputs) var actual = coinAccum(inputs, outputs, f.feeRate) - t.same(actual, f.expected) + t.same(actual.inputs, f.expected.inputs) + t.same(actual.outputs, f.expected.outputs) + if (f.expected.fee) t.ok(actual.fee.cmp(f.expected.fee) === 0) + else t.ok(actual.fee === f.expected.fee) + if (actual.inputs) { var feedback = coinAccum(actual.inputs, actual.outputs, f.feeRate) - t.same(feedback, f.expected) + t.same(feedback.inputs, f.expected.inputs) + t.same(feedback.outputs, f.expected.outputs) + if (f.expected.fee) t.ok(feedback.fee.cmp(f.expected.fee) === 0) + else t.ok(actual.fee === f.expected.fee) } t.end() diff --git a/test/fixtures/accumulative.js b/test/fixtures/accumulative.js new file mode 100644 index 0000000..02090ed --- /dev/null +++ b/test/fixtures/accumulative.js @@ -0,0 +1,589 @@ +var BN = require('bn.js') + +module.exports = [{ + 'description': '1 output, no change', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('102001') + ], + 'outputs': [ + new BN('100000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('102001') + }], + 'outputs': [{ + 'value': new BN('100000') + }], + 'fee': new BN('2001') + } +}, +{ + 'description': '1 output, change expected', + 'feeRate': new BN('5'), + 'inputs': [ + new BN('106001') + ], + 'outputs': [ + new BN('100000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('106001') + }], + 'outputs': [{ + 'value': new BN('100000') + }, + { + 'value': new BN('4871') + } + ], + 'fee': new BN('1130') + } +}, +{ + 'description': '1 output, change expected, value > 2^32', + 'feeRate': new BN('5'), + 'inputs': [ + new BN('5000000000') + ], + 'outputs': [ + new BN('1') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('5000000000') + }], + 'outputs': [{ + 'value': new BN('1') + }, + { + 'value': new BN('4999998869') + } + ], + 'fee': new BN('1130') + } +}, +{ + 'description': '1 output, sub-optimal inputs (if re-ordered), direct possible', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000'), + new BN('40000'), + new BN('40000') + ], + 'outputs': [ + new BN('7700') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('10000') + }], + 'outputs': [{ + 'value': new BN('7700') + }], + 'fee': new BN('2300') + } +}, +{ + 'description': '1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000'), + new BN('40000'), + new BN('40000') + ], + 'outputs': [ + new BN('6800') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('10000') + }], + 'outputs': [{ + 'value': new BN('6800') + }], + 'fee': new BN('3200') + } +}, +{ + 'description': '1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected', + 'feeRate': new BN('5'), + 'inputs': [ + new BN('10000'), + new BN('40000'), + new BN('40000') + ], + 'outputs': [ + new BN('4700') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('10000') + }], + 'outputs': [{ + 'value': new BN('4700') + }, + { + 'value': new BN('4170') + } + ], + 'fee': new BN('1130') + } +}, +{ + 'description': '1 output, fails, skips (and finishes on) detrimental input', + 'feeRate': new BN('55'), + 'inputs': [{ + 'value': new BN('44000') + }, + { + 'value': new BN('800') + } + ], + 'outputs': [ + new BN('38000') + ], + 'expected': { + 'fee': new BN('18700') + } +}, +{ + 'description': '1 output, optimal inputs, no change', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000') + ], + 'outputs': [ + new BN('7700') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('10000') + }], + 'outputs': [{ + 'value': new BN('7700') + }], + 'fee': new BN('2300') + } +}, +{ + 'description': '1 output, no fee, change expected', + 'feeRate': new BN('0'), + 'inputs': [ + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000') + ], + 'outputs': [ + new BN('28000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('5000') + }, + { + 'i': 1, + 'value': new BN('5000') + }, + { + 'i': 2, + 'value': new BN('5000') + }, + { + 'i': 3, + 'value': new BN('5000') + }, + { + 'i': 4, + 'value': new BN('5000') + }, + { + 'i': 5, + 'value': new BN('5000') + } + ], + 'outputs': [{ + 'value': new BN('28000') + }, + { + 'value': new BN('2000') + } + ], + 'fee': new BN('0') + } +}, +{ + 'description': '1 output, 2 inputs (related), no change', + 'feeRate': new BN('10'), + 'inputs': [{ + 'address': 'a', + 'value': new BN('100000') + }, + { + 'address': 'a', + 'value': new BN('2000') + } + ], + 'outputs': [ + new BN('98000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'address': 'a', + 'value': new BN('100000') + }], + 'outputs': [{ + 'value': new BN('98000') + }], + 'fee': new BN('2000') + } +}, +{ + 'description': 'many outputs, no change', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('30000'), + new BN('12220'), + new BN('10001') + ], + 'outputs': [ + new BN('35000'), + new BN('5000'), + new BN('5000'), + new BN('1000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('30000') + }, + { + 'i': 1, + 'value': new BN('12220') + }, + { + 'i': 2, + 'value': new BN('10001') + } + ], + 'outputs': [{ + 'value': new BN('35000') + }, + { + 'value': new BN('5000') + }, + { + 'value': new BN('5000') + }, + { + 'value': new BN('1000') + } + ], + 'fee': new BN('6221') + } +}, +{ + 'description': 'many outputs, change expected', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('30000'), + new BN('14220'), + new BN('10001') + ], + 'outputs': [ + new BN('35000'), + new BN('5000'), + new BN('5000'), + new BN('1000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('30000') + }, + { + 'i': 1, + 'value': new BN('14220') + }, + { + 'i': 2, + 'value': new BN('10001') + } + ], + 'outputs': [{ + 'value': new BN('35000') + }, + { + 'value': new BN('5000') + }, + { + 'value': new BN('5000') + }, + { + 'value': new BN('1000') + }, + { + 'value': new BN('1981') + } + ], + 'fee': new BN('6240') + } +}, +{ + 'description': 'many outputs, no fee, change expected', + 'feeRate': new BN('0'), + 'inputs': [ + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000') + ], + 'outputs': [ + new BN('28000'), + new BN('1000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('5000') + }, + { + 'i': 1, + 'value': new BN('5000') + }, + { + 'i': 2, + 'value': new BN('5000') + }, + { + 'i': 3, + 'value': new BN('5000') + }, + { + 'i': 4, + 'value': new BN('5000') + }, + { + 'i': 5, + 'value': new BN('5000') + } + ], + 'outputs': [{ + 'value': new BN('28000') + }, + { + 'value': new BN('1000') + }, + { + 'value': new BN('1000') + } + ], + 'fee': new BN('0') + } +}, +{ + 'description': 'no outputs, no change', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('1900') + ], + 'outputs': [], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('1900') + }], + 'outputs': [], + 'fee': new BN('1900') + } +}, +{ + 'description': 'no outputs, change expected', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('20000') + }], + 'outputs': [{ + 'value': new BN('18080') + }], + 'fee': new BN('1920') + } +}, +{ + 'description': 'not enough funds, empty result', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + new BN('40000') + ], + 'expected': { + 'fee': new BN('1920') + } +}, +{ + 'description': 'not enough funds (w/ fee), empty result', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('40000') + ], + 'outputs': [ + new BN('40000') + ], + 'expected': { + 'fee': new BN('1920') + } +}, +{ + 'description': 'not enough funds (no inputs), empty result', + 'feeRate': new BN('10'), + 'inputs': [], + 'outputs': [], + 'expected': { + 'fee': new BN('100') + } +}, +{ + 'description': 'not enough funds (no inputs), empty result (>1KiB)', + 'feeRate': new BN('10'), + 'inputs': [], + 'outputs': [ + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1') + ], + 'expected': { + 'fee': new BN('9960') + } +}, +{ + 'description': '2 outputs, some with missing value (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + new BN('1000'), + {} + ], + 'expected': { + 'fee': new BN('2260') + } +}, +{ + 'description': 'input with float values (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + 20000.5 + ], + 'outputs': [ + new BN('10000'), + new BN('1200') + ], + 'expected': { + 'fee': new BN('2260') + } +}, +{ + 'description': '2 outputs, with float values (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + 10000.25, + 1200.5 + ], + 'expected': { + 'fee': new BN('2260') + } +}, +{ + 'description': '2 outputs, string values (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [{ + 'value': '100' + }, + { + 'value': '204' + } + ], + 'expected': { + 'fee': new BN('2260') + } +}, +{ + 'description': 'inputs and outputs, bad feeRate (NaN)', + 'feeRate': '1', + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + new BN('10000') + ], + 'expected': {} +}, +{ + 'description': 'inputs and outputs, bad feeRate (NaN)', + 'feeRate': 1.5, + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + new BN('10000') + ], + 'expected': {} +} +] diff --git a/test/fixtures/accumulative.json b/test/fixtures/accumulative.json deleted file mode 100644 index b14a989..0000000 --- a/test/fixtures/accumulative.json +++ /dev/null @@ -1,634 +0,0 @@ -[ - { - "description": "1 output, no change", - "feeRate": 10, - "inputs": [ - 102001 - ], - "outputs": [ - 100000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 102001 - } - ], - "outputs": [ - { - "value": 100000 - } - ], - "fee": 2001 - } - }, - { - "description": "1 output, change expected", - "feeRate": 5, - "inputs": [ - 106001 - ], - "outputs": [ - 100000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 106001 - } - ], - "outputs": [ - { - "value": 100000 - }, - { - "value": 4871 - } - ], - "fee": 1130 - } - }, - { - "description": "1 output, change expected, value > 2^32", - "feeRate": 5, - "inputs": [ - 5000000000 - ], - "outputs": [ - 1 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 5000000000 - } - ], - "outputs": [ - { - "value": 1 - }, - { - "value": 4999998869 - } - ], - "fee": 1130 - } - }, - { - "description": "1 output, sub-optimal inputs (if re-ordered), direct possible", - "feeRate": 10, - "inputs": [ - 10000, - 40000, - 40000 - ], - "outputs": [ - 7700 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 10000 - } - ], - "outputs": [ - { - "value": 7700 - } - ], - "fee": 2300 - } - }, - { - "description": "1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee", - "feeRate": 10, - "inputs": [ - 10000, - 40000, - 40000 - ], - "outputs": [ - 6800 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 10000 - } - ], - "outputs": [ - { - "value": 6800 - } - ], - "fee": 3200 - } - }, - { - "description": "1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected", - "feeRate": 5, - "inputs": [ - 10000, - 40000, - 40000 - ], - "outputs": [ - 4700 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 10000 - } - ], - "outputs": [ - { - "value": 4700 - }, - { - "value": 4170 - } - ], - "fee": 1130 - } - }, - { - "description": "1 output, fails, skips (and finishes on) detrimental input", - "feeRate": 55, - "inputs": [ - { - "value": 44000 - }, - { - "value": 800 - } - ], - "outputs": [ - 38000 - ], - "expected": { - "fee": 18700 - } - }, - { - "description": "1 output, optimal inputs, no change", - "feeRate": 10, - "inputs": [ - 10000 - ], - "outputs": [ - 7700 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 10000 - } - ], - "outputs": [ - { - "value": 7700 - } - ], - "fee": 2300 - } - }, - { - "description": "1 output, no fee, change expected", - "feeRate": 0, - "inputs": [ - 5000, - 5000, - 5000, - 5000, - 5000, - 5000 - ], - "outputs": [ - 28000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 5000 - }, - { - "i": 1, - "value": 5000 - }, - { - "i": 2, - "value": 5000 - }, - { - "i": 3, - "value": 5000 - }, - { - "i": 4, - "value": 5000 - }, - { - "i": 5, - "value": 5000 - } - ], - "outputs": [ - { - "value": 28000 - }, - { - "value": 2000 - } - ], - "fee": 0 - } - }, - { - "description": "1 output, 2 inputs (related), no change", - "feeRate": 10, - "inputs": [ - { - "address": "a", - "value": 100000 - }, - { - "address": "a", - "value": 2000 - } - ], - "outputs": [ - 98000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "address": "a", - "value": 100000 - } - ], - "outputs": [ - { - "value": 98000 - } - ], - "fee": 2000 - } - }, - { - "description": "many outputs, no change", - "feeRate": 10, - "inputs": [ - 30000, - 12220, - 10001 - ], - "outputs": [ - 35000, - 5000, - 5000, - 1000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 30000 - }, - { - "i": 1, - "value": 12220 - }, - { - "i": 2, - "value": 10001 - } - ], - "outputs": [ - { - "value": 35000 - }, - { - "value": 5000 - }, - { - "value": 5000 - }, - { - "value": 1000 - } - ], - "fee": 6221 - } - }, - { - "description": "many outputs, change expected", - "feeRate": 10, - "inputs": [ - 30000, - 14220, - 10001 - ], - "outputs": [ - 35000, - 5000, - 5000, - 1000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 30000 - }, - { - "i": 1, - "value": 14220 - }, - { - "i": 2, - "value": 10001 - } - ], - "outputs": [ - { - "value": 35000 - }, - { - "value": 5000 - }, - { - "value": 5000 - }, - { - "value": 1000 - }, - { - "value": 1981 - } - ], - "fee": 6240 - } - }, - { - "description": "many outputs, no fee, change expected", - "feeRate": 0, - "inputs": [ - 5000, - 5000, - 5000, - 5000, - 5000, - 5000 - ], - "outputs": [ - 28000, - 1000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 5000 - }, - { - "i": 1, - "value": 5000 - }, - { - "i": 2, - "value": 5000 - }, - { - "i": 3, - "value": 5000 - }, - { - "i": 4, - "value": 5000 - }, - { - "i": 5, - "value": 5000 - } - ], - "outputs": [ - { - "value": 28000 - }, - { - "value": 1000 - }, - { - "value": 1000 - } - ], - "fee": 0 - } - }, - { - "description": "no outputs, no change", - "feeRate": 10, - "inputs": [ - 1900 - ], - "outputs": [], - "expected": { - "inputs": [ - { - "i": 0, - "value": 1900 - } - ], - "outputs": [], - "fee": 1900 - } - }, - { - "description": "no outputs, change expected", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [], - "expected": { - "inputs": [ - { - "i": 0, - "value": 20000 - } - ], - "outputs": [ - { - "value": 18080 - } - ], - "fee": 1920 - } - }, - { - "description": "not enough funds, empty result", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [ - 40000 - ], - "expected": { - "fee": 1920 - } - }, - { - "description": "not enough funds (w/ fee), empty result", - "feeRate": 10, - "inputs": [ - 40000 - ], - "outputs": [ - 40000 - ], - "expected": { - "fee": 1920 - } - }, - { - "description": "not enough funds (no inputs), empty result", - "feeRate": 10, - "inputs": [], - "outputs": [], - "expected": { - "fee": 100 - } - }, - { - "description": "not enough funds (no inputs), empty result (>1KiB)", - "feeRate": 10, - "inputs": [], - "outputs": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 - ], - "expected": { - "fee": 9960 - } - }, - { - "description": "2 outputs, some with missing value (NaN)", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [ - 1000, - {} - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "input with float values (NaN)", - "feeRate": 10, - "inputs": [ - 20000.5 - ], - "outputs": [ - 10000, - 1200 - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "2 outputs, with float values (NaN)", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [ - 10000.25, - 1200.5 - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "2 outputs, string values (NaN)", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [ - { - "value": "100" - }, - { - "value": "204" - } - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "inputs and outputs, bad feeRate (NaN)", - "feeRate": "1", - "inputs": [ - 20000 - ], - "outputs": [ - 10000 - ], - "expected": {} - }, - { - "description": "inputs and outputs, bad feeRate (NaN)", - "feeRate": 1.5, - "inputs": [ - 20000 - ], - "outputs": [ - 10000 - ], - "expected": {} - } -] diff --git a/test/fixtures/break.js b/test/fixtures/break.js new file mode 100644 index 0000000..cdbc998 --- /dev/null +++ b/test/fixtures/break.js @@ -0,0 +1,274 @@ +var BN = require('bn.js') + +module.exports = [{ + 'description': '1:1, no remainder', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('11920') + ], + 'output': new BN('10000'), + 'expected': { + 'inputs': [{ + 'value': new BN('11920') + }], + 'outputs': [{ + 'value': new BN('10000') + }], + 'fee': new BN('1920') + } +}, +{ + 'description': '1:1', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('12000') + ], + 'output': { + 'address': 'woop', + 'value': new BN('10000') + }, + 'expected': { + 'fee': new BN('2000'), + 'inputs': [{ + 'value': new BN('12000') + }], + 'outputs': [{ + 'address': 'woop', + 'value': new BN('10000') + }] + } +}, +{ + 'description': '1:1, w/ change', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('12000') + ], + 'output': new BN('8000'), + 'expected': { + 'inputs': [{ + 'value': new BN('12000') + }], + 'outputs': [{ + 'value': new BN('8000') + }, + { + 'value': new BN('1740') + } + ], + 'fee': new BN('2260') + } +}, +{ + 'description': '1:4', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('12000') + ], + 'output': new BN('2000'), + 'expected': { + 'inputs': [{ + 'value': new BN('12000') + }], + 'outputs': [{ + 'value': new BN('2000') + }, + { + 'value': new BN('2000') + }, + { + 'value': new BN('2000') + }, + { + 'value': new BN('2000') + } + ], + 'fee': new BN('4000') + } +}, +{ + 'description': '2:5', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('3000'), + new BN('12000') + ], + 'output': new BN('2000'), + 'expected': { + 'inputs': [{ + 'value': new BN('3000') + }, + { + 'value': new BN('12000') + } + ], + 'outputs': [{ + 'value': new BN('2000') + }, + { + 'value': new BN('2000') + }, + { + 'value': new BN('2000') + }, + { + 'value': new BN('2000') + }, + { + 'value': new BN('2000') + } + ], + 'fee': new BN('5000') + } +}, +{ + 'description': '2:5, no fee', + 'feeRate': new BN('0'), + 'inputs': [ + new BN('5000'), + new BN('10000') + ], + 'output': new BN('3000'), + 'expected': { + 'inputs': [{ + 'value': new BN('5000') + }, + { + 'value': new BN('10000') + } + ], + 'outputs': [{ + 'value': new BN('3000') + }, + { + 'value': new BN('3000') + }, + { + 'value': new BN('3000') + }, + { + 'value': new BN('3000') + }, + { + 'value': new BN('3000') + } + ], + 'fee': new BN('0') + } +}, +{ + 'description': '2:2 (+1), w/ change', + 'feeRate': new BN('7'), + 'inputs': [ + new BN('16000') + ], + 'output': new BN('6000'), + 'expected': { + 'inputs': [{ + 'value': new BN('16000') + }], + 'outputs': [{ + 'value': new BN('6000') + }, + { + 'value': new BN('6000') + }, + { + 'value': new BN('2180') + } + ], + 'fee': new BN('1820') + } +}, +{ + 'description': '2:3 (+1), no fee, w/ change', + 'feeRate': new BN('0'), + 'inputs': [ + new BN('5000'), + new BN('10000') + ], + 'output': new BN('4000'), + 'expected': { + 'inputs': [{ + 'value': new BN('5000') + }, + { + 'value': new BN('10000') + } + ], + 'outputs': [{ + 'value': new BN('4000') + }, + { + 'value': new BN('4000') + }, + { + 'value': new BN('4000') + }, + { + 'value': new BN('3000') + } + ], + 'fee': new BN('0') + } +}, +{ + 'description': 'not enough funds', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('41000'), + new BN('1000') + ], + 'output': new BN('40000'), + 'expected': { + 'fee': new BN('3400') + } +}, +{ + 'description': 'no inputs', + 'feeRate': new BN('10'), + 'inputs': [], + 'output': new BN('2000'), + 'expected': { + 'fee': new BN('440') + } +}, +{ + 'description': 'invalid output (NaN)', + 'feeRate': new BN('10'), + 'inputs': [], + 'output': {}, + 'expected': { + 'fee': new BN('100') + } +}, +{ + 'description': 'input with float values (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + 10000.5 + ], + 'output': new BN('5000'), + 'expected': { + 'fee': new BN('1580') + } +}, +{ + 'description': 'inputs and outputs, bad feeRate (NaN)', + 'feeRate': '1', + 'inputs': [ + new BN('20000') + ], + 'output': new BN('10000'), + 'expected': {} +}, +{ + 'description': 'inputs and outputs, bad feeRate (NaN)', + 'feeRate': 1.5, + 'inputs': [ + new BN('20000') + ], + 'output': new BN('10000'), + 'expected': {} +} +] diff --git a/test/fixtures/break.json b/test/fixtures/break.json deleted file mode 100644 index 8977e4d..0000000 --- a/test/fixtures/break.json +++ /dev/null @@ -1,296 +0,0 @@ -[ - { - "description": "1:1, no remainder", - "feeRate": 10, - "inputs": [ - 11920 - ], - "output": 10000, - "expected": { - "inputs": [ - { - "value": 11920 - } - ], - "outputs": [ - { - "value": 10000 - } - ], - "fee": 1920 - } - }, - { - "description": "1:1", - "feeRate": 10, - "inputs": [ - 12000 - ], - "output": { - "address": "woop", - "value": 10000 - }, - "expected": { - "fee": 2000, - "inputs": [ - { - "value": 12000 - } - ], - "outputs": [ - { - "address": "woop", - "value": 10000 - } - ] - } - }, - { - "description": "1:1, w/ change", - "feeRate": 10, - "inputs": [ - 12000 - ], - "output": 8000, - "expected": { - "inputs": [ - { - "value": 12000 - } - ], - "outputs": [ - { - "value": 8000 - }, - { - "value": 1740 - } - ], - "fee": 2260 - } - }, - { - "description": "1:4", - "feeRate": 10, - "inputs": [ - 12000 - ], - "output": 2000, - "expected": { - "inputs": [ - { - "value": 12000 - } - ], - "outputs": [ - { - "value": 2000 - }, - { - "value": 2000 - }, - { - "value": 2000 - }, - { - "value": 2000 - } - ], - "fee": 4000 - } - }, - { - "description": "2:5", - "feeRate": 10, - "inputs": [ - 3000, - 12000 - ], - "output": 2000, - "expected": { - "inputs": [ - { - "value": 3000 - }, - { - "value": 12000 - } - ], - "outputs": [ - { - "value": 2000 - }, - { - "value": 2000 - }, - { - "value": 2000 - }, - { - "value": 2000 - }, - { - "value": 2000 - } - ], - "fee": 5000 - } - }, - { - "description": "2:5, no fee", - "feeRate": 0, - "inputs": [ - 5000, - 10000 - ], - "output": 3000, - "expected": { - "inputs": [ - { - "value": 5000 - }, - { - "value": 10000 - } - ], - "outputs": [ - { - "value": 3000 - }, - { - "value": 3000 - }, - { - "value": 3000 - }, - { - "value": 3000 - }, - { - "value": 3000 - } - ], - "fee": 0 - } - }, - { - "description": "2:2 (+1), w/ change", - "feeRate": 7, - "inputs": [ - 16000 - ], - "output": 6000, - "expected": { - "inputs": [ - { - "value": 16000 - } - ], - "outputs": [ - { - "value": 6000 - }, - { - "value": 6000 - }, - { - "value": 2180 - } - ], - "fee": 1820 - } - }, - { - "description": "2:3 (+1), no fee, w/ change", - "feeRate": 0, - "inputs": [ - 5000, - 10000 - ], - "output": 4000, - "expected": { - "inputs": [ - { - "value": 5000 - }, - { - "value": 10000 - } - ], - "outputs": [ - { - "value": 4000 - }, - { - "value": 4000 - }, - { - "value": 4000 - }, - { - "value": 3000 - } - ], - "fee": 0 - } - }, - { - "description": "not enough funds", - "feeRate": 10, - "inputs": [ - 41000, - 1000 - ], - "output": 40000, - "expected": { - "fee": 3400 - } - }, - { - "description": "no inputs", - "feeRate": 10, - "inputs": [], - "output": 2000, - "expected": { - "fee": 440 - } - }, - { - "description": "invalid output (NaN)", - "feeRate": 10, - "inputs": [], - "output": {}, - "expected": { - "fee": 100 - } - }, - { - "description": "input with float values (NaN)", - "feeRate": 10, - "inputs": [ - 10000.5 - ], - "output": 5000, - "expected": { - "fee": 1580 - } - }, - { - "description": "inputs and outputs, bad feeRate (NaN)", - "feeRate": "1", - "inputs": [ - 20000 - ], - "output": 10000, - "expected": {} - }, - { - "description": "inputs and outputs, bad feeRate (NaN)", - "feeRate": 1.5, - "inputs": [ - 20000 - ], - "output": 10000, - "expected": {} - } -] diff --git a/test/fixtures/index.js b/test/fixtures/index.js new file mode 100644 index 0000000..60e7ab1 --- /dev/null +++ b/test/fixtures/index.js @@ -0,0 +1,548 @@ +var BN = require('bn.js') + +module.exports = [{ + 'description': '1 output, no change', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('102001') + ], + 'outputs': [ + new BN('100000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('102001') + }], + 'outputs': [{ + 'value': new BN('100000') + }], + 'fee': new BN('2001') + } +}, +{ + 'description': '1 output, change expected', + 'feeRate': new BN('5'), + 'inputs': [ + new BN('106001') + ], + 'outputs': [ + new BN('100000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('106001') + }], + 'outputs': [{ + 'value': new BN('100000') + }, + { + 'value': new BN('4871') + } + ], + 'fee': new BN('1130') + } +}, +{ + 'description': '1 output, sub-optimal inputs (if re-ordered), direct possible', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000'), + new BN('40000'), + new BN('40000') + ], + 'outputs': [ + new BN('7700') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('10000') + }], + 'outputs': [{ + 'value': new BN('7700') + }], + 'fee': new BN('2300') + } +}, +{ + 'description': '1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000'), + new BN('40000'), + new BN('40000') + ], + 'outputs': [ + new BN('6800') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('10000') + }], + 'outputs': [{ + 'value': new BN('6800') + }], + 'fee': new BN('3200') + } +}, +{ + 'description': '1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected', + 'feeRate': new BN('5'), + 'inputs': [ + new BN('10000'), + new BN('40000'), + new BN('40000') + ], + 'outputs': [ + new BN('4700') + ], + 'expected': { + 'inputs': [{ + 'i': 1, + 'value': new BN('40000') + }], + 'outputs': [{ + 'value': new BN('4700') + }, + { + 'value': new BN('34170') + } + ], + 'fee': new BN('1130') + } +}, +{ + 'description': '1 output, optimal inputs, no change', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000') + ], + 'outputs': [ + new BN('7700') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('10000') + }], + 'outputs': [{ + 'value': new BN('7700') + }], + 'fee': new BN('2300') + } +}, +{ + 'description': '1 output, no fee, change expected', + 'feeRate': new BN('0'), + 'inputs': [ + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000') + ], + 'outputs': [ + new BN('28000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('5000') + }, + { + 'i': 1, + 'value': new BN('5000') + }, + { + 'i': 2, + 'value': new BN('5000') + }, + { + 'i': 3, + 'value': new BN('5000') + }, + { + 'i': 4, + 'value': new BN('5000') + }, + { + 'i': 5, + 'value': new BN('5000') + } + ], + 'outputs': [{ + 'value': new BN('28000') + }, + { + 'value': new BN('2000') + } + ], + 'fee': new BN('0') + } +}, +{ + 'description': '1 output, 2 inputs (related), no change', + 'feeRate': new BN('10'), + 'inputs': [{ + 'address': 'a', + 'value': new BN('100000') + }, + { + 'address': 'a', + 'value': new BN('2000') + } + ], + 'outputs': [ + new BN('98000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'address': 'a', + 'value': new BN('100000') + }], + 'outputs': [{ + 'value': new BN('98000') + }], + 'fee': new BN('2000') + } +}, +{ + 'description': 'many outputs, no change', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('30000'), + new BN('12220'), + new BN('10001') + ], + 'outputs': [ + new BN('35000'), + new BN('5000'), + new BN('5000'), + new BN('1000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('30000') + }, + { + 'i': 1, + 'value': new BN('12220') + }, + { + 'i': 2, + 'value': new BN('10001') + } + ], + 'outputs': [{ + 'value': new BN('35000') + }, + { + 'value': new BN('5000') + }, + { + 'value': new BN('5000') + }, + { + 'value': new BN('1000') + } + ], + 'fee': new BN('6221') + } +}, +{ + 'description': 'many outputs, change expected', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('30000'), + new BN('14220'), + new BN('10001') + ], + 'outputs': [ + new BN('35000'), + new BN('5000'), + new BN('5000'), + new BN('1000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('30000') + }, + { + 'i': 1, + 'value': new BN('14220') + }, + { + 'i': 2, + 'value': new BN('10001') + } + ], + 'outputs': [{ + 'value': new BN('35000') + }, + { + 'value': new BN('5000') + }, + { + 'value': new BN('5000') + }, + { + 'value': new BN('1000') + }, + { + 'value': new BN('1981') + } + ], + 'fee': new BN('6240') + } +}, +{ + 'description': 'many outputs, no fee, change expected', + 'feeRate': new BN('0'), + 'inputs': [ + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000'), + new BN('5000') + ], + 'outputs': [ + new BN('28000'), + new BN('1000') + ], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('5000') + }, + { + 'i': 1, + 'value': new BN('5000') + }, + { + 'i': 2, + 'value': new BN('5000') + }, + { + 'i': 3, + 'value': new BN('5000') + }, + { + 'i': 4, + 'value': new BN('5000') + }, + { + 'i': 5, + 'value': new BN('5000') + } + ], + 'outputs': [{ + 'value': new BN('28000') + }, + { + 'value': new BN('1000') + }, + { + 'value': new BN('1000') + } + ], + 'fee': new BN('0') + } +}, +{ + 'description': 'no outputs, no change', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('1900') + ], + 'outputs': [], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('1900') + }], + 'outputs': [], + 'fee': new BN('1900') + } +}, +{ + 'description': 'no outputs, change expected', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [], + 'expected': { + 'inputs': [{ + 'i': 0, + 'value': new BN('20000') + }], + 'outputs': [{ + 'value': new BN('18080') + }], + 'fee': new BN('1920') + } +}, +{ + 'description': 'not enough funds, empty result', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + new BN('40000') + ], + 'expected': { + 'fee': new BN('1920') + } +}, +{ + 'description': 'not enough funds (w/ fee), empty result', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('40000') + ], + 'outputs': [ + new BN('40000') + ], + 'expected': { + 'fee': new BN('1920') + } +}, +{ + 'description': 'not enough funds (no inputs), empty result', + 'feeRate': new BN('10'), + 'inputs': [], + 'outputs': [], + 'expected': { + 'fee': new BN('100') + } +}, +{ + 'description': 'not enough funds (no inputs), empty result (>1KiB)', + 'feeRate': new BN('10'), + 'inputs': [], + 'outputs': [ + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1'), + new BN('1') + ], + 'expected': { + 'fee': new BN('9960') + } +}, +{ + 'description': '2 outputs, some with missing value (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + new BN('1000'), + {} + ], + 'expected': { + 'fee': new BN('2260') + } +}, +{ + 'description': 'input with float values (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + 20000.5 + ], + 'outputs': [ + 10000, + 1200 + ], + 'expected': { + 'fee': new BN('2260') + } +}, +{ + 'description': '2 outputs, with float values (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + new BN('10000.25'), + new BN('1200.5') + ], + 'expected': { + 'fee': new BN('2260') + } +}, +{ + 'description': '2 outputs, string values (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [{ + 'value': '100' + }, + { + 'value': '204' + } + ], + 'expected': { + 'fee': new BN('2260') + } +}, +{ + 'description': 'inputs and outputs, bad feeRate (NaN)', + 'feeRate': '1', + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + new BN('10000') + ], + 'expected': {} +}, +{ + 'description': 'inputs and outputs, bad feeRate (NaN)', + 'feeRate': 1.5, + 'inputs': [ + new BN('20000') + ], + 'outputs': [ + new BN('10000') + ], + 'expected': {} +} +] diff --git a/test/fixtures/index.json b/test/fixtures/index.json deleted file mode 100644 index 9886706..0000000 --- a/test/fixtures/index.json +++ /dev/null @@ -1,590 +0,0 @@ -[ - { - "description": "1 output, no change", - "feeRate": 10, - "inputs": [ - 102001 - ], - "outputs": [ - 100000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 102001 - } - ], - "outputs": [ - { - "value": 100000 - } - ], - "fee": 2001 - } - }, - { - "description": "1 output, change expected", - "feeRate": 5, - "inputs": [ - 106001 - ], - "outputs": [ - 100000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 106001 - } - ], - "outputs": [ - { - "value": 100000 - }, - { - "value": 4871 - } - ], - "fee": 1130 - } - }, - { - "description": "1 output, sub-optimal inputs (if re-ordered), direct possible", - "feeRate": 10, - "inputs": [ - 10000, - 40000, - 40000 - ], - "outputs": [ - 7700 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 10000 - } - ], - "outputs": [ - { - "value": 7700 - } - ], - "fee": 2300 - } - }, - { - "description": "1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee", - "feeRate": 10, - "inputs": [ - 10000, - 40000, - 40000 - ], - "outputs": [ - 6800 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 10000 - } - ], - "outputs": [ - { - "value": 6800 - } - ], - "fee": 3200 - } - }, - { - "description": "1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected", - "feeRate": 5, - "inputs": [ - 10000, - 40000, - 40000 - ], - "outputs": [ - 4700 - ], - "expected": { - "inputs": [ - { - "i": 1, - "value": 40000 - } - ], - "outputs": [ - { - "value": 4700 - }, - { - "value": 34170 - } - ], - "fee": 1130 - } - }, - { - "description": "1 output, optimal inputs, no change", - "feeRate": 10, - "inputs": [ - 10000 - ], - "outputs": [ - 7700 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 10000 - } - ], - "outputs": [ - { - "value": 7700 - } - ], - "fee": 2300 - } - }, - { - "description": "1 output, no fee, change expected", - "feeRate": 0, - "inputs": [ - 5000, - 5000, - 5000, - 5000, - 5000, - 5000 - ], - "outputs": [ - 28000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 5000 - }, - { - "i": 1, - "value": 5000 - }, - { - "i": 2, - "value": 5000 - }, - { - "i": 3, - "value": 5000 - }, - { - "i": 4, - "value": 5000 - }, - { - "i": 5, - "value": 5000 - } - ], - "outputs": [ - { - "value": 28000 - }, - { - "value": 2000 - } - ], - "fee": 0 - } - }, - { - "description": "1 output, 2 inputs (related), no change", - "feeRate": 10, - "inputs": [ - { - "address": "a", - "value": 100000 - }, - { - "address": "a", - "value": 2000 - } - ], - "outputs": [ - 98000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "address": "a", - "value": 100000 - } - ], - "outputs": [ - { - "value": 98000 - } - ], - "fee": 2000 - } - }, - { - "description": "many outputs, no change", - "feeRate": 10, - "inputs": [ - 30000, - 12220, - 10001 - ], - "outputs": [ - 35000, - 5000, - 5000, - 1000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 30000 - }, - { - "i": 1, - "value": 12220 - }, - { - "i": 2, - "value": 10001 - } - ], - "outputs": [ - { - "value": 35000 - }, - { - "value": 5000 - }, - { - "value": 5000 - }, - { - "value": 1000 - } - ], - "fee": 6221 - } - }, - { - "description": "many outputs, change expected", - "feeRate": 10, - "inputs": [ - 30000, - 14220, - 10001 - ], - "outputs": [ - 35000, - 5000, - 5000, - 1000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 30000 - }, - { - "i": 1, - "value": 14220 - }, - { - "i": 2, - "value": 10001 - } - ], - "outputs": [ - { - "value": 35000 - }, - { - "value": 5000 - }, - { - "value": 5000 - }, - { - "value": 1000 - }, - { - "value": 1981 - } - ], - "fee": 6240 - } - }, - { - "description": "many outputs, no fee, change expected", - "feeRate": 0, - "inputs": [ - 5000, - 5000, - 5000, - 5000, - 5000, - 5000 - ], - "outputs": [ - 28000, - 1000 - ], - "expected": { - "inputs": [ - { - "i": 0, - "value": 5000 - }, - { - "i": 1, - "value": 5000 - }, - { - "i": 2, - "value": 5000 - }, - { - "i": 3, - "value": 5000 - }, - { - "i": 4, - "value": 5000 - }, - { - "i": 5, - "value": 5000 - } - ], - "outputs": [ - { - "value": 28000 - }, - { - "value": 1000 - }, - { - "value": 1000 - } - ], - "fee": 0 - } - }, - { - "description": "no outputs, no change", - "feeRate": 10, - "inputs": [ - 1900 - ], - "outputs": [], - "expected": { - "inputs": [ - { - "i": 0, - "value": 1900 - } - ], - "outputs": [], - "fee": 1900 - } - }, - { - "description": "no outputs, change expected", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [], - "expected": { - "inputs": [ - { - "i": 0, - "value": 20000 - } - ], - "outputs": [ - { - "value": 18080 - } - ], - "fee": 1920 - } - }, - { - "description": "not enough funds, empty result", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [ - 40000 - ], - "expected": { - "fee": 1920 - } - }, - { - "description": "not enough funds (w/ fee), empty result", - "feeRate": 10, - "inputs": [ - 40000 - ], - "outputs": [ - 40000 - ], - "expected": { - "fee": 1920 - } - }, - { - "description": "not enough funds (no inputs), empty result", - "feeRate": 10, - "inputs": [], - "outputs": [], - "expected": { - "fee": 100 - } - }, - { - "description": "not enough funds (no inputs), empty result (>1KiB)", - "feeRate": 10, - "inputs": [], - "outputs": [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 - ], - "expected": { - "fee": 9960 - } - }, - { - "description": "2 outputs, some with missing value (NaN)", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [ - 1000, - {} - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "input with float values (NaN)", - "feeRate": 10, - "inputs": [ - 20000.5 - ], - "outputs": [ - 10000, - 1200 - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "2 outputs, with float values (NaN)", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [ - 10000.25, - 1200.5 - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "2 outputs, string values (NaN)", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [ - { - "value": "100" - }, - { - "value": "204" - } - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "inputs and outputs, bad feeRate (NaN)", - "feeRate": "1", - "inputs": [ - 20000 - ], - "outputs": [ - 10000 - ], - "expected": {} - }, - { - "description": "inputs and outputs, bad feeRate (NaN)", - "feeRate": 1.5, - "inputs": [ - 20000 - ], - "outputs": [ - 10000 - ], - "expected": {} - } -] - diff --git a/test/fixtures/split.js b/test/fixtures/split.js new file mode 100644 index 0000000..6962ed6 --- /dev/null +++ b/test/fixtures/split.js @@ -0,0 +1,264 @@ +var BN = require('bn.js') + +module.exports = [{ + 'description': '1 to 3', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('18000') + ], + 'outputs': [{}, + {}, + {} + ], + 'expected': { + 'inputs': [{ + 'value': new BN('18000') + }], + 'outputs': [{ + 'value': new BN('5133') + }, + { + 'value': new BN('5133') + }, + { + 'value': new BN('5133') + } + ], + 'fee': new BN('2601') + } +}, +{ + 'description': '5 to 2', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000'), + new BN('10000'), + new BN('10000'), + new BN('10000'), + new BN('10000') + ], + 'outputs': [{}, + {} + ], + 'expected': { + 'inputs': [{ + 'value': new BN('10000') + }, + { + 'value': new BN('10000') + }, + { + 'value': new BN('10000') + }, + { + 'value': new BN('10000') + }, + { + 'value': new BN('10000') + } + ], + 'outputs': [{ + 'value': new BN('20910') + }, + { + 'value': new BN('20910') + } + ], + 'fee': new BN('8180') + } +}, +{ + 'description': '3 to 1', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000'), + new BN('10000'), + new BN('10000') + ], + 'outputs': [{}], + 'expected': { + 'inputs': [{ + 'value': new BN('10000') + }, + { + 'value': new BN('10000') + }, + { + 'value': new BN('10000') + } + ], + 'outputs': [{ + 'value': new BN('25120') + }], + 'fee': new BN('4880') + } +}, +{ + 'description': '3 to 3 (1 output pre-defined)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000'), + new BN('10000'), + new BN('10000') + ], + 'outputs': [{ + 'address': 'foobar', + 'value': new BN('12000') + }, + { + 'address': 'fizzbuzz' + }, + {} + ], + 'expected': { + 'inputs': [{ + 'value': new BN('10000') + }, + { + 'value': new BN('10000') + }, + { + 'value': new BN('10000') + } + ], + 'outputs': [{ + 'address': 'foobar', + 'value': new BN('12000') + }, + { + 'address': 'fizzbuzz', + 'value': new BN('6220') + }, + { + 'value': new BN('6220') + } + ], + 'fee': new BN('5560') + } +}, +{ + 'description': '2 to 0 (no result)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('10000'), + new BN('10000') + ], + 'outputs': [], + 'expected': { + 'fee': new BN('3060') + } +}, +{ + 'description': '0 to 2 (no result)', + 'feeRate': new BN('10'), + 'inputs': [], + 'outputs': [{}, + {} + ], + 'expected': { + 'fee': new BN('780') + } +}, +{ + 'description': '1 to 2, output is dust (no result)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('2000') + ], + 'outputs': [{}], + 'expected': { + 'fee': new BN('1920') + } +}, +{ + 'description': '2 outputs, some with missing value (NaN)', + 'feeRate': new BN('11'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [{ + 'value': new BN('4000') + }, + {} + ], + 'expected': { + 'inputs': [{ + 'value': new BN('20000') + }], + 'outputs': [{ + 'value': new BN('4000') + }, + { + 'value': new BN('13514') + } + ], + 'fee': new BN('2486') + } +}, + +// TODO +{ + 'description': '2 outputs, some with float values (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [{ + 'value': 4000.5 + }, + {} + ], + 'expected': { + 'fee': new BN('2260') + } +}, + +{ + 'description': '2 outputs, string values (NaN)', + 'feeRate': new BN('11'), + 'inputs': [ + new BN('20000') + ], + 'outputs': [{ + 'value': '100' + }, + { + 'value': '204' + } + ], + 'expected': { + 'fee': new BN('2486') + } +}, +{ + 'description': 'input with float values (NaN)', + 'feeRate': new BN('10'), + 'inputs': [ + 20000.5 + ], + 'outputs': [{}, + {} + ], + 'expected': { + 'fee': new BN('2260') + } +}, +{ + 'description': 'inputs and outputs, bad feeRate (NaN)', + 'feeRate': '1', + 'inputs': [ + new BN('20000') + ], + 'outputs': [{}], + 'expected': {} +}, +{ + 'description': 'inputs and outputs, bad feeRate (NaN)', + 'feeRate': 1.5, + 'inputs': [ + new BN('20000') + ], + 'outputs': [{}], + 'expected': {} +} +] diff --git a/test/fixtures/split.json b/test/fixtures/split.json deleted file mode 100644 index 30c1dd2..0000000 --- a/test/fixtures/split.json +++ /dev/null @@ -1,289 +0,0 @@ -[ - { - "description": "1 to 3", - "feeRate": 10, - "inputs": [ - 18000 - ], - "outputs": [ - {}, - {}, - {} - ], - "expected": { - "inputs": [ - { - "value": 18000 - } - ], - "outputs": [ - { - "value": 5133 - }, - { - "value": 5133 - }, - { - "value": 5133 - } - ], - "fee": 2601 - } - }, - { - "description": "5 to 2", - "feeRate": 10, - "inputs": [ - 10000, - 10000, - 10000, - 10000, - 10000 - ], - "outputs": [ - {}, - {} - ], - "expected": { - "inputs": [ - { - "value": 10000 - }, - { - "value": 10000 - }, - { - "value": 10000 - }, - { - "value": 10000 - }, - { - "value": 10000 - } - ], - "outputs": [ - { - "value": 20910 - }, - { - "value": 20910 - } - ], - "fee": 8180 - } - }, - { - "description": "3 to 1", - "feeRate": 10, - "inputs": [ - 10000, - 10000, - 10000 - ], - "outputs": [ - {} - ], - "expected": { - "inputs": [ - { - "value": 10000 - }, - { - "value": 10000 - }, - { - "value": 10000 - } - ], - "outputs": [ - { - "value": 25120 - } - ], - "fee": 4880 - } - }, - { - "description": "3 to 3 (1 output pre-defined)", - "feeRate": 10, - "inputs": [ - 10000, - 10000, - 10000 - ], - "outputs": [ - { - "address": "foobar", - "value": 12000 - }, - { - "address": "fizzbuzz" - }, - {} - ], - "expected": { - "inputs": [ - { - "value": 10000 - }, - { - "value": 10000 - }, - { - "value": 10000 - } - ], - "outputs": [ - { - "address": "foobar", - "value": 12000 - }, - { - "address": "fizzbuzz", - "value": 6220 - }, - { - "value": 6220 - } - ], - "fee": 5560 - } - }, - { - "description": "2 to 0 (no result)", - "feeRate": 10, - "inputs": [ - 10000, - 10000 - ], - "outputs": [], - "expected": { - "fee": 3060 - } - }, - { - "description": "0 to 2 (no result)", - "feeRate": 10, - "inputs": [], - "outputs": [ - {}, - {} - ], - "expected": { - "fee": 780 - } - }, - { - "description": "1 to 2, output is dust (no result)", - "feeRate": 10, - "inputs": [ - 2000 - ], - "outputs": [ - {} - ], - "expected": { - "fee": 1920 - } - }, - { - "description": "2 outputs, some with missing value (NaN)", - "feeRate": 11, - "inputs": [ - 20000 - ], - "outputs": [ - { - "value": 4000 - }, - {} - ], - "expected": { - "inputs": [ - { - "value": 20000 - } - ], - "outputs": [ - { - "value": 4000 - }, - { - "value": 13514 - } - ], - "fee": 2486 - } - }, - { - "description": "2 outputs, some with float values (NaN)", - "feeRate": 10, - "inputs": [ - 20000 - ], - "outputs": [ - { - "value": 4000.5 - }, - {} - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "2 outputs, string values (NaN)", - "feeRate": 11, - "inputs": [ - 20000 - ], - "outputs": [ - { - "value": "100" - }, - { - "value": "204" - } - ], - "expected": { - "fee": 2486 - } - }, - { - "description": "input with float values (NaN)", - "feeRate": 10, - "inputs": [ - 20000.5 - ], - "outputs": [ - {}, - {} - ], - "expected": { - "fee": 2260 - } - }, - { - "description": "inputs and outputs, bad feeRate (NaN)", - "feeRate": "1", - "inputs": [ - 20000 - ], - "outputs": [ - {} - ], - "expected": {} - }, - { - "description": "inputs and outputs, bad feeRate (NaN)", - "feeRate": 1.5, - "inputs": [ - 20000 - ], - "outputs": [ - {} - ], - "expected": {} - } -] diff --git a/test/index.js b/test/index.js index 3554363..9b84948 100644 --- a/test/index.js +++ b/test/index.js @@ -6,7 +6,9 @@ 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) t.same(actual, f.expected) diff --git a/test/utils.js b/test/utils.js index 57ce34f..5428b33 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,18 +1,19 @@ var tape = require('tape') var utils = require('../utils') +var BN = require('bn.js') +var ext = require('../bn-extensions') tape('utils', function (t) { - t.test('uintOrNaN', function (t) { + t.test('bnOrNaN', function (t) { t.plan(8) - - t.equal(utils.uintOrNaN(1), 1) - t.equal(isNaN(utils.uintOrNaN('')), true) - t.equal(isNaN(utils.uintOrNaN(Infinity)), true) - t.equal(isNaN(utils.uintOrNaN(NaN)), true) - t.equal(isNaN(utils.uintOrNaN('1')), true) - t.equal(isNaN(utils.uintOrNaN('1.1')), true) - t.equal(isNaN(utils.uintOrNaN(1.1)), true) - t.equal(isNaN(utils.uintOrNaN(-1)), true) + t.ok(utils.bnOrNaN(new BN(1)).cmp(ext.BN_ONE) === 0) + t.equal(isNaN(utils.bnOrNaN('')), true) + t.equal(isNaN(utils.bnOrNaN(Infinity)), true) + t.equal(isNaN(utils.bnOrNaN(NaN)), true) + t.equal(isNaN(utils.bnOrNaN('1')), true) + t.equal(isNaN(utils.bnOrNaN('1.1')), true) + t.equal(isNaN(utils.bnOrNaN(1.1)), true) + t.equal(isNaN(utils.bnOrNaN(-1)), true) }) t.end() diff --git a/utils.js b/utils.js index 4e5ca31..589c0ca 100644 --- a/utils.js +++ b/utils.js @@ -1,16 +1,19 @@ +var BN = require('bn.js') +var ext = require('./bn-extensions') + // baseline estimates, used to improve performance -var TX_BASE_SIZE = 10 +var TX_BASE_SIZE = new BN('10') var TX_INPUT_SIZE = { - LEGACY: 148, - P2SH: 92, - BECH32: 69 + LEGACY: new BN('148'), + P2SH: new BN('92'), + BECH32: new BN('69') } var TX_OUTPUT_SIZE = { - LEGACY: 34, - P2SH: 32, - BECH32: 31 + LEGACY: new BN('34'), + P2SH: new BN('32'), + BECH32: new BN('31') } function inputBytes (input) { @@ -23,45 +26,54 @@ function outputBytes (output) { function dustThreshold (output, feeRate) { /* ... classify the output for input estimate */ - return inputBytes({}) * feeRate + return ext.mul(inputBytes({}), feeRate) } function transactionBytes (inputs, outputs) { - return TX_BASE_SIZE + - inputs.reduce(function (a, x) { return a + inputBytes(x) }, 0) + - outputs.reduce(function (a, x) { return a + outputBytes(x) }, 0) + return TX_BASE_SIZE + .add(inputs.reduce(function (a, x) { + return a.add(inputBytes(x)) + }, ext.BN_ZERO)) + .add(outputs.reduce(function (a, x) { + return a.add(outputBytes(x)) + }, ext.BN_ZERO)) } -function uintOrNaN (v) { - if (typeof v !== 'number') return NaN +function bnOrNaN (v) { if (!isFinite(v)) return NaN - if (Math.floor(v) !== v) return NaN - if (v < 0) return NaN + if (!BN.isBN(v)) return NaN + if (v.isNeg()) return NaN return v } function sumForgiving (range) { - return range.reduce(function (a, x) { return a + (isFinite(x.value) ? x.value : 0) }, 0) + return range.reduce(function (a, x) { + var valueOrZero = BN.isBN(x.value) ? x.value : ext.BN_ZERO + return ext.add(a, valueOrZero) + }, + ext.BN_ZERO) } function sumOrNaN (range) { - return range.reduce(function (a, x) { return a + uintOrNaN(x.value) }, 0) + return range.reduce(function (a, x) { + return ext.add(a, bnOrNaN(x.value)) + }, ext.BN_ZERO) } var BLANK_OUTPUT = outputBytes({}) function finalize (inputs, outputs, feeRate) { var bytesAccum = transactionBytes(inputs, outputs) - var feeAfterExtraOutput = feeRate * (bytesAccum + BLANK_OUTPUT) - var remainderAfterExtraOutput = sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput) + var feeAfterExtraOutput = ext.mul(feeRate, ext.add(bytesAccum, BLANK_OUTPUT)) + var remainderAfterExtraOutput = ext.sub(sumOrNaN(inputs), ext.add(sumOrNaN(outputs), feeAfterExtraOutput)) // is it worth a change output? - if (remainderAfterExtraOutput > dustThreshold({}, feeRate)) { + if (ext.gt(remainderAfterExtraOutput, dustThreshold({}, feeRate))) { outputs = outputs.concat({ value: remainderAfterExtraOutput }) } - var fee = sumOrNaN(inputs) - sumOrNaN(outputs) - if (!isFinite(fee)) return { fee: feeRate * bytesAccum } + var fee = ext.sub(sumOrNaN(inputs), sumOrNaN(outputs)) + if (!isFinite(fee)) return { fee: ext.mul(feeRate, bytesAccum) } return { inputs: inputs, @@ -78,5 +90,5 @@ module.exports = { sumOrNaN: sumOrNaN, sumForgiving: sumForgiving, transactionBytes: transactionBytes, - uintOrNaN: uintOrNaN + bnOrNaN: bnOrNaN } From 25bac3b13b0a02bcddf5373ac2e5fd72d305e2c8 Mon Sep 17 00:00:00 2001 From: Peter Jihoon Kim Date: Fri, 19 Apr 2019 17:02:22 -0700 Subject: [PATCH 2/3] Minor refactoring --- accumulative.js | 17 +++------- blackjack.js | 17 +++------- bn-extensions.js | 81 +++++++++++++++++++++++--------------------- break.js | 15 +++----- index.js | 5 +-- split.js | 14 +++----- stats/index.js | 2 +- test/_utils.js | 15 ++------ test/accumulative.js | 4 +-- test/utils.js | 18 +++++----- utils.js | 31 ++++++++--------- 11 files changed, 95 insertions(+), 124 deletions(-) diff --git a/accumulative.js b/accumulative.js index 3fc0537..c29222a 100644 --- a/accumulative.js +++ b/accumulative.js @@ -4,7 +4,7 @@ var ext = require('./bn-extensions') // add inputs until we reach or surpass the target value (or deplete) // worst-case: O(n) module.exports = function accumulative (utxos, outputs, feeRate) { - if (!isFinite(utils.bnOrNaN(feeRate))) return {} + if (!utils.uintOrNull(feeRate)) return {} var bytesAccum = utils.transactionBytes([], outputs) var inAccum = ext.BN_ZERO @@ -15,17 +15,12 @@ module.exports = function accumulative (utxos, outputs, feeRate) { var utxo = utxos[i] var utxoBytes = utils.inputBytes(utxo) var utxoFee = ext.mul(feeRate, utxoBytes) - var utxoValue = utils.bnOrNaN(utxo.value) + var utxoValue = utils.uintOrNull(utxo.value) // skip detrimental input - var feeIsMoreThanValue = ext.gt(utxoFee, utxoValue) - // utxoFee > utxoValue - if (feeIsMoreThanValue) { + if (ext.gt(utxoFee, utxoValue)) { if (i === utxos.length - 1) { - var bytesSum = ext.add(bytesAccum, utxoBytes) - return { - fee: ext.mul(feeRate, bytesSum) - } + return { fee: ext.mul(feeRate, ext.add(bytesAccum, utxoBytes)) } } continue } @@ -42,7 +37,5 @@ module.exports = function accumulative (utxos, outputs, feeRate) { return utils.finalize(inputs, outputs, feeRate) } - return { - fee: feeRate.mul(bytesAccum) - } + return { fee: ext.mul(feeRate, bytesAccum) } } diff --git a/blackjack.js b/blackjack.js index 60fdca2..ca962bf 100644 --- a/blackjack.js +++ b/blackjack.js @@ -4,7 +4,7 @@ var ext = require('./bn-extensions') // 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) { - if (!isFinite(utils.bnOrNaN(feeRate))) return {} + if (!utils.uintOrNull(feeRate)) return {} var bytesAccum = utils.transactionBytes([], outputs) @@ -17,26 +17,19 @@ module.exports = function blackjack (utxos, outputs, feeRate) { var input = utxos[i] var inputBytes = utils.inputBytes(input) var fee = ext.mul(feeRate, ext.add(bytesAccum, inputBytes)) - var inputValue = utils.bnOrNaN(input.value) + var inputValue = utils.uintOrNull(input.value) // would it waste value? - var totalInputs = ext.add(inAccum, inputValue) - var outputsAndFee = ext.add(outAccum, fee) - var totalOutputs = ext.add(outputsAndFee, threshold) - var inputsAreGreaterThanOutputs = ext.gt(totalInputs, totalOutputs) - - if (inputsAreGreaterThanOutputs) continue + if (ext.gt(ext.add(inAccum, inputValue), ext.add(outAccum, fee, threshold))) continue bytesAccum = ext.add(bytesAccum, inputBytes) inAccum = ext.add(inAccum, inputValue) inputs.push(input) // go again? - if (ext.lt(inAccum, outputsAndFee)) continue + if (ext.lt(inAccum, ext.add(outAccum, fee))) continue return utils.finalize(inputs, outputs, feeRate) } - return { - fee: feeRate.mul(bytesAccum) - } + return { fee: ext.mul(feeRate, bytesAccum) } } diff --git a/bn-extensions.js b/bn-extensions.js index 83fb4d7..9e6572e 100644 --- a/bn-extensions.js +++ b/bn-extensions.js @@ -1,63 +1,68 @@ var BN = require('bn.js') +var slice = Array.prototype.slice var BN_ZERO = new BN(0) var BN_ONE = new BN(1) -function mul (multiplicand, multiplier) { - if (!BN.isBN(multiplicand) || !BN.isBN(multiplier)) return NaN - - return multiplicand.mul(multiplier) +function add () { + var args = slice.call(arguments) + return args.reduce(_add) } -function div (dividend, divisor) { - if (!BN.isBN(dividend) || !BN.isBN(divisor)) return NaN - if (divisor.cmp(BN_ZERO) === 0) return Infinity +function _add (a, b) { + if (!BN.isBN(a) || !BN.isBN(b)) return null + return a.add(b) +} - return dividend.div(divisor) +function sub () { + var args = slice.call(arguments) + return args.reduce(_sub) } -function add (arg1, arg2, arg3) { - // Add two items - if (!BN.isBN(arg1) || !BN.isBN(arg2)) return NaN - if (typeof arg3 === 'undefined') return arg1.add(arg2) +function _sub (a, b) { + if (!BN.isBN(a) || !BN.isBN(b)) return null + return a.sub(b) +} - // Add three items - if (!BN.isBN(arg3)) return NaN - return arg1.add(arg2).add(arg3) +function mul () { + var args = slice.call(arguments) + return args.reduce(_mul) } -function sub (arg1, arg2, arg3) { - // Subtract two items - if (!BN.isBN(arg1) || !BN.isBN(arg2)) return NaN - if (typeof arg3 === 'undefined') return arg1.sub(arg2) +function _mul (a, b) { + if (!BN.isBN(a) || !BN.isBN(b)) return null + return a.mul(b) +} - // Subtract three items - if (!BN.isBN(arg3)) return NaN - return arg1.sub(arg2).sub(arg3) +function div () { + var args = slice.call(arguments) + return args.reduce(_div) } -function shrn (argument, shiftBy) { - if (!BN.isBN(argument)) return NaN - if (BN.isBN(shiftBy)) shiftBy = shiftBy.toNumber() - if (typeof shiftBy !== 'number') return NaN +function _div (a, b) { + if (!BN.isBN(a) || !BN.isBN(b)) return null + if (b.isZero()) return null + return a.div(b) +} - return argument.shrn(shiftBy) +function isZero (v) { + if (!BN.isBN(v)) return false + return v.isZero() } -function isZero (argument) { - if (!BN.isBN(argument)) return false - if (argument.cmp(BN_ZERO) === 0) return true - return false +function eq (a, b) { + if (!BN.isBN(a) || !BN.isBN(b)) return false + return a.eq(b) } -function lt (subject, argument) { - if (!BN.isBN(argument) || !BN.isBN(subject)) return false - return subject.lt(argument) +function lt (a, b) { + if (!BN.isBN(a) || !BN.isBN(b)) return false + return a.lt(b) } -function gt (subject, argument) { - if (!BN.isBN(argument) || !BN.isBN(subject)) return false - return subject.gt(argument) +function gt (a, b) { + if (!BN.isBN(a) || !BN.isBN(b)) return false + return a.gt(b) } module.exports = { @@ -65,8 +70,8 @@ module.exports = { div: div, add: add, sub: sub, - shrn: shrn, isZero: isZero, + eq: eq, lt: lt, gt: gt, BN_ZERO: BN_ZERO, diff --git a/break.js b/break.js index f910bf9..bbc565c 100644 --- a/break.js +++ b/break.js @@ -3,32 +3,25 @@ var ext = require('./bn-extensions') // break utxos into the maximum number of 'output' possible module.exports = function broken (utxos, output, feeRate) { - if (!isFinite(utils.bnOrNaN(feeRate))) return {} + if (!utils.uintOrNull(feeRate)) return {} var bytesAccum = utils.transactionBytes(utxos, []) - var value = utils.bnOrNaN(output.value) + var value = utils.uintOrNull(output.value) var inAccum = utils.sumOrNaN(utxos) - if (!isFinite(value) || - !isFinite(inAccum)) return { fee: feeRate.mul(bytesAccum) } + if (!value || !inAccum) return { fee: ext.mul(feeRate, bytesAccum) } var outputBytes = utils.outputBytes(output) var outAccum = ext.BN_ZERO var outputs = [] while (true) { - // feeRate * (bytesAccum + outputBytes) var fee = ext.mul(feeRate, ext.add(bytesAccum, outputBytes)) // did we bust? if (ext.lt(inAccum, ext.add(outAccum, fee, value))) { - var isZero = ext.isZero(outAccum) // premature? - if (isZero) { - return { - fee: fee - } - } + if (ext.isZero(outAccum)) return { fee: fee } break } diff --git a/index.js b/index.js index dd096dd..9439ed0 100644 --- a/index.js +++ b/index.js @@ -1,15 +1,16 @@ var accumulative = require('./accumulative') var blackjack = require('./blackjack') var utils = require('./utils') +var ext = require('./bn-extensions') // order by descending value, minus the inputs approximate fee function utxoScore (x, feeRate) { - return x.value.sub((feeRate.mul(utils.inputBytes(x)))) + return ext.sub(x.value, ext.mul(feeRate, utils.inputBytes(x))) } module.exports = function coinSelect (utxos, outputs, feeRate) { utxos = utxos.concat().sort(function (a, b) { - return utxoScore(b, feeRate).sub(utxoScore(a, feeRate)) + return ext.sub(utxoScore(b, feeRate), utxoScore(a, feeRate)) }) // attempt to use the blackjack strategy first (no change output) diff --git a/split.js b/split.js index f2bd436..baf53f8 100644 --- a/split.js +++ b/split.js @@ -4,7 +4,7 @@ var ext = require('./bn-extensions') // split utxos between each output, ignores outputs with .value defined module.exports = function split (utxos, outputs, feeRate) { - if (!isFinite(utils.bnOrNaN(feeRate))) return {} + if (!utils.uintOrNull(feeRate)) return {} var bytesAccum = utils.transactionBytes(utxos, outputs) var fee = ext.mul(feeRate, bytesAccum) @@ -13,10 +13,10 @@ module.exports = function split (utxos, outputs, feeRate) { var inAccum = utils.sumOrNaN(utxos) var outAccum = utils.sumForgiving(outputs) var remaining = ext.sub(inAccum, outAccum, fee) - if (!isFinite(remaining) || remaining < 0) return { fee: fee } + if (!remaining || remaining < 0) return { fee: fee } var unspecified = outputs.reduce(function (a, x) { - return a + !isFinite(x.value) + return a + !x.value }, 0) if (ext.isZero(remaining) && unspecified === 0) return utils.finalize(utxos, outputs, feeRate) @@ -27,16 +27,12 @@ module.exports = function split (utxos, outputs, feeRate) { }, 0)) // any number / 0 = infinity (shift right = 0) - var splitValue = ext.shrn(ext.div(remaining, splitOutputsCount), 0) + var splitValue = ext.div(remaining, splitOutputsCount) // ensure every output is either user defined, or over the threshold if (!outputs.every(function (x) { return x.value !== undefined || ext.gt(splitValue, utils.dustThreshold(x, feeRate)) - })) { - return { - fee: fee - } - } + })) return { fee: fee } // assign splitValue to outputs not user defined outputs = outputs.map(function (x) { diff --git a/stats/index.js b/stats/index.js index 4a17411..ea99dfc 100644 --- a/stats/index.js +++ b/stats/index.js @@ -41,7 +41,7 @@ let selectedData = walletType === 'p2pkh' ? pubkeyhashScriptLengthData : scripth let inLengthProbs = selectedData.inLengthPercs let outLengthProbs = {}; -[scripthashScriptLengthData, pubkeyhashScriptLengthData].forEach(({ prob, outLength }) => { +[scripthashScriptLengthData, pubkeyhashScriptLengthData].forEach(({prob, outLength}) => { outLengthProbs[outLength] = prob }) diff --git a/test/_utils.js b/test/_utils.js index 9375893..debad54 100644 --- a/test/_utils.js +++ b/test/_utils.js @@ -3,24 +3,15 @@ var BN = require('bn.js') function expand (values, indices) { if (indices) { return values.map(function (x, i) { - if (BN.isBN(x)) { - return { - i: i, - value: x - } - } - var y = { - i: i - } + if (BN.isBN(x)) { return { i: i, value: x } } + var y = { i: i } for (var k in x) y[k] = x[k] return y }) } return values.map(function (x, i) { - return (typeof x === 'object' && !BN.isBN(x)) ? x : { - value: x - } + return (typeof x === 'object' && !BN.isBN(x)) ? x : { value: x } }) } diff --git a/test/accumulative.js b/test/accumulative.js index 5279295..d4940c6 100644 --- a/test/accumulative.js +++ b/test/accumulative.js @@ -11,14 +11,14 @@ fixtures.forEach(function (f) { t.same(actual.inputs, f.expected.inputs) t.same(actual.outputs, f.expected.outputs) - if (f.expected.fee) t.ok(actual.fee.cmp(f.expected.fee) === 0) + if (f.expected.fee) t.ok(actual.fee.eq(f.expected.fee)) else t.ok(actual.fee === f.expected.fee) if (actual.inputs) { var feedback = coinAccum(actual.inputs, actual.outputs, f.feeRate) t.same(feedback.inputs, f.expected.inputs) t.same(feedback.outputs, f.expected.outputs) - if (f.expected.fee) t.ok(feedback.fee.cmp(f.expected.fee) === 0) + if (f.expected.fee) t.ok(feedback.fee.eq(f.expected.fee)) else t.ok(actual.fee === f.expected.fee) } diff --git a/test/utils.js b/test/utils.js index 5428b33..1c7b0c6 100644 --- a/test/utils.js +++ b/test/utils.js @@ -4,16 +4,16 @@ var BN = require('bn.js') var ext = require('../bn-extensions') tape('utils', function (t) { - t.test('bnOrNaN', function (t) { + t.test('uintOrNull', function (t) { t.plan(8) - t.ok(utils.bnOrNaN(new BN(1)).cmp(ext.BN_ONE) === 0) - t.equal(isNaN(utils.bnOrNaN('')), true) - t.equal(isNaN(utils.bnOrNaN(Infinity)), true) - t.equal(isNaN(utils.bnOrNaN(NaN)), true) - t.equal(isNaN(utils.bnOrNaN('1')), true) - t.equal(isNaN(utils.bnOrNaN('1.1')), true) - t.equal(isNaN(utils.bnOrNaN(1.1)), true) - t.equal(isNaN(utils.bnOrNaN(-1)), true) + t.ok(utils.uintOrNull(new BN(1)).cmp(ext.BN_ONE) === 0) + t.equal(!utils.uintOrNull(''), true) + t.equal(!utils.uintOrNull(Infinity), true) + t.equal(!utils.uintOrNull(NaN), true) + t.equal(!utils.uintOrNull('1'), true) + t.equal(!utils.uintOrNull('1.1'), true) + t.equal(!utils.uintOrNull(1.1), true) + t.equal(!utils.uintOrNull(-1), true) }) t.end() diff --git a/utils.js b/utils.js index 589c0ca..50c31bf 100644 --- a/utils.js +++ b/utils.js @@ -2,18 +2,18 @@ var BN = require('bn.js') var ext = require('./bn-extensions') // baseline estimates, used to improve performance -var TX_BASE_SIZE = new BN('10') +var TX_BASE_SIZE = new BN(10) var TX_INPUT_SIZE = { - LEGACY: new BN('148'), - P2SH: new BN('92'), - BECH32: new BN('69') + LEGACY: new BN(148), + P2SH: new BN(92), + BECH32: new BN(69) } var TX_OUTPUT_SIZE = { - LEGACY: new BN('34'), - P2SH: new BN('32'), - BECH32: new BN('31') + LEGACY: new BN(34), + P2SH: new BN(32), + BECH32: new BN(31) } function inputBytes (input) { @@ -32,17 +32,16 @@ function dustThreshold (output, feeRate) { function transactionBytes (inputs, outputs) { return TX_BASE_SIZE .add(inputs.reduce(function (a, x) { - return a.add(inputBytes(x)) + return ext.add(a, inputBytes(x)) }, ext.BN_ZERO)) .add(outputs.reduce(function (a, x) { - return a.add(outputBytes(x)) + return ext.add(a, outputBytes(x)) }, ext.BN_ZERO)) } -function bnOrNaN (v) { - if (!isFinite(v)) return NaN - if (!BN.isBN(v)) return NaN - if (v.isNeg()) return NaN +function uintOrNull (v) { + if (!BN.isBN(v)) return null + if (v.isNeg()) return null return v } @@ -56,7 +55,7 @@ function sumForgiving (range) { function sumOrNaN (range) { return range.reduce(function (a, x) { - return ext.add(a, bnOrNaN(x.value)) + return ext.add(a, uintOrNull(x.value)) }, ext.BN_ZERO) } @@ -73,7 +72,7 @@ function finalize (inputs, outputs, feeRate) { } var fee = ext.sub(sumOrNaN(inputs), sumOrNaN(outputs)) - if (!isFinite(fee)) return { fee: ext.mul(feeRate, bytesAccum) } + if (!fee) return { fee: ext.mul(feeRate, bytesAccum) } return { inputs: inputs, @@ -90,5 +89,5 @@ module.exports = { sumOrNaN: sumOrNaN, sumForgiving: sumForgiving, transactionBytes: transactionBytes, - bnOrNaN: bnOrNaN + uintOrNull: uintOrNull } From 00f1c47e813c7d408927c21d888036121e47bf49 Mon Sep 17 00:00:00 2001 From: Peter Jihoon Kim Date: Fri, 19 Apr 2019 17:05:37 -0700 Subject: [PATCH 3/3] Clean up fixtures --- test/fixtures/accumulative.js | 776 +++++++++++++++++----------------- test/fixtures/break.js | 304 ++++++------- test/fixtures/index.js | 722 +++++++++++++++---------------- test/fixtures/split.js | 278 ++++++------ 4 files changed, 1040 insertions(+), 1040 deletions(-) diff --git a/test/fixtures/accumulative.js b/test/fixtures/accumulative.js index 02090ed..e194331 100644 --- a/test/fixtures/accumulative.js +++ b/test/fixtures/accumulative.js @@ -1,589 +1,589 @@ var BN = require('bn.js') module.exports = [{ - 'description': '1 output, no change', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('102001') - ], - 'outputs': [ - new BN('100000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('102001') + description: '1 output, no change', + feeRate: new BN(10), + inputs: [ + new BN(102001) + ], + outputs: [ + new BN(100000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(102001) }], - 'outputs': [{ - 'value': new BN('100000') + outputs: [{ + value: new BN(100000) }], - 'fee': new BN('2001') + fee: new BN(2001) } }, { - 'description': '1 output, change expected', - 'feeRate': new BN('5'), - 'inputs': [ - new BN('106001') - ], - 'outputs': [ - new BN('100000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('106001') + description: '1 output, change expected', + feeRate: new BN(5), + inputs: [ + new BN(106001) + ], + outputs: [ + new BN(100000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(106001) }], - 'outputs': [{ - 'value': new BN('100000') + outputs: [{ + value: new BN(100000) }, { - 'value': new BN('4871') + value: new BN(4871) } ], - 'fee': new BN('1130') + fee: new BN(1130) } }, { - 'description': '1 output, change expected, value > 2^32', - 'feeRate': new BN('5'), - 'inputs': [ - new BN('5000000000') - ], - 'outputs': [ - new BN('1') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('5000000000') + description: '1 output, change expected, value > 2^32', + feeRate: new BN(5), + inputs: [ + new BN(5000000000) + ], + outputs: [ + new BN(1) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(5000000000) }], - 'outputs': [{ - 'value': new BN('1') + outputs: [{ + value: new BN(1) }, { - 'value': new BN('4999998869') + value: new BN(4999998869) } ], - 'fee': new BN('1130') + fee: new BN(1130) } }, { - 'description': '1 output, sub-optimal inputs (if re-ordered), direct possible', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000'), - new BN('40000'), - new BN('40000') - ], - 'outputs': [ - new BN('7700') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('10000') + description: '1 output, sub-optimal inputs (if re-ordered), direct possible', + feeRate: new BN(10), + inputs: [ + new BN(10000), + new BN(40000), + new BN(40000) + ], + outputs: [ + new BN(7700) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(10000) }], - 'outputs': [{ - 'value': new BN('7700') + outputs: [{ + value: new BN(7700) }], - 'fee': new BN('2300') + fee: new BN(2300) } }, { - 'description': '1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000'), - new BN('40000'), - new BN('40000') - ], - 'outputs': [ - new BN('6800') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('10000') + description: '1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee', + feeRate: new BN(10), + inputs: [ + new BN(10000), + new BN(40000), + new BN(40000) + ], + outputs: [ + new BN(6800) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(10000) }], - 'outputs': [{ - 'value': new BN('6800') + outputs: [{ + value: new BN(6800) }], - 'fee': new BN('3200') + fee: new BN(3200) } }, { - 'description': '1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected', - 'feeRate': new BN('5'), - 'inputs': [ - new BN('10000'), - new BN('40000'), - new BN('40000') - ], - 'outputs': [ - new BN('4700') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('10000') + description: '1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected', + feeRate: new BN(5), + inputs: [ + new BN(10000), + new BN(40000), + new BN(40000) + ], + outputs: [ + new BN(4700) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(10000) }], - 'outputs': [{ - 'value': new BN('4700') + outputs: [{ + value: new BN(4700) }, { - 'value': new BN('4170') + value: new BN(4170) } ], - 'fee': new BN('1130') + fee: new BN(1130) } }, { - 'description': '1 output, fails, skips (and finishes on) detrimental input', - 'feeRate': new BN('55'), - 'inputs': [{ - 'value': new BN('44000') + description: '1 output, fails, skips (and finishes on) detrimental input', + feeRate: new BN(55), + inputs: [{ + value: new BN(44000) }, { - 'value': new BN('800') + value: new BN(800) } ], - 'outputs': [ - new BN('38000') + outputs: [ + new BN(38000) ], - 'expected': { - 'fee': new BN('18700') + expected: { + fee: new BN(18700) } }, { - 'description': '1 output, optimal inputs, no change', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000') - ], - 'outputs': [ - new BN('7700') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('10000') + description: '1 output, optimal inputs, no change', + feeRate: new BN(10), + inputs: [ + new BN(10000) + ], + outputs: [ + new BN(7700) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(10000) }], - 'outputs': [{ - 'value': new BN('7700') + outputs: [{ + value: new BN(7700) }], - 'fee': new BN('2300') + fee: new BN(2300) } }, { - 'description': '1 output, no fee, change expected', - 'feeRate': new BN('0'), - 'inputs': [ - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000') - ], - 'outputs': [ - new BN('28000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('5000') + description: '1 output, no fee, change expected', + feeRate: new BN(0), + inputs: [ + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000) + ], + outputs: [ + new BN(28000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(5000) }, { - 'i': 1, - 'value': new BN('5000') + i: 1, + value: new BN(5000) }, { - 'i': 2, - 'value': new BN('5000') + i: 2, + value: new BN(5000) }, { - 'i': 3, - 'value': new BN('5000') + i: 3, + value: new BN(5000) }, { - 'i': 4, - 'value': new BN('5000') + i: 4, + value: new BN(5000) }, { - 'i': 5, - 'value': new BN('5000') + i: 5, + value: new BN(5000) } ], - 'outputs': [{ - 'value': new BN('28000') + outputs: [{ + value: new BN(28000) }, { - 'value': new BN('2000') + value: new BN(2000) } ], - 'fee': new BN('0') + fee: new BN(0) } }, { - 'description': '1 output, 2 inputs (related), no change', - 'feeRate': new BN('10'), - 'inputs': [{ - 'address': 'a', - 'value': new BN('100000') + description: '1 output, 2 inputs (related), no change', + feeRate: new BN(10), + inputs: [{ + address: 'a', + value: new BN(100000) }, { - 'address': 'a', - 'value': new BN('2000') + address: 'a', + value: new BN(2000) } ], - 'outputs': [ - new BN('98000') + outputs: [ + new BN(98000) ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'address': 'a', - 'value': new BN('100000') + expected: { + inputs: [{ + i: 0, + address: 'a', + value: new BN(100000) }], - 'outputs': [{ - 'value': new BN('98000') + outputs: [{ + value: new BN(98000) }], - 'fee': new BN('2000') + fee: new BN(2000) } }, { - 'description': 'many outputs, no change', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('30000'), - new BN('12220'), - new BN('10001') - ], - 'outputs': [ - new BN('35000'), - new BN('5000'), - new BN('5000'), - new BN('1000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('30000') + description: 'many outputs, no change', + feeRate: new BN(10), + inputs: [ + new BN(30000), + new BN(12220), + new BN(10001) + ], + outputs: [ + new BN(35000), + new BN(5000), + new BN(5000), + new BN(1000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(30000) }, { - 'i': 1, - 'value': new BN('12220') + i: 1, + value: new BN(12220) }, { - 'i': 2, - 'value': new BN('10001') + i: 2, + value: new BN(10001) } ], - 'outputs': [{ - 'value': new BN('35000') + outputs: [{ + value: new BN(35000) }, { - 'value': new BN('5000') + value: new BN(5000) }, { - 'value': new BN('5000') + value: new BN(5000) }, { - 'value': new BN('1000') + value: new BN(1000) } ], - 'fee': new BN('6221') + fee: new BN(6221) } }, { - 'description': 'many outputs, change expected', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('30000'), - new BN('14220'), - new BN('10001') - ], - 'outputs': [ - new BN('35000'), - new BN('5000'), - new BN('5000'), - new BN('1000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('30000') + description: 'many outputs, change expected', + feeRate: new BN(10), + inputs: [ + new BN(30000), + new BN(14220), + new BN(10001) + ], + outputs: [ + new BN(35000), + new BN(5000), + new BN(5000), + new BN(1000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(30000) }, { - 'i': 1, - 'value': new BN('14220') + i: 1, + value: new BN(14220) }, { - 'i': 2, - 'value': new BN('10001') + i: 2, + value: new BN(10001) } ], - 'outputs': [{ - 'value': new BN('35000') + outputs: [{ + value: new BN(35000) }, { - 'value': new BN('5000') + value: new BN(5000) }, { - 'value': new BN('5000') + value: new BN(5000) }, { - 'value': new BN('1000') + value: new BN(1000) }, { - 'value': new BN('1981') + value: new BN(1981) } ], - 'fee': new BN('6240') + fee: new BN(6240) } }, { - 'description': 'many outputs, no fee, change expected', - 'feeRate': new BN('0'), - 'inputs': [ - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000') - ], - 'outputs': [ - new BN('28000'), - new BN('1000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('5000') + description: 'many outputs, no fee, change expected', + feeRate: new BN(0), + inputs: [ + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000) + ], + outputs: [ + new BN(28000), + new BN(1000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(5000) }, { - 'i': 1, - 'value': new BN('5000') + i: 1, + value: new BN(5000) }, { - 'i': 2, - 'value': new BN('5000') + i: 2, + value: new BN(5000) }, { - 'i': 3, - 'value': new BN('5000') + i: 3, + value: new BN(5000) }, { - 'i': 4, - 'value': new BN('5000') + i: 4, + value: new BN(5000) }, { - 'i': 5, - 'value': new BN('5000') + i: 5, + value: new BN(5000) } ], - 'outputs': [{ - 'value': new BN('28000') + outputs: [{ + value: new BN(28000) }, { - 'value': new BN('1000') + value: new BN(1000) }, { - 'value': new BN('1000') + value: new BN(1000) } ], - 'fee': new BN('0') + fee: new BN(0) } }, { - 'description': 'no outputs, no change', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('1900') - ], - 'outputs': [], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('1900') + description: 'no outputs, no change', + feeRate: new BN(10), + inputs: [ + new BN(1900) + ], + outputs: [], + expected: { + inputs: [{ + i: 0, + value: new BN(1900) }], - 'outputs': [], - 'fee': new BN('1900') + outputs: [], + fee: new BN(1900) } }, { - 'description': 'no outputs, change expected', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') - ], - 'outputs': [], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('20000') + description: 'no outputs, change expected', + feeRate: new BN(10), + inputs: [ + new BN(20000) + ], + outputs: [], + expected: { + inputs: [{ + i: 0, + value: new BN(20000) }], - 'outputs': [{ - 'value': new BN('18080') + outputs: [{ + value: new BN(18080) }], - 'fee': new BN('1920') + fee: new BN(1920) } }, { - 'description': 'not enough funds, empty result', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') + description: 'not enough funds, empty result', + feeRate: new BN(10), + inputs: [ + new BN(20000) ], - 'outputs': [ - new BN('40000') + outputs: [ + new BN(40000) ], - 'expected': { - 'fee': new BN('1920') + expected: { + fee: new BN(1920) } }, { - 'description': 'not enough funds (w/ fee), empty result', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('40000') + description: 'not enough funds (w/ fee), empty result', + feeRate: new BN(10), + inputs: [ + new BN(40000) ], - 'outputs': [ - new BN('40000') + outputs: [ + new BN(40000) ], - 'expected': { - 'fee': new BN('1920') + expected: { + fee: new BN(1920) } }, { - 'description': 'not enough funds (no inputs), empty result', - 'feeRate': new BN('10'), - 'inputs': [], - 'outputs': [], - 'expected': { - 'fee': new BN('100') + description: 'not enough funds (no inputs), empty result', + feeRate: new BN(10), + inputs: [], + outputs: [], + expected: { + fee: new BN(100) } }, { - 'description': 'not enough funds (no inputs), empty result (>1KiB)', - 'feeRate': new BN('10'), - 'inputs': [], - 'outputs': [ - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1') - ], - 'expected': { - 'fee': new BN('9960') + description: 'not enough funds (no inputs), empty result (>1KiB)', + feeRate: new BN(10), + inputs: [], + outputs: [ + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1) + ], + expected: { + fee: new BN(9960) } }, { - 'description': '2 outputs, some with missing value (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') + description: '2 outputs, some with missing value (NaN)', + feeRate: new BN(10), + inputs: [ + new BN(20000) ], - 'outputs': [ - new BN('1000'), + outputs: [ + new BN(1000), {} ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': 'input with float values (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ + description: 'input with float values (NaN)', + feeRate: new BN(10), + inputs: [ 20000.5 ], - 'outputs': [ - new BN('10000'), - new BN('1200') + outputs: [ + new BN(10000), + new BN(1200) ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': '2 outputs, with float values (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') + description: '2 outputs, with float values (NaN)', + feeRate: new BN(10), + inputs: [ + new BN(20000) ], - 'outputs': [ + outputs: [ 10000.25, 1200.5 ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': '2 outputs, string values (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') + description: '2 outputs, string values (NaN)', + feeRate: new BN(10), + inputs: [ + new BN(20000) ], - 'outputs': [{ - 'value': '100' + outputs: [{ + value: '100' }, { - 'value': '204' + value: '204' } ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': 'inputs and outputs, bad feeRate (NaN)', - 'feeRate': '1', - 'inputs': [ - new BN('20000') + description: 'inputs and outputs, bad feeRate (NaN)', + feeRate: '1', + inputs: [ + new BN(20000) ], - 'outputs': [ - new BN('10000') + outputs: [ + new BN(10000) ], - 'expected': {} + expected: {} }, { - 'description': 'inputs and outputs, bad feeRate (NaN)', - 'feeRate': 1.5, - 'inputs': [ - new BN('20000') + description: 'inputs and outputs, bad feeRate (NaN)', + feeRate: 1.5, + inputs: [ + new BN(20000) ], - 'outputs': [ - new BN('10000') + outputs: [ + new BN(10000) ], - 'expected': {} + expected: {} } ] diff --git a/test/fixtures/break.js b/test/fixtures/break.js index cdbc998..2be5f54 100644 --- a/test/fixtures/break.js +++ b/test/fixtures/break.js @@ -1,274 +1,274 @@ var BN = require('bn.js') module.exports = [{ - 'description': '1:1, no remainder', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('11920') + description: '1:1, no remainder', + feeRate: new BN(10), + inputs: [ + new BN(11920) ], - 'output': new BN('10000'), - 'expected': { - 'inputs': [{ - 'value': new BN('11920') + output: new BN(10000), + expected: { + inputs: [{ + value: new BN(11920) }], - 'outputs': [{ - 'value': new BN('10000') + outputs: [{ + value: new BN(10000) }], - 'fee': new BN('1920') + fee: new BN(1920) } }, { - 'description': '1:1', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('12000') + description: '1:1', + feeRate: new BN(10), + inputs: [ + new BN(12000) ], - 'output': { - 'address': 'woop', - 'value': new BN('10000') + output: { + address: 'woop', + value: new BN(10000) }, - 'expected': { - 'fee': new BN('2000'), - 'inputs': [{ - 'value': new BN('12000') + expected: { + fee: new BN(2000), + inputs: [{ + value: new BN(12000) }], - 'outputs': [{ - 'address': 'woop', - 'value': new BN('10000') + outputs: [{ + address: 'woop', + value: new BN(10000) }] } }, { - 'description': '1:1, w/ change', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('12000') + description: '1:1, w/ change', + feeRate: new BN(10), + inputs: [ + new BN(12000) ], - 'output': new BN('8000'), - 'expected': { - 'inputs': [{ - 'value': new BN('12000') + output: new BN(8000), + expected: { + inputs: [{ + value: new BN(12000) }], - 'outputs': [{ - 'value': new BN('8000') + outputs: [{ + value: new BN(8000) }, { - 'value': new BN('1740') + value: new BN(1740) } ], - 'fee': new BN('2260') + fee: new BN(2260) } }, { - 'description': '1:4', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('12000') + description: '1:4', + feeRate: new BN(10), + inputs: [ + new BN(12000) ], - 'output': new BN('2000'), - 'expected': { - 'inputs': [{ - 'value': new BN('12000') + output: new BN(2000), + expected: { + inputs: [{ + value: new BN(12000) }], - 'outputs': [{ - 'value': new BN('2000') + outputs: [{ + value: new BN(2000) }, { - 'value': new BN('2000') + value: new BN(2000) }, { - 'value': new BN('2000') + value: new BN(2000) }, { - 'value': new BN('2000') + value: new BN(2000) } ], - 'fee': new BN('4000') + fee: new BN(4000) } }, { - 'description': '2:5', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('3000'), - new BN('12000') + description: '2:5', + feeRate: new BN(10), + inputs: [ + new BN(3000), + new BN(12000) ], - 'output': new BN('2000'), - 'expected': { - 'inputs': [{ - 'value': new BN('3000') + output: new BN(2000), + expected: { + inputs: [{ + value: new BN(3000) }, { - 'value': new BN('12000') + value: new BN(12000) } ], - 'outputs': [{ - 'value': new BN('2000') + outputs: [{ + value: new BN(2000) }, { - 'value': new BN('2000') + value: new BN(2000) }, { - 'value': new BN('2000') + value: new BN(2000) }, { - 'value': new BN('2000') + value: new BN(2000) }, { - 'value': new BN('2000') + value: new BN(2000) } ], - 'fee': new BN('5000') + fee: new BN(5000) } }, { - 'description': '2:5, no fee', - 'feeRate': new BN('0'), - 'inputs': [ - new BN('5000'), - new BN('10000') + description: '2:5, no fee', + feeRate: new BN(0), + inputs: [ + new BN(5000), + new BN(10000) ], - 'output': new BN('3000'), - 'expected': { - 'inputs': [{ - 'value': new BN('5000') + output: new BN(3000), + expected: { + inputs: [{ + value: new BN(5000) }, { - 'value': new BN('10000') + value: new BN(10000) } ], - 'outputs': [{ - 'value': new BN('3000') + outputs: [{ + value: new BN(3000) }, { - 'value': new BN('3000') + value: new BN(3000) }, { - 'value': new BN('3000') + value: new BN(3000) }, { - 'value': new BN('3000') + value: new BN(3000) }, { - 'value': new BN('3000') + value: new BN(3000) } ], - 'fee': new BN('0') + fee: new BN(0) } }, { - 'description': '2:2 (+1), w/ change', - 'feeRate': new BN('7'), - 'inputs': [ - new BN('16000') + description: '2:2 (+1), w/ change', + feeRate: new BN(7), + inputs: [ + new BN(16000) ], - 'output': new BN('6000'), - 'expected': { - 'inputs': [{ - 'value': new BN('16000') + output: new BN(6000), + expected: { + inputs: [{ + value: new BN(16000) }], - 'outputs': [{ - 'value': new BN('6000') + outputs: [{ + value: new BN(6000) }, { - 'value': new BN('6000') + value: new BN(6000) }, { - 'value': new BN('2180') + value: new BN(2180) } ], - 'fee': new BN('1820') + fee: new BN(1820) } }, { - 'description': '2:3 (+1), no fee, w/ change', - 'feeRate': new BN('0'), - 'inputs': [ - new BN('5000'), - new BN('10000') + description: '2:3 (+1), no fee, w/ change', + feeRate: new BN(0), + inputs: [ + new BN(5000), + new BN(10000) ], - 'output': new BN('4000'), - 'expected': { - 'inputs': [{ - 'value': new BN('5000') + output: new BN(4000), + expected: { + inputs: [{ + value: new BN(5000) }, { - 'value': new BN('10000') + value: new BN(10000) } ], - 'outputs': [{ - 'value': new BN('4000') + outputs: [{ + value: new BN(4000) }, { - 'value': new BN('4000') + value: new BN(4000) }, { - 'value': new BN('4000') + value: new BN(4000) }, { - 'value': new BN('3000') + value: new BN(3000) } ], - 'fee': new BN('0') + fee: new BN(0) } }, { - 'description': 'not enough funds', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('41000'), - new BN('1000') + description: 'not enough funds', + feeRate: new BN(10), + inputs: [ + new BN(41000), + new BN(1000) ], - 'output': new BN('40000'), - 'expected': { - 'fee': new BN('3400') + output: new BN(40000), + expected: { + fee: new BN(3400) } }, { - 'description': 'no inputs', - 'feeRate': new BN('10'), - 'inputs': [], - 'output': new BN('2000'), - 'expected': { - 'fee': new BN('440') + description: 'no inputs', + feeRate: new BN(10), + inputs: [], + output: new BN(2000), + expected: { + fee: new BN(440) } }, { - 'description': 'invalid output (NaN)', - 'feeRate': new BN('10'), - 'inputs': [], - 'output': {}, - 'expected': { - 'fee': new BN('100') + description: 'invalid output (NaN)', + feeRate: new BN(10), + inputs: [], + output: {}, + expected: { + fee: new BN(100) } }, { - 'description': 'input with float values (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ + description: 'input with float values (NaN)', + feeRate: new BN(10), + inputs: [ 10000.5 ], - 'output': new BN('5000'), - 'expected': { - 'fee': new BN('1580') + output: new BN(5000), + expected: { + fee: new BN(1580) } }, { - 'description': 'inputs and outputs, bad feeRate (NaN)', - 'feeRate': '1', - 'inputs': [ - new BN('20000') + description: 'inputs and outputs, bad feeRate (NaN)', + feeRate: '1', + inputs: [ + new BN(20000) ], - 'output': new BN('10000'), - 'expected': {} + output: new BN(10000), + expected: {} }, { - 'description': 'inputs and outputs, bad feeRate (NaN)', - 'feeRate': 1.5, - 'inputs': [ - new BN('20000') + description: 'inputs and outputs, bad feeRate (NaN)', + feeRate: 1.5, + inputs: [ + new BN(20000) ], - 'output': new BN('10000'), - 'expected': {} + output: new BN(10000), + expected: {} } ] diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 60e7ab1..5c5e1e9 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -1,548 +1,548 @@ var BN = require('bn.js') module.exports = [{ - 'description': '1 output, no change', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('102001') - ], - 'outputs': [ - new BN('100000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('102001') + description: '1 output, no change', + feeRate: new BN(10), + inputs: [ + new BN(102001) + ], + outputs: [ + new BN(100000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(102001) }], - 'outputs': [{ - 'value': new BN('100000') + outputs: [{ + value: new BN(100000) }], - 'fee': new BN('2001') + fee: new BN(2001) } }, { - 'description': '1 output, change expected', - 'feeRate': new BN('5'), - 'inputs': [ - new BN('106001') - ], - 'outputs': [ - new BN('100000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('106001') + description: '1 output, change expected', + feeRate: new BN(5), + inputs: [ + new BN(106001) + ], + outputs: [ + new BN(100000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(106001) }], - 'outputs': [{ - 'value': new BN('100000') + outputs: [{ + value: new BN(100000) }, { - 'value': new BN('4871') + value: new BN(4871) } ], - 'fee': new BN('1130') + fee: new BN(1130) } }, { - 'description': '1 output, sub-optimal inputs (if re-ordered), direct possible', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000'), - new BN('40000'), - new BN('40000') - ], - 'outputs': [ - new BN('7700') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('10000') + description: '1 output, sub-optimal inputs (if re-ordered), direct possible', + feeRate: new BN(10), + inputs: [ + new BN(10000), + new BN(40000), + new BN(40000) + ], + outputs: [ + new BN(7700) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(10000) }], - 'outputs': [{ - 'value': new BN('7700') + outputs: [{ + value: new BN(7700) }], - 'fee': new BN('2300') + fee: new BN(2300) } }, { - 'description': '1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000'), - new BN('40000'), - new BN('40000') - ], - 'outputs': [ - new BN('6800') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('10000') + description: '1 output, sub-optimal inputs (if re-ordered), direct possible, but slightly higher fee', + feeRate: new BN(10), + inputs: [ + new BN(10000), + new BN(40000), + new BN(40000) + ], + outputs: [ + new BN(6800) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(10000) }], - 'outputs': [{ - 'value': new BN('6800') + outputs: [{ + value: new BN(6800) }], - 'fee': new BN('3200') + fee: new BN(3200) } }, { - 'description': '1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected', - 'feeRate': new BN('5'), - 'inputs': [ - new BN('10000'), - new BN('40000'), - new BN('40000') - ], - 'outputs': [ - new BN('4700') - ], - 'expected': { - 'inputs': [{ - 'i': 1, - 'value': new BN('40000') + description: '1 output, sub-optimal inputs (if re-ordered, no direct possible), change expected', + feeRate: new BN(5), + inputs: [ + new BN(10000), + new BN(40000), + new BN(40000) + ], + outputs: [ + new BN(4700) + ], + expected: { + inputs: [{ + i: 1, + value: new BN(40000) }], - 'outputs': [{ - 'value': new BN('4700') + outputs: [{ + value: new BN(4700) }, { - 'value': new BN('34170') + value: new BN(34170) } ], - 'fee': new BN('1130') + fee: new BN(1130) } }, { - 'description': '1 output, optimal inputs, no change', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000') - ], - 'outputs': [ - new BN('7700') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('10000') + description: '1 output, optimal inputs, no change', + feeRate: new BN(10), + inputs: [ + new BN(10000) + ], + outputs: [ + new BN(7700) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(10000) }], - 'outputs': [{ - 'value': new BN('7700') + outputs: [{ + value: new BN(7700) }], - 'fee': new BN('2300') + fee: new BN(2300) } }, { - 'description': '1 output, no fee, change expected', - 'feeRate': new BN('0'), - 'inputs': [ - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000') - ], - 'outputs': [ - new BN('28000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('5000') + description: '1 output, no fee, change expected', + feeRate: new BN(0), + inputs: [ + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000) + ], + outputs: [ + new BN(28000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(5000) }, { - 'i': 1, - 'value': new BN('5000') + i: 1, + value: new BN(5000) }, { - 'i': 2, - 'value': new BN('5000') + i: 2, + value: new BN(5000) }, { - 'i': 3, - 'value': new BN('5000') + i: 3, + value: new BN(5000) }, { - 'i': 4, - 'value': new BN('5000') + i: 4, + value: new BN(5000) }, { - 'i': 5, - 'value': new BN('5000') + i: 5, + value: new BN(5000) } ], - 'outputs': [{ - 'value': new BN('28000') + outputs: [{ + value: new BN(28000) }, { - 'value': new BN('2000') + value: new BN(2000) } ], - 'fee': new BN('0') + fee: new BN(0) } }, { - 'description': '1 output, 2 inputs (related), no change', - 'feeRate': new BN('10'), - 'inputs': [{ - 'address': 'a', - 'value': new BN('100000') + description: '1 output, 2 inputs (related), no change', + feeRate: new BN(10), + inputs: [{ + address: 'a', + value: new BN(100000) }, { - 'address': 'a', - 'value': new BN('2000') + address: 'a', + value: new BN(2000) } ], - 'outputs': [ - new BN('98000') + outputs: [ + new BN(98000) ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'address': 'a', - 'value': new BN('100000') + expected: { + inputs: [{ + i: 0, + address: 'a', + value: new BN(100000) }], - 'outputs': [{ - 'value': new BN('98000') + outputs: [{ + value: new BN(98000) }], - 'fee': new BN('2000') + fee: new BN(2000) } }, { - 'description': 'many outputs, no change', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('30000'), - new BN('12220'), - new BN('10001') - ], - 'outputs': [ - new BN('35000'), - new BN('5000'), - new BN('5000'), - new BN('1000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('30000') + description: 'many outputs, no change', + feeRate: new BN(10), + inputs: [ + new BN(30000), + new BN(12220), + new BN(10001) + ], + outputs: [ + new BN(35000), + new BN(5000), + new BN(5000), + new BN(1000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(30000) }, { - 'i': 1, - 'value': new BN('12220') + i: 1, + value: new BN(12220) }, { - 'i': 2, - 'value': new BN('10001') + i: 2, + value: new BN(10001) } ], - 'outputs': [{ - 'value': new BN('35000') + outputs: [{ + value: new BN(35000) }, { - 'value': new BN('5000') + value: new BN(5000) }, { - 'value': new BN('5000') + value: new BN(5000) }, { - 'value': new BN('1000') + value: new BN(1000) } ], - 'fee': new BN('6221') + fee: new BN(6221) } }, { - 'description': 'many outputs, change expected', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('30000'), - new BN('14220'), - new BN('10001') - ], - 'outputs': [ - new BN('35000'), - new BN('5000'), - new BN('5000'), - new BN('1000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('30000') + description: 'many outputs, change expected', + feeRate: new BN(10), + inputs: [ + new BN(30000), + new BN(14220), + new BN(10001) + ], + outputs: [ + new BN(35000), + new BN(5000), + new BN(5000), + new BN(1000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(30000) }, { - 'i': 1, - 'value': new BN('14220') + i: 1, + value: new BN(14220) }, { - 'i': 2, - 'value': new BN('10001') + i: 2, + value: new BN(10001) } ], - 'outputs': [{ - 'value': new BN('35000') + outputs: [{ + value: new BN(35000) }, { - 'value': new BN('5000') + value: new BN(5000) }, { - 'value': new BN('5000') + value: new BN(5000) }, { - 'value': new BN('1000') + value: new BN(1000) }, { - 'value': new BN('1981') + value: new BN(1981) } ], - 'fee': new BN('6240') + fee: new BN(6240) } }, { - 'description': 'many outputs, no fee, change expected', - 'feeRate': new BN('0'), - 'inputs': [ - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000'), - new BN('5000') - ], - 'outputs': [ - new BN('28000'), - new BN('1000') - ], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('5000') + description: 'many outputs, no fee, change expected', + feeRate: new BN(0), + inputs: [ + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000), + new BN(5000) + ], + outputs: [ + new BN(28000), + new BN(1000) + ], + expected: { + inputs: [{ + i: 0, + value: new BN(5000) }, { - 'i': 1, - 'value': new BN('5000') + i: 1, + value: new BN(5000) }, { - 'i': 2, - 'value': new BN('5000') + i: 2, + value: new BN(5000) }, { - 'i': 3, - 'value': new BN('5000') + i: 3, + value: new BN(5000) }, { - 'i': 4, - 'value': new BN('5000') + i: 4, + value: new BN(5000) }, { - 'i': 5, - 'value': new BN('5000') + i: 5, + value: new BN(5000) } ], - 'outputs': [{ - 'value': new BN('28000') + outputs: [{ + value: new BN(28000) }, { - 'value': new BN('1000') + value: new BN(1000) }, { - 'value': new BN('1000') + value: new BN(1000) } ], - 'fee': new BN('0') + fee: new BN(0) } }, { - 'description': 'no outputs, no change', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('1900') - ], - 'outputs': [], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('1900') + description: 'no outputs, no change', + feeRate: new BN(10), + inputs: [ + new BN(1900) + ], + outputs: [], + expected: { + inputs: [{ + i: 0, + value: new BN(1900) }], - 'outputs': [], - 'fee': new BN('1900') + outputs: [], + fee: new BN(1900) } }, { - 'description': 'no outputs, change expected', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') - ], - 'outputs': [], - 'expected': { - 'inputs': [{ - 'i': 0, - 'value': new BN('20000') + description: 'no outputs, change expected', + feeRate: new BN(10), + inputs: [ + new BN(20000) + ], + outputs: [], + expected: { + inputs: [{ + i: 0, + value: new BN(20000) }], - 'outputs': [{ - 'value': new BN('18080') + outputs: [{ + value: new BN(18080) }], - 'fee': new BN('1920') + fee: new BN(1920) } }, { - 'description': 'not enough funds, empty result', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') + description: 'not enough funds, empty result', + feeRate: new BN(10), + inputs: [ + new BN(20000) ], - 'outputs': [ - new BN('40000') + outputs: [ + new BN(40000) ], - 'expected': { - 'fee': new BN('1920') + expected: { + fee: new BN(1920) } }, { - 'description': 'not enough funds (w/ fee), empty result', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('40000') + description: 'not enough funds (w/ fee), empty result', + feeRate: new BN(10), + inputs: [ + new BN(40000) ], - 'outputs': [ - new BN('40000') + outputs: [ + new BN(40000) ], - 'expected': { - 'fee': new BN('1920') + expected: { + fee: new BN(1920) } }, { - 'description': 'not enough funds (no inputs), empty result', - 'feeRate': new BN('10'), - 'inputs': [], - 'outputs': [], - 'expected': { - 'fee': new BN('100') + description: 'not enough funds (no inputs), empty result', + feeRate: new BN(10), + inputs: [], + outputs: [], + expected: { + fee: new BN(100) } }, { - 'description': 'not enough funds (no inputs), empty result (>1KiB)', - 'feeRate': new BN('10'), - 'inputs': [], - 'outputs': [ - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1'), - new BN('1') - ], - 'expected': { - 'fee': new BN('9960') + description: 'not enough funds (no inputs), empty result (>1KiB)', + feeRate: new BN(10), + inputs: [], + outputs: [ + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1), + new BN(1) + ], + expected: { + fee: new BN(9960) } }, { - 'description': '2 outputs, some with missing value (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') + description: '2 outputs, some with missing value (NaN)', + feeRate: new BN(10), + inputs: [ + new BN(20000) ], - 'outputs': [ - new BN('1000'), + outputs: [ + new BN(1000), {} ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': 'input with float values (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ + description: 'input with float values (NaN)', + feeRate: new BN(10), + inputs: [ 20000.5 ], - 'outputs': [ + outputs: [ 10000, 1200 ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': '2 outputs, with float values (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') + description: '2 outputs, with float values (NaN)', + feeRate: new BN(10), + inputs: [ + new BN(20000) ], - 'outputs': [ + outputs: [ new BN('10000.25'), new BN('1200.5') ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': '2 outputs, string values (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') + description: '2 outputs, string values (NaN)', + feeRate: new BN(10), + inputs: [ + new BN(20000) ], - 'outputs': [{ - 'value': '100' + outputs: [{ + value: '100' }, { - 'value': '204' + value: '204' } ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': 'inputs and outputs, bad feeRate (NaN)', - 'feeRate': '1', - 'inputs': [ - new BN('20000') + description: 'inputs and outputs, bad feeRate (NaN)', + feeRate: '1', + inputs: [ + new BN(20000) ], - 'outputs': [ - new BN('10000') + outputs: [ + new BN(10000) ], - 'expected': {} + expected: {} }, { - 'description': 'inputs and outputs, bad feeRate (NaN)', - 'feeRate': 1.5, - 'inputs': [ - new BN('20000') + description: 'inputs and outputs, bad feeRate (NaN)', + feeRate: 1.5, + inputs: [ + new BN(20000) ], - 'outputs': [ - new BN('10000') + outputs: [ + new BN(10000) ], - 'expected': {} + expected: {} } ] diff --git a/test/fixtures/split.js b/test/fixtures/split.js index 6962ed6..4e881d6 100644 --- a/test/fixtures/split.js +++ b/test/fixtures/split.js @@ -1,264 +1,264 @@ var BN = require('bn.js') module.exports = [{ - 'description': '1 to 3', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('18000') + description: '1 to 3', + feeRate: new BN(10), + inputs: [ + new BN(18000) ], - 'outputs': [{}, + outputs: [{}, {}, {} ], - 'expected': { - 'inputs': [{ - 'value': new BN('18000') + expected: { + inputs: [{ + value: new BN(18000) }], - 'outputs': [{ - 'value': new BN('5133') + outputs: [{ + value: new BN(5133) }, { - 'value': new BN('5133') + value: new BN(5133) }, { - 'value': new BN('5133') + value: new BN(5133) } ], - 'fee': new BN('2601') + fee: new BN(2601) } }, { - 'description': '5 to 2', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000'), - new BN('10000'), - new BN('10000'), - new BN('10000'), - new BN('10000') + description: '5 to 2', + feeRate: new BN(10), + inputs: [ + new BN(10000), + new BN(10000), + new BN(10000), + new BN(10000), + new BN(10000) ], - 'outputs': [{}, + outputs: [{}, {} ], - 'expected': { - 'inputs': [{ - 'value': new BN('10000') + expected: { + inputs: [{ + value: new BN(10000) }, { - 'value': new BN('10000') + value: new BN(10000) }, { - 'value': new BN('10000') + value: new BN(10000) }, { - 'value': new BN('10000') + value: new BN(10000) }, { - 'value': new BN('10000') + value: new BN(10000) } ], - 'outputs': [{ - 'value': new BN('20910') + outputs: [{ + value: new BN(20910) }, { - 'value': new BN('20910') + value: new BN(20910) } ], - 'fee': new BN('8180') + fee: new BN(8180) } }, { - 'description': '3 to 1', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000'), - new BN('10000'), - new BN('10000') + description: '3 to 1', + feeRate: new BN(10), + inputs: [ + new BN(10000), + new BN(10000), + new BN(10000) ], - 'outputs': [{}], - 'expected': { - 'inputs': [{ - 'value': new BN('10000') + outputs: [{}], + expected: { + inputs: [{ + value: new BN(10000) }, { - 'value': new BN('10000') + value: new BN(10000) }, { - 'value': new BN('10000') + value: new BN(10000) } ], - 'outputs': [{ - 'value': new BN('25120') + outputs: [{ + value: new BN(25120) }], - 'fee': new BN('4880') + fee: new BN(4880) } }, { - 'description': '3 to 3 (1 output pre-defined)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000'), - new BN('10000'), - new BN('10000') + description: '3 to 3 (1 output pre-defined)', + feeRate: new BN(10), + inputs: [ + new BN(10000), + new BN(10000), + new BN(10000) ], - 'outputs': [{ - 'address': 'foobar', - 'value': new BN('12000') + outputs: [{ + address: 'foobar', + value: new BN(12000) }, { - 'address': 'fizzbuzz' + address: 'fizzbuzz' }, {} ], - 'expected': { - 'inputs': [{ - 'value': new BN('10000') + expected: { + inputs: [{ + value: new BN(10000) }, { - 'value': new BN('10000') + value: new BN(10000) }, { - 'value': new BN('10000') + value: new BN(10000) } ], - 'outputs': [{ - 'address': 'foobar', - 'value': new BN('12000') + outputs: [{ + address: 'foobar', + value: new BN(12000) }, { - 'address': 'fizzbuzz', - 'value': new BN('6220') + address: 'fizzbuzz', + value: new BN(6220) }, { - 'value': new BN('6220') + value: new BN(6220) } ], - 'fee': new BN('5560') + fee: new BN(5560) } }, { - 'description': '2 to 0 (no result)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('10000'), - new BN('10000') + description: '2 to 0 (no result)', + feeRate: new BN(10), + inputs: [ + new BN(10000), + new BN(10000) ], - 'outputs': [], - 'expected': { - 'fee': new BN('3060') + outputs: [], + expected: { + fee: new BN(3060) } }, { - 'description': '0 to 2 (no result)', - 'feeRate': new BN('10'), - 'inputs': [], - 'outputs': [{}, + description: '0 to 2 (no result)', + feeRate: new BN(10), + inputs: [], + outputs: [{}, {} ], - 'expected': { - 'fee': new BN('780') + expected: { + fee: new BN(780) } }, { - 'description': '1 to 2, output is dust (no result)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('2000') + description: '1 to 2, output is dust (no result)', + feeRate: new BN(10), + inputs: [ + new BN(2000) ], - 'outputs': [{}], - 'expected': { - 'fee': new BN('1920') + outputs: [{}], + expected: { + fee: new BN(1920) } }, { - 'description': '2 outputs, some with missing value (NaN)', - 'feeRate': new BN('11'), - 'inputs': [ - new BN('20000') + description: '2 outputs, some with missing value (NaN)', + feeRate: new BN(11), + inputs: [ + new BN(20000) ], - 'outputs': [{ - 'value': new BN('4000') + outputs: [{ + value: new BN(4000) }, {} ], - 'expected': { - 'inputs': [{ - 'value': new BN('20000') + expected: { + inputs: [{ + value: new BN(20000) }], - 'outputs': [{ - 'value': new BN('4000') + outputs: [{ + value: new BN(4000) }, { - 'value': new BN('13514') + value: new BN(13514) } ], - 'fee': new BN('2486') + fee: new BN(2486) } }, // TODO { - 'description': '2 outputs, some with float values (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ - new BN('20000') + description: '2 outputs, some with float values (NaN)', + feeRate: new BN(10), + inputs: [ + new BN(20000) ], - 'outputs': [{ - 'value': 4000.5 + outputs: [{ + value: 4000.5 }, {} ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': '2 outputs, string values (NaN)', - 'feeRate': new BN('11'), - 'inputs': [ - new BN('20000') + description: '2 outputs, string values (NaN)', + feeRate: new BN(11), + inputs: [ + new BN(20000) ], - 'outputs': [{ - 'value': '100' + outputs: [{ + value: '100' }, { - 'value': '204' + value: '204' } ], - 'expected': { - 'fee': new BN('2486') + expected: { + fee: new BN(2486) } }, { - 'description': 'input with float values (NaN)', - 'feeRate': new BN('10'), - 'inputs': [ + description: 'input with float values (NaN)', + feeRate: new BN(10), + inputs: [ 20000.5 ], - 'outputs': [{}, + outputs: [{}, {} ], - 'expected': { - 'fee': new BN('2260') + expected: { + fee: new BN(2260) } }, { - 'description': 'inputs and outputs, bad feeRate (NaN)', - 'feeRate': '1', - 'inputs': [ - new BN('20000') + description: 'inputs and outputs, bad feeRate (NaN)', + feeRate: '1', + inputs: [ + new BN(20000) ], - 'outputs': [{}], - 'expected': {} + outputs: [{}], + expected: {} }, { - 'description': 'inputs and outputs, bad feeRate (NaN)', - 'feeRate': 1.5, - 'inputs': [ - new BN('20000') + description: 'inputs and outputs, bad feeRate (NaN)', + feeRate: 1.5, + inputs: [ + new BN(20000) ], - 'outputs': [{}], - 'expected': {} + outputs: [{}], + expected: {} } ]