Skip to content

Commit

Permalink
fix(lint): autofix linting rules
Browse files Browse the repository at this point in the history
  • Loading branch information
schnogz committed May 27, 2021
1 parent 699b727 commit 01477bb
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 133 deletions.
12 changes: 9 additions & 3 deletions packages/blockchain-wallet-v4/src/coinSelection/coin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable */
import { inputComparator, sortOutputs } from 'bip69'
import * as Bitcoin from 'bitcoinjs-lib'
import { clamp, curry, is, length, sort, split } from 'ramda'
Expand Down Expand Up @@ -36,6 +35,7 @@ export class Coin extends Type {
}

overValue(f) {
// eslint-disable-next-line
return over(value, f, this)
}

Expand All @@ -51,30 +51,35 @@ export class Coin extends Type {
let type = 'P2PKH'
try {
const output = Bitcoin.address.toOutputScript(this.address)
// TODO: is addr var even needed?
// TODO: is addr var even needed doesnt seem to be used?
// eslint-disable-next-line
let addr = null

try {
// eslint-disable-next-line
addr = Bitcoin.payments.p2pkh({ output }).address
type = 'P2PKH'
// eslint-disable-next-line
} catch (e) {}
try {
// eslint-disable-next-line
addr = Bitcoin.payments.p2sh({ output }).address
type = 'P2SH'
// eslint-disable-next-line
} catch (e) {}
try {
// eslint-disable-next-line
addr = Bitcoin.payments.p2wpkh({ output }).address
type = 'P2WPKH'
// eslint-disable-next-line
} catch (e) {}
try {
// eslint-disable-next-line
addr = Bitcoin.payments.p2wsh({ output }).address
type = 'P2WSH'
// eslint-disable-next-line
} catch (e) {}
// eslint-disable-next-line
} catch (e) {}

return type
Expand Down Expand Up @@ -109,8 +114,9 @@ export const fromJS = (o, network) => {
priv: o.priv,
script: o.script ? o.script : addressToScript(o.address, network),
txHash: o.tx_hash_big_endian,
// eslint-disable-next-line
value: parseInt(o.value),
xpub: o.xpub,
xpub: o.xpub
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable */
import { map } from 'ramda'

import * as Coin from './coin.js'
Expand Down Expand Up @@ -264,14 +263,14 @@ describe('Coin Selection', () => {
])
const targets = map(Coin.fromJS, [{ value: 100000 }])
const selection = cs.descentDraw(targets, 55, inputs, 'change-address')
expect(selection.inputs.map(x => x.value)).toEqual([20000, 300000])
expect(selection.inputs.map((x) => x.value)).toEqual([20000, 300000])

// overhead + inputs + outputs
// 55 * (10 + 148 * 2 + 34 * 2) = 20570
expect(selection.fee).toEqual(20570)
// change = inputs - outputs - fee + feeForAdditionalChangeOutput
// 20000 + 300000 - 100000 - 20570 + 1870 = 201300
expect(selection.outputs.map(x => x.value)).toEqual([100000, 201300])
expect(selection.outputs.map((x) => x.value)).toEqual([100000, 201300])
})
})

Expand All @@ -288,15 +287,15 @@ describe('Coin Selection', () => {
])
const targets = map(Coin.fromJS, [{ value: 100000 }])
const selection = cs.ascentDraw(targets, 55, inputs, 'change-address')
expect(selection.inputs.map(x => x.value)).toEqual([20000, 300000])
expect(selection.inputs.map((x) => x.value)).toEqual([20000, 300000])

// overhead + inputs + outputs
// 55 * (10 + 148 * 2 + 34 * 2) = 20570
expect(selection.fee).toEqual(20570)

// change = inputs - outputs - fee
// 20000 + 300000 - 100000 - 20570 + 1870 = 201300
expect(selection.outputs.map(x => x.value)).toEqual([100000, 201300])
expect(selection.outputs.map((x) => x.value)).toEqual([100000, 201300])
})
})

Expand Down
221 changes: 96 additions & 125 deletions packages/blockchain-wallet-v4/src/coinSelection/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable */
import memoize from 'fast-memoize'
import shuffle from 'fisher-yates'
import { List } from 'immutable-ext'
Expand All @@ -21,19 +20,82 @@ import seedrandom from 'seedrandom'

import * as Coin from './coin.js'

// getByteCount implementation
// based on https://gist.github.com/junderw/b43af3253ea5865ed52cb51c200ac19c
// Usage:
// - getByteCount({'P2WPKH':45},{'P2PKH':1}) Means "45 inputs of P2WPKH and 1 output of P2PKH"
// - getByteCount({'P2PKH':1,'P2WPKH':2},{'P2PKH':2}) means "1 P2PKH input and 2 P2WPKH inputs along with 2 P2PKH outputs"

