Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Options Parameter #54

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions accumulative.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
var utils = require('./utils')

var processOptions = require('./defaultOpts')
var processOptionsFunc = processOptions.processOptions
// add inputs until we reach or surpass the target value (or deplete)
// worst-case: O(n)
module.exports = function accumulative (utxos, outputs, feeRate) {
module.exports = function accumulative (utxos, outputs, feeRate, options) {
options = processOptionsFunc(options)
if (!isFinite(utils.uintOrNaN(feeRate))) return {}
var bytesAccum = utils.transactionBytes([], outputs)

Expand Down Expand Up @@ -31,7 +33,7 @@ module.exports = function accumulative (utxos, outputs, feeRate) {
// go again?
if (inAccum < outAccum + fee) continue

return utils.finalize(inputs, outputs, feeRate)
return utils.finalize(inputs, outputs, feeRate, options)
}

return { fee: feeRate * bytesAccum }
Expand Down
10 changes: 6 additions & 4 deletions blackjack.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
var utils = require('./utils')

var processOptions = require('./defaultOpts')
var processOptionsFunc = processOptions.processOptions
// only add inputs if they don't bust the target value (aka, exact match)
// worst-case: O(n)
module.exports = function blackjack (utxos, outputs, feeRate) {
module.exports = function blackjack (utxos, outputs, feeRate, options) {
options = processOptionsFunc()
if (!isFinite(utils.uintOrNaN(feeRate))) return {}

var bytesAccum = utils.transactionBytes([], outputs)

var inAccum = 0
var inputs = []
var outAccum = utils.sumOrNaN(outputs)
var threshold = utils.dustThreshold({}, feeRate)
var threshold = utils.dustThreshold(feeRate, options.changeInputLengthEstimate)

for (var i = 0; i < utxos.length; ++i) {
var input = utxos[i]
Expand All @@ -28,7 +30,7 @@ module.exports = function blackjack (utxos, outputs, feeRate) {
// go again?
if (inAccum < outAccum + fee) continue

return utils.finalize(inputs, outputs, feeRate)
return utils.finalize(inputs, outputs, feeRate, options)
}

return { fee: feeRate * bytesAccum }
Expand Down
11 changes: 6 additions & 5 deletions break.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
var utils = require('./utils')

// break utxos into the maximum number of output possible
module.exports = function broken (utxos, output, feeRate) {
var processOptions = require('./defaultOpts')
var processOptionsFunc = processOptions.processOptions
// break utxos into the maximum number of 'output' possible
module.exports = function broken (utxos, output, feeRate, options) {
options = processOptionsFunc(options)
if (!isFinite(utils.uintOrNaN(feeRate))) return {}

var bytesAccum = utils.transactionBytes(utxos, [])
Expand Down Expand Up @@ -29,6 +31,5 @@ module.exports = function broken (utxos, output, feeRate) {
outAccum += value
outputs.push(output)
}

return utils.finalize(utxos, outputs, feeRate)
return utils.finalize(utxos, outputs, feeRate, options)
}
11 changes: 11 additions & 0 deletions defaultOpts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const defaultOpts = {
changeInputLengthEstimate: 107,
changeOutputLength: 25
}

function processOptions (options) {
const mergerdOptions = Object.assign(defaultOpts, options)
return mergerdOptions
}

exports.processOptions = processOptions
10 changes: 6 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
var accumulative = require('./accumulative')
var blackjack = require('./blackjack')
var utils = require('./utils')

var processOptions = require('./defaultOpts')
var processOptionsFunc = processOptions.processOptions
// order by descending value, minus the inputs approximate fee
function utxoScore (x, feeRate) {
return x.value - (feeRate * utils.inputBytes(x))
}

module.exports = function coinSelect (utxos, outputs, feeRate) {
module.exports = function coinSelect (utxos, outputs, feeRate, options) {
options = processOptionsFunc()
utxos = utxos.concat().sort(function (a, b) {
return utxoScore(b, feeRate) - utxoScore(a, feeRate)
})

// attempt to use the blackjack strategy first (no change output)
var base = blackjack(utxos, outputs, feeRate)
var base = blackjack(utxos, outputs, feeRate, options)
if (base.inputs) return base

// else, try the accumulative strategy
return accumulative(utxos, outputs, feeRate)
return accumulative(utxos, outputs, feeRate, options)
}
12 changes: 7 additions & 5 deletions split.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
var utils = require('./utils')

var processOptions = require('./defaultOpts')
var processOptionsFunc = processOptions.processOptions
// split utxos between each output, ignores outputs with .value defined
module.exports = function split (utxos, outputs, feeRate) {
module.exports = function split (utxos, outputs, feeRate, options) {
options = processOptionsFunc()
if (!isFinite(utils.uintOrNaN(feeRate))) return {}

var bytesAccum = utils.transactionBytes(utxos, outputs)
Expand All @@ -17,7 +19,7 @@ module.exports = function split (utxos, outputs, feeRate) {
return a + !isFinite(x.value)
}, 0)

if (remaining === 0 && unspecified === 0) return utils.finalize(utxos, outputs, feeRate)
if (remaining === 0 && unspecified === 0) return utils.finalize(utxos, outputs, feeRate, options.changeInputLengthEstimate, options.changeOutputLength)

var splitOutputsCount = outputs.reduce(function (a, x) {
return a + !x.value
Expand All @@ -26,7 +28,7 @@ module.exports = function split (utxos, outputs, feeRate) {

// ensure every output is either user defined, or over the threshold
if (!outputs.every(function (x) {
return x.value !== undefined || (splitValue > utils.dustThreshold(x, feeRate))
return x.value !== undefined || (splitValue > utils.dustThreshold(feeRate, options.changeInputLengthEstimate))
})) return { fee: fee }

// assign splitValue to outputs not user defined
Expand All @@ -40,5 +42,5 @@ module.exports = function split (utxos, outputs, feeRate) {
return y
})

return utils.finalize(utxos, outputs, feeRate)
return utils.finalize(utxos, outputs, feeRate, options)
}
85 changes: 66 additions & 19 deletions stats/index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,81 @@
let Simulation = require('./simulation')
let modules = require('./strategies')
let min = 14226 // 0.1 USD
let max = 142251558 // 1000 USD
let feeRate = 56 * 100
let results = []
const Simulation = require('./simulation')
const modules = require('./strategies')
const min = 14226 // 0.1 USD
const max = 142251558 // 1000 USD
const feeRate = 56 * 100
const results = []

// switch between two modes
// true - failed payments are discarded
// false - failed payments are queued until there is enough balance
const discardFailed = false
const walletType = 'p2pkh' // set to either p2pkh or p2sh

// data from blockchaing from ~august 2015 - august 2017
// see https://gist.github.com/runn1ng/8e2881e5bb44e01748e46b3d1c549038
// these are 2-of-3 lengths, most common p2sh (66%), percs changed so they add to 100
const scripthashScriptLengthData = {
prob: 0.16,
inLengthPercs: {
252: 25.29,
253: 49.77,
254: 24.94
},
outLength: 23
}

// these are compressed key length (90% of p2pkh inputs)
// percs changed so they add to 100
const pubkeyhashScriptLengthData = {
prob: 0.84,
inLengthPercs: {
105: 0.4,
106: 50.7,
107: 48.81,
108: 0.09
},
outLength: 25
}

const selectedData = walletType === 'p2pkh' ? pubkeyhashScriptLengthData : scripthashScriptLengthData
const inLengthProbs = selectedData.inLengthPercs

const outLengthProbs = {};
[scripthashScriptLengthData, pubkeyhashScriptLengthData].forEach(({ prob, outLength }) => {
outLengthProbs[outLength] = prob
})

// n samples
for (var j = 0; j < 100; ++j) {
if (j % 200 === 0) console.log('Iteration', j)

let stages = []
const stages = []

for (var i = 1; i < 4; ++i) {
let utxos = Simulation.generateTxos(20 / i, min, max)
let txos = Simulation.generateTxos(80 / i, min, max / 3)

const utxos = Simulation.generateTxos(20 / i, min, max, inLengthProbs)
const txos = Simulation.generateTxos(80 / i, min, max / 3, outLengthProbs)
stages.push({ utxos, txos })
}

// for each strategy
for (var name in modules) {
let f = modules[name]
let simulation = new Simulation(name, f, feeRate)
const f = modules[name]
const simulation = new Simulation(name, f, feeRate)

stages.forEach((stage) => {
// supplement our UTXOs
stage.utxos.forEach(x => simulation.addUTXO(x))
stage.utxos.forEach(x => {
simulation.addUTXO(x)

// if discardFailed == true, this should do nothing
simulation.run(discardFailed)
})

// now, run stage.txos.length transactions
stage.txos.forEach((txo) => simulation.runQueued([txo]))
stage.txos.forEach((txo) => {
simulation.plan([txo])
simulation.run(discardFailed)
})
})

simulation.finish()
Expand All @@ -37,16 +84,16 @@ for (var j = 0; j < 100; ++j) {
}

function merge (results) {
let resultMap = {}
const resultMap = {}

results.forEach(({ stats }) => {
let result = resultMap[stats.name]
const result = resultMap[stats.name]

if (result) {
result.inputs += stats.inputs
result.outputs += stats.outputs
result.transactions += stats.transactions
result.failed += stats.failed
result.plannedTransactions += stats.plannedTransactions
result.fees += stats.fees
result.bytes += stats.bytes
result.utxos += stats.utxos
Expand Down Expand Up @@ -76,8 +123,8 @@ merge(results).sort((a, b) => {

// top 20 only
}).slice(0, 20).forEach((x, i) => {
let { stats } = x
let DNF = stats.failed / (stats.transactions + stats.failed)
const { stats } = x
const DNF = 1 - stats.transactions / stats.plannedTransactions

console.log(
pad(i),
Expand Down
Loading