From 45a72416c9ed8a9267ec394ad4625ccdf1759d9a Mon Sep 17 00:00:00 2001 From: Wei Lu Date: Tue, 17 Jun 2014 12:03:14 +0800 Subject: [PATCH 1/6] wallet: Move dust and fee per kb into networks.js --- src/networks.js | 16 ++++++++++++---- src/wallet.js | 10 +++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/networks.js b/src/networks.js index da71d23..4b1f690 100644 --- a/src/networks.js +++ b/src/networks.js @@ -9,7 +9,9 @@ module.exports = { }, pubKeyHash: 0x00, scriptHash: 0x05, - wif: 0x80 + wif: 0x80, + dustThreshold: 5430, //should be 546 https://github.com/bitcoin/bitcoin/pull/2760/files + feePerKb: 20000 }, dogecoin: { magicPrefix: '\x19Dogecoin Signed Message:\n', @@ -19,7 +21,9 @@ module.exports = { }, pubKeyHash: 0x1e, scriptHash: 0x16, - wif: 0x9e + wif: 0x9e, + dustThreshold: 1000000, + feePerKb: 100000000 }, litecoin: { magicPrefix: '\x19Litecoin Signed Message:\n', @@ -29,7 +33,9 @@ module.exports = { }, pubKeyHash: 0x30, scriptHash: 0x05, - wif: 0xb0 + wif: 0xb0, + dustThreshold: 1000, + feePerKb: 100000 }, testnet: { magicPrefix: '\x18Bitcoin Signed Message:\n', @@ -39,6 +45,8 @@ module.exports = { }, pubKeyHash: 0x6f, scriptHash: 0xc4, - wif: 0xef + wif: 0xef, + dustThreshold: 5430, + feePerKb: 20000 } } diff --git a/src/wallet.js b/src/wallet.js index 1501456..1e484f2 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -20,9 +20,6 @@ function Wallet(seed, network) { this.addresses = [] this.changeAddresses = [] - // Dust value - this.dustThreshold = 5430 - // Transaction output data this.outputs = {} @@ -182,7 +179,7 @@ function Wallet(seed, network) { } this.createTx = function(to, value, fixedFee, changeAddress) { - assert(value > this.dustThreshold, value + ' must be above dust threshold (' + this.dustThreshold + ' Satoshis)') + assert(value > network.dustThreshold, value + ' must be above dust threshold (' + network.dustThreshold + ' Satoshis)') var utxos = getCandidateOutputs(value) var accum = 0 @@ -206,7 +203,7 @@ function Wallet(seed, network) { if (accum >= subTotal) { var change = accum - subTotal - if (change > this.dustThreshold) { + if (change > network.dustThreshold) { tx.addOutput(changeAddress || getChangeAddress(), change) } @@ -235,13 +232,12 @@ function Wallet(seed, network) { return sortByValueDesc } - var feePerKb = 20000 function estimateFeePadChangeOutput(tx) { var tmpTx = tx.clone() tmpTx.addOutput(getChangeAddress(), 0) var byteSize = tmpTx.toBuffer().length - return feePerKb * Math.ceil(byteSize / 1000) + return network.feePerKb * Math.ceil(byteSize / 1000) } function getChangeAddress() { From 79ec61d0858e71e0ed975d7cd19b47147c2908ff Mon Sep 17 00:00:00 2001 From: Wei Lu Date: Tue, 17 Jun 2014 14:36:57 +0800 Subject: [PATCH 2/6] Fix bitcoin dustThreshold and feePerKb values --- src/networks.js | 12 ++++++------ test/wallet.js | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/networks.js b/src/networks.js index 4b1f690..fdb3706 100644 --- a/src/networks.js +++ b/src/networks.js @@ -10,8 +10,8 @@ module.exports = { pubKeyHash: 0x00, scriptHash: 0x05, wif: 0x80, - dustThreshold: 5430, //should be 546 https://github.com/bitcoin/bitcoin/pull/2760/files - feePerKb: 20000 + dustThreshold: 546, + feePerKb: 10000 }, dogecoin: { magicPrefix: '\x19Dogecoin Signed Message:\n', @@ -22,7 +22,7 @@ module.exports = { pubKeyHash: 0x1e, scriptHash: 0x16, wif: 0x9e, - dustThreshold: 1000000, + dustThreshold: 0, feePerKb: 100000000 }, litecoin: { @@ -34,7 +34,7 @@ module.exports = { pubKeyHash: 0x30, scriptHash: 0x05, wif: 0xb0, - dustThreshold: 1000, + dustThreshold: 0, feePerKb: 100000 }, testnet: { @@ -46,7 +46,7 @@ module.exports = { pubKeyHash: 0x6f, scriptHash: 0xc4, wif: 0xef, - dustThreshold: 5430, - feePerKb: 20000 + dustThreshold: 546, + feePerKb: 10000 } } diff --git a/test/wallet.js b/test/wallet.js index ad94d03..eec0ac0 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -389,7 +389,7 @@ describe('Wallet', function() { "hash": fakeTxId(3), "outputIndex": 0, "address" : address2, - "value": 520000 // enough for value and fee + "value": 510000 // enough for value and fee } ] wallet.setUnspentOutputs(utxo) @@ -417,7 +417,7 @@ describe('Wallet', function() { }) it('allows fee to be set to zero', function(){ - value = 520000 + value = 510000 var fee = 0 var tx = wallet.createTx(to, value, fee) @@ -459,7 +459,7 @@ describe('Wallet', function() { }]) var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' - var toValue = value - 20000 + var toValue = value - 10000 var tx = wallet.createTx(to, toValue) assert.equal(tx.outs.length, 1) @@ -516,7 +516,7 @@ describe('Wallet', function() { describe('change', function(){ it('uses the last change address if there is any', function(){ - var fee = 5000 + var fee = 0 wallet.generateChangeAddress() wallet.generateChangeAddress() var tx = wallet.createTx(to, value, fee) @@ -526,11 +526,11 @@ describe('Wallet', function() { var outAddress = Address.fromOutputScript(out.script) assert.equal(outAddress.toString(), wallet.changeAddresses[1]) - assert.equal(out.value, 15000) + assert.equal(out.value, 10000) }) it('generates a change address if there is not any', function(){ - var fee = 5000 + var fee = 0 assert.equal(wallet.changeAddresses.length, 0) var tx = wallet.createTx(to, value, fee) @@ -540,7 +540,7 @@ describe('Wallet', function() { var outAddress = Address.fromOutputScript(out.script) assert.equal(outAddress.toString(), wallet.changeAddresses[0]) - assert.equal(out.value, 15000) + assert.equal(out.value, 10000) }) it('skips change if it is not above dust threshold', function(){ @@ -569,11 +569,11 @@ describe('Wallet', function() { describe('when value is below dust threshold', function(){ it('throws an error', function(){ - var value = 5430 + var value = 546 assert.throws(function() { wallet.createTx(to, value) - }, /5430 must be above dust threshold \(5430 Satoshis\)/) + }, /546 must be above dust threshold \(546 Satoshis\)/) }) }) @@ -583,7 +583,7 @@ describe('Wallet', function() { assert.throws(function() { wallet.createTx(to, value) - }, /Not enough funds \(incl. fee\): 1420000 < 1420001/) + }, /Not enough funds \(incl. fee\): 1410000 < 1410001/) }) }) }) From 122b613eaf52417f2965d213e26e1cc7d58c1360 Mon Sep 17 00:00:00 2001 From: Wei Lu Date: Tue, 17 Jun 2014 14:48:06 +0800 Subject: [PATCH 3/6] Move fee estimation into networks.js --- src/networks.js | 16 ++++++++++++---- src/wallet.js | 2 +- test/network.js | 23 +++++++++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 test/network.js diff --git a/src/networks.js b/src/networks.js index fdb3706..eedb66a 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,6 +1,11 @@ // https://en.bitcoin.it/wiki/List_of_address_prefixes // Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 -module.exports = { + +function bitcoinEstimateFee(txByteSize) { + return networks.bitcoin.feePerKb * Math.ceil(txByteSize / 1000) +} + +var networks = { bitcoin: { magicPrefix: '\x18Bitcoin Signed Message:\n', bip32: { @@ -10,8 +15,9 @@ module.exports = { pubKeyHash: 0x00, scriptHash: 0x05, wif: 0x80, - dustThreshold: 546, - feePerKb: 10000 + dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/529047fcd18acd1b64dc95d6eb69edeaad75d405/src/core.h#L176-L188 + feePerKb: 10000, // https://github.com/bitcoin/bitcoin/blob/3f39b9d4551d729c3a2e4decd810ac6887cfaeb3/src/main.cpp#L52 + estimateFee: bitcoinEstimateFee }, dogecoin: { magicPrefix: '\x19Dogecoin Signed Message:\n', @@ -47,6 +53,8 @@ module.exports = { scriptHash: 0xc4, wif: 0xef, dustThreshold: 546, - feePerKb: 10000 + feePerKb: 10000, + estimateFee: bitcoinEstimateFee } } +module.exports = networks diff --git a/src/wallet.js b/src/wallet.js index 1e484f2..b15bcfd 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -237,7 +237,7 @@ function Wallet(seed, network) { tmpTx.addOutput(getChangeAddress(), 0) var byteSize = tmpTx.toBuffer().length - return network.feePerKb * Math.ceil(byteSize / 1000) + return network.estimateFee(byteSize) } function getChangeAddress() { diff --git a/test/network.js b/test/network.js new file mode 100644 index 0000000..ee0d6d4 --- /dev/null +++ b/test/network.js @@ -0,0 +1,23 @@ +var assert = require('assert') +var networks = require('../src/networks') +var Transaction = require('../src/transaction') + +var fixtureTxes = require('./fixtures/mainnet_tx') +var fixtureTx1Hex = fixtureTxes.prevTx +var fixtureTxBigHex = fixtureTxes.bigTx + +describe('bitcoin', function() { + describe('estimateFee', function() { + var estimateFee = networks.bitcoin.estimateFee + + it('works for fixture tx 1', function() { + var tx = Transaction.fromHex(fixtureTx1Hex) + assert.equal(estimateFee(tx.toBuffer().length), 10000) + }) + + it('works for fixture big tx', function() { + var tx = Transaction.fromHex(fixtureTxBigHex) + assert.equal(estimateFee(tx.toBuffer().length), 30000) + }) + }) +}) From bc3e0770f928f1b50eca8d957c9c180753f923ae Mon Sep 17 00:00:00 2001 From: Wei Lu Date: Tue, 17 Jun 2014 16:23:09 +0800 Subject: [PATCH 4/6] Add fee estimation functions for dogecoin and litecoin --- src/networks.js | 40 +++++++++++++++++------ src/wallet.js | 3 +- test/network.js | 84 +++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 106 insertions(+), 21 deletions(-) diff --git a/src/networks.js b/src/networks.js index eedb66a..8e3ea4d 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,10 +1,6 @@ // https://en.bitcoin.it/wiki/List_of_address_prefixes // Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 -function bitcoinEstimateFee(txByteSize) { - return networks.bitcoin.feePerKb * Math.ceil(txByteSize / 1000) -} - var networks = { bitcoin: { magicPrefix: '\x18Bitcoin Signed Message:\n', @@ -15,9 +11,9 @@ var networks = { pubKeyHash: 0x00, scriptHash: 0x05, wif: 0x80, - dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/529047fcd18acd1b64dc95d6eb69edeaad75d405/src/core.h#L176-L188 - feePerKb: 10000, // https://github.com/bitcoin/bitcoin/blob/3f39b9d4551d729c3a2e4decd810ac6887cfaeb3/src/main.cpp#L52 - estimateFee: bitcoinEstimateFee + dustThreshold: 546, + feePerKb: 10000, + estimateFee: estimateFee('bitcoin') }, dogecoin: { magicPrefix: '\x19Dogecoin Signed Message:\n', @@ -29,7 +25,9 @@ var networks = { scriptHash: 0x16, wif: 0x9e, dustThreshold: 0, - feePerKb: 100000000 + dustSoftThreshold: 100000000, + feePerKb: 100000000, + estimateFee: estimateFee('dogecoin') }, litecoin: { magicPrefix: '\x19Litecoin Signed Message:\n', @@ -41,7 +39,9 @@ var networks = { scriptHash: 0x05, wif: 0xb0, dustThreshold: 0, - feePerKb: 100000 + dustSoftThreshold: 100000, + feePerKb: 100000, + estimateFee: estimateFee('litecoin') }, testnet: { magicPrefix: '\x18Bitcoin Signed Message:\n', @@ -54,7 +54,27 @@ var networks = { wif: 0xef, dustThreshold: 546, feePerKb: 10000, - estimateFee: bitcoinEstimateFee + estimateFee: estimateFee('bitcoin') } } + +function estimateFee(type) { + return function(tx) { + var network = networks[type] + var baseFee = network.feePerKb + var byteSize = tx.toBuffer().length + + var fee = baseFee * Math.ceil(byteSize / 1000) + if(network.dustSoftThreshold == undefined) return fee + + tx.outs.forEach(function(e){ + if(e.value < network.dustSoftThreshold) { + fee += baseFee + } + }) + + return fee + } +} + module.exports = networks diff --git a/src/wallet.js b/src/wallet.js index b15bcfd..a77cd02 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -236,8 +236,7 @@ function Wallet(seed, network) { var tmpTx = tx.clone() tmpTx.addOutput(getChangeAddress(), 0) - var byteSize = tmpTx.toBuffer().length - return network.estimateFee(byteSize) + return network.estimateFee(tmpTx) } function getChangeAddress() { diff --git a/test/network.js b/test/network.js index ee0d6d4..9e178b2 100644 --- a/test/network.js +++ b/test/network.js @@ -6,18 +6,84 @@ var fixtureTxes = require('./fixtures/mainnet_tx') var fixtureTx1Hex = fixtureTxes.prevTx var fixtureTxBigHex = fixtureTxes.bigTx -describe('bitcoin', function() { - describe('estimateFee', function() { - var estimateFee = networks.bitcoin.estimateFee +describe('networks', function() { + describe('bitcoin', function() { + describe('estimateFee', function() { + var estimateFee = networks.bitcoin.estimateFee - it('works for fixture tx 1', function() { - var tx = Transaction.fromHex(fixtureTx1Hex) - assert.equal(estimateFee(tx.toBuffer().length), 10000) + it('works for fixture tx 1', function() { + var tx = Transaction.fromHex(fixtureTx1Hex) + assert.equal(estimateFee(tx), 10000) + }) + + it('works for fixture big tx', function() { + var tx = Transaction.fromHex(fixtureTxBigHex) + assert.equal(estimateFee(tx), 30000) + }) }) + }) - it('works for fixture big tx', function() { - var tx = Transaction.fromHex(fixtureTxBigHex) - assert.equal(estimateFee(tx.toBuffer().length), 30000) + describe('dogecoin', function() { + describe('estimateFee', function() { + var estimateFee = networks.dogecoin.estimateFee + + it('regular fee per kb applies when every output has value no less than DUST_SOFT_LIMIT', function() { + var tx = Transaction.fromHex(fixtureTx1Hex) + tx.outs.forEach(function(e){ + e.value = 100000000 + }) + + assert.equal(estimateFee(tx), 100000000) + }) + + it('applies additional fee on every output with value below DUST_SOFT_LIMIT', function() { + var tx = Transaction.fromHex(fixtureTx1Hex) + tx.outs.forEach(function(e){ + e.value = 99999999 + }) + + assert.equal(estimateFee(tx), 4 * 100000000) // 3 outs in total + }) + + it('works for fixture big tx', function() { + var tx = Transaction.fromHex(fixtureTxBigHex) + tx.outs.forEach(function(e){ + e.value = 100000000 + }) + assert.equal(estimateFee(tx), 300000000) + }) + }) + }) + + describe('litecoin', function() { + describe('estimateFee', function() { + var estimateFee = networks.litecoin.estimateFee + + it('regular fee per kb applies when every output has value no less than DUST_SOFT_LIMIT', function() { + var tx = Transaction.fromHex(fixtureTx1Hex) + tx.outs.forEach(function(e){ + e.value = 100000 + }) + + assert.equal(estimateFee(tx), 100000) + }) + + it('applies additional fee on every output with value below DUST_SOFT_LIMIT', function() { + var tx = Transaction.fromHex(fixtureTx1Hex) + tx.outs.forEach(function(e){ + e.value = 99999 + }) + + assert.equal(estimateFee(tx), 4 * 100000) // 3 outs in total + }) + + it('works for fixture big tx', function() { + var tx = Transaction.fromHex(fixtureTxBigHex) + tx.outs.forEach(function(e){ + e.value = 100000 + }) + assert.equal(estimateFee(tx), 300000) + }) }) }) }) From c4285d9da261afd55a27f0c4627a1140aecda771 Mon Sep 17 00:00:00 2001 From: Wei Lu Date: Tue, 17 Jun 2014 16:30:50 +0800 Subject: [PATCH 5/6] Network specific constants reference the latest tags --- src/networks.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/networks.js b/src/networks.js index 8e3ea4d..fccef52 100644 --- a/src/networks.js +++ b/src/networks.js @@ -11,8 +11,8 @@ var networks = { pubKeyHash: 0x00, scriptHash: 0x05, wif: 0x80, - dustThreshold: 546, - feePerKb: 10000, + dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162 + feePerKb: 10000, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/main.cpp#L53 estimateFee: estimateFee('bitcoin') }, dogecoin: { @@ -24,9 +24,9 @@ var networks = { pubKeyHash: 0x1e, scriptHash: 0x16, wif: 0x9e, - dustThreshold: 0, - dustSoftThreshold: 100000000, - feePerKb: 100000000, + dustThreshold: 0, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160 + dustSoftThreshold: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.h#L62 + feePerKb: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.cpp#L58 estimateFee: estimateFee('dogecoin') }, litecoin: { @@ -38,9 +38,9 @@ var networks = { pubKeyHash: 0x30, scriptHash: 0x05, wif: 0xb0, - dustThreshold: 0, - dustSoftThreshold: 100000, - feePerKb: 100000, + dustThreshold: 0, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365 + dustSoftThreshold: 100000, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.h#L53 + feePerKb: 100000, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L56 estimateFee: estimateFee('litecoin') }, testnet: { From 5dcefc53291086a839cac8ca7c58eedf2152e085 Mon Sep 17 00:00:00 2001 From: Wei Lu Date: Tue, 17 Jun 2014 23:35:40 +0800 Subject: [PATCH 6/6] Network estimateFee tests no longer relies on fixtures --- test/network.js | 76 ++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/test/network.js b/test/network.js index 9e178b2..8662b1c 100644 --- a/test/network.js +++ b/test/network.js @@ -1,23 +1,31 @@ var assert = require('assert') var networks = require('../src/networks') +var sinon = require('sinon') var Transaction = require('../src/transaction') -var fixtureTxes = require('./fixtures/mainnet_tx') -var fixtureTx1Hex = fixtureTxes.prevTx -var fixtureTxBigHex = fixtureTxes.bigTx - describe('networks', function() { + var txToBuffer + before(function(){ + txToBuffer = sinon.stub(Transaction.prototype, "toBuffer") + }) + + after(function(){ + Transaction.prototype.toBuffer.restore() + }) + describe('bitcoin', function() { describe('estimateFee', function() { var estimateFee = networks.bitcoin.estimateFee - it('works for fixture tx 1', function() { - var tx = Transaction.fromHex(fixtureTx1Hex) + it('works at boundry', function() { + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() assert.equal(estimateFee(tx), 10000) }) - it('works for fixture big tx', function() { - var tx = Transaction.fromHex(fixtureTxBigHex) + it('rounds up to the closest kb for estimation', function() { + txToBuffer.returns(new Buffer(2800)) + var tx = new Transaction() assert.equal(estimateFee(tx), 30000) }) }) @@ -28,28 +36,26 @@ describe('networks', function() { var estimateFee = networks.dogecoin.estimateFee it('regular fee per kb applies when every output has value no less than DUST_SOFT_LIMIT', function() { - var tx = Transaction.fromHex(fixtureTx1Hex) - tx.outs.forEach(function(e){ - e.value = 100000000 - }) + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() + tx.outs[0] = { value: 100000000 } assert.equal(estimateFee(tx), 100000000) }) it('applies additional fee on every output with value below DUST_SOFT_LIMIT', function() { - var tx = Transaction.fromHex(fixtureTx1Hex) - tx.outs.forEach(function(e){ - e.value = 99999999 - }) + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() + tx.outs[0] = { value: 99999999 } + tx.outs[1] = { value: 99999999 } - assert.equal(estimateFee(tx), 4 * 100000000) // 3 outs in total + assert.equal(estimateFee(tx), 3 * 100000000) }) - it('works for fixture big tx', function() { - var tx = Transaction.fromHex(fixtureTxBigHex) - tx.outs.forEach(function(e){ - e.value = 100000000 - }) + it('rounds up to the closest kb for estimation', function() { + txToBuffer.returns(new Buffer(2800)) + var tx = new Transaction() + assert.equal(estimateFee(tx), 300000000) }) }) @@ -60,28 +66,26 @@ describe('networks', function() { var estimateFee = networks.litecoin.estimateFee it('regular fee per kb applies when every output has value no less than DUST_SOFT_LIMIT', function() { - var tx = Transaction.fromHex(fixtureTx1Hex) - tx.outs.forEach(function(e){ - e.value = 100000 - }) + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() + tx.outs[0] = { value: 100000 } assert.equal(estimateFee(tx), 100000) }) it('applies additional fee on every output with value below DUST_SOFT_LIMIT', function() { - var tx = Transaction.fromHex(fixtureTx1Hex) - tx.outs.forEach(function(e){ - e.value = 99999 - }) + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() + tx.outs[0] = { value: 99999 } + tx.outs[1] = { value: 99999 } - assert.equal(estimateFee(tx), 4 * 100000) // 3 outs in total + assert.equal(estimateFee(tx), 3 * 100000) }) - it('works for fixture big tx', function() { - var tx = Transaction.fromHex(fixtureTxBigHex) - tx.outs.forEach(function(e){ - e.value = 100000 - }) + it('rounds up to the closest kb for estimation', function() { + txToBuffer.returns(new Buffer(2800)) + var tx = new Transaction() + assert.equal(estimateFee(tx), 300000) }) })