// assumes compressed pubkeys in all cases.
// TODO: SEGWIT we need to account for uncompressed pubkeys!
export const IO_TYPES = {
inputs: {
P2PKH: 148, // legacy
P2WPKH: 67.75 // native segwit
},
outputs: {
P2PKH: 34,
P2SH: 32,
P2WPKH: 31,
P2WSH: 43
}
}
const VBYTES_PER_WEIGHT_UNIT = 4

// isFromAccount :: selection -> boolean
export const isFromAccount = selection =>
export const isFromAccount = (selection) =>
selection.inputs[0] ? selection.inputs[0].isFromAccount() : false

// isFromLegacy :: selection -> boolean
export const isFromLegacy = selection =>
export const isFromLegacy = (selection) =>
selection.inputs[0] ? selection.inputs[0].isFromLegacy() : false

export const dustThreshold = (feeRate, change) =>
Math.ceil((Coin.inputBytes(change) + Coin.outputBytes(change)) * feeRate)

export const getByteCount = (inputs, outputs) => {
let vBytesTotal = 0
let hasWitness = false
let inputCount = 0
let outputCount = 0
// assumes compressed pubkeys in all cases.

function checkUInt53(n) {
if (n < 0 || n > Number.MAX_SAFE_INTEGER || n % 1 !== 0)
throw new RangeError('value out of range')
}

function varIntLength(number) {
checkUInt53(number)

return number < 0xfd ? 1 : number <= 0xffff ? 3 : number <= 0xffffffff ? 5 : 9
}

Object.keys(inputs).forEach(function (key) {
checkUInt53(inputs[key])
vBytesTotal += IO_TYPES.inputs[key] * inputs[key]
inputCount += inputs[key]
if (key.indexOf('W') >= 0) hasWitness = true
})

Object.keys(outputs).forEach(function (key) {
checkUInt53(outputs[key])
vBytesTotal += IO_TYPES.outputs[key] * outputs[key]
outputCount += outputs[key]
})

// segwit marker + segwit flag + witness element count
let overhead = hasWitness ? 0.25 + 0.25 + varIntLength(inputCount) / VBYTES_PER_WEIGHT_UNIT : 0

overhead += 4 // nVersion
overhead += varIntLength(inputCount)
overhead += varIntLength(outputCount)
overhead += 4 // nLockTime

vBytesTotal += overhead
return vBytesTotal
}

export const transactionBytes = (inputs, outputs) => {
const coinTypeReducer = (acc, coin) => {
const type = coin.type ? coin.type() : 'P2PKH'
Expand All @@ -57,19 +119,15 @@ export const DEPRECATED_transactionBytes = (inputs, outputs) =>
export const effectiveBalance = curry((feePerByte, inputs, outputs = [{}]) =>
List(inputs)
.fold(Coin.empty)
.overValue(v =>
clamp(
0,
Infinity,
v - Math.ceil(transactionBytes(inputs, outputs) * feePerByte)
)
.overValue((v) =>
clamp(0, Infinity, v - Math.ceil(transactionBytes(inputs, outputs) * feePerByte))
)
)

// findTarget :: [Coin(x), ..., Coin(y)] -> Number -> [Coin(a), ..., Coin(b)] -> Selection
const ft = (targets, feePerByte, coins, changeAddress) => {
const target = List(targets).fold(Coin.empty).value
const _findTarget = seed => {
const _findTarget = (seed) => {
const acc = seed[0]
const newCoin = head(seed[2])
if (isNil(newCoin) || acc > target + seed[1]) {
Expand All @@ -86,68 +144,53 @@ const ft = (targets, feePerByte, coins, changeAddress) => {
]
}
const partialFee = Math.ceil(transactionBytes([], targets) * feePerByte)
const effectiveCoins = filter(
c => Coin.effectiveValue(feePerByte, c) > 0,
coins
)
const effectiveCoins = filter((c) => Coin.effectiveValue(feePerByte, c) > 0, coins)
const selection = unfold(_findTarget, [0, partialFee, effectiveCoins])
if (isEmpty(selection)) {
// no coins to select
return { fee: 0, inputs: [], outputs: [] }
} else {
const maxBalance = last(selection)[0]
const fee = last(selection)[1]
const selectedCoins = map(e => e[2], selection)
if (maxBalance < target + fee) {
// not enough money to satisfy target
return { fee: fee, inputs: [], outputs: targets }
} else {
const extra = maxBalance - target - fee
const change = Coin.fromJS({
address: changeAddress,
change: true,
value: extra,
})
// should we add change?
if (extra >= dustThreshold(feePerByte, change)) {
const feeForAdditionalChangeOutput = changeBytes(change.type()) * feePerByte
return {
fee: fee + feeForAdditionalChangeOutput,
inputs: selectedCoins,
outputs: [...targets, change]
}
} else {
// burn change
return { fee: fee + extra, inputs: selectedCoins, outputs: targets }
}
}
const maxBalance = last(selection)[0]
const fee = last(selection)[1]
const selectedCoins = map((e) => e[2], selection)
if (maxBalance < target + fee) {
// not enough money to satisfy target
return { fee, inputs: [], outputs: targets }
}
const extra = maxBalance - target - fee
const change = Coin.fromJS({
address: changeAddress,
change: true,
value: extra
})
// should we add change?
if (extra >= dustThreshold(feePerByte, change)) {
const feeForAdditionalChangeOutput = changeBytes(change.type()) * feePerByte
return {
fee: fee + feeForAdditionalChangeOutput,
inputs: selectedCoins,
outputs: [...targets, change]
}
}
// burn change
return { fee: fee + extra, inputs: selectedCoins, outputs: targets }
}
export const findTarget = memoize(ft)

// singleRandomDraw :: Number -> [Coin(a), ..., Coin(b)] -> String -> Selection
export const selectAll = (feePerByte, coins, outAddress) => {
const effectiveCoins = filter(
c => Coin.effectiveValue(feePerByte, c) > 0,
coins
)
const effectiveCoins = filter((c) => Coin.effectiveValue(feePerByte, c) > 0, coins)
const effBalance = effectiveBalance(feePerByte, effectiveCoins).value
const Balance = List(effectiveCoins).fold(Coin.empty).value
const fee = Balance - effBalance
return {
fee: fee,
fee,
inputs: effectiveCoins,
outputs: [Coin.fromJS({ value: effBalance, address: outAddress })]
outputs: [Coin.fromJS({ address: outAddress, value: effBalance })]
}
}
// singleRandomDraw :: [Coin(x), ..., Coin(y)] -> Number -> [Coin(a), ..., Coin(b)] -> String -> Selection
export const singleRandomDraw = (
targets,
feePerByte,
coins,
changeAddress,
seed
) => {
export const singleRandomDraw = (targets, feePerByte, coins, changeAddress, seed) => {
const rng = is(String, seed) ? seedrandom(seed) : undefined
return findTarget(targets, feePerByte, shuffle(coins, rng), changeAddress)
}
Expand All @@ -168,75 +211,3 @@ export const ascentDraw = (targets, feePerByte, coins, changeAddress) =>
sort((a, b) => b.lte(a), coins),
changeAddress
)

// getByteCount implementation
// based on https://gist.github.com/junderw/b43af3253ea5865ed52cb51c200ac19c
// Usage:
// - getByteCount({'P2WPKH':45},{'P2PKH':1}) Means "45 inputs of P2WPKH and 1 output of P2PKH"
// - getByteCount({'P2PKH':1,'P2WPKH':2},{'P2PKH':2}) means "1 P2PKH input and 2 P2WPKH inputs along with 2 P2PKH outputs"

// assumes compressed pubkeys in all cases.
// TODO: SEGWIT we need to account for uncompressed pubkeys!
export const IO_TYPES = {
inputs: {
P2PKH: 148, // legacy
P2WPKH: 67.75 // native segwit
},
outputs: {
P2PKH: 34,
P2SH: 32,
P2WPKH: 31,
P2WSH: 43
}
}

export const getByteCount = (inputs, outputs) => {
var vBytesTotal = 0
var hasWitness = false
var inputCount = 0
var outputCount = 0
// assumes compressed pubkeys in all cases.

function checkUInt53(n) {
if (n < 0 || n > Number.MAX_SAFE_INTEGER || n % 1 !== 0)
throw new RangeError('value out of range')
}

function varIntLength(number) {
checkUInt53(number)

return number < 0xfd
? 1
: number <= 0xffff
? 3
: number <= 0xffffffff
? 5
: 9
}

Object.keys(inputs).forEach(function(key) {
checkUInt53(inputs[key])
vBytesTotal += IO_TYPES.inputs[key] * inputs[key]
inputCount += inputs[key]
if (key.indexOf('W') >= 0) hasWitness = true
})

Object.keys(outputs).forEach(function(key) {
checkUInt53(outputs[key])
vBytesTotal += IO_TYPES.outputs[key] * outputs[key]
outputCount += outputs[key]
})

// segwit marker + segwit flag + witness element count
var overhead = hasWitness
? 0.25 + 0.25 + varIntLength(inputCount) / VBYTES_PER_WEIGHT_UNIT
: 0

overhead += 4 // nVersion
overhead += varIntLength(inputCount)
overhead += varIntLength(outputCount)
overhead += 4 // nLockTime

vBytesTotal += overhead
return vBytesTotal
}

0 comments on commit 01477bb

Please sign in to comment.