From 0f9674e65ff1980ffeb4a46b6b38152d925e425c Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Mon, 18 Aug 2014 10:47:39 +1000 Subject: [PATCH 1/9] tests: code formatting --- test/wallet.js | 164 ++++++++++++++++++++++++------------------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/test/wallet.js b/test/wallet.js index 9c002fe..8e30d33 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -28,13 +28,13 @@ function fakeTxId(i) { describe('Wallet', function() { var seed - beforeEach(function(){ + beforeEach(function() { seed = crypto.sha256("don't use a string seed like this in real life") }) describe('constructor', function() { var wallet - beforeEach(function(){ + beforeEach(function() { wallet = new Wallet(seed) }) @@ -60,8 +60,8 @@ describe('Wallet', function() { assert.equal(account.depth, 2) }) - describe('when seed is not specified', function(){ - it('generates a seed', function(){ + describe('when seed is not specified', function() { + it('generates a seed', function() { var wallet = new Wallet() assert(wallet.getMasterKey()) }) @@ -78,8 +78,8 @@ describe('Wallet', function() { }) }) - describe('newMasterKey', function(){ - it('resets accounts', function(){ + describe('newMasterKey', function() { + it('resets accounts', function() { var wallet = new Wallet() var oldAccountZero = wallet.getAccountZero() var oldExternalAccount = wallet.getExternalAccount() @@ -91,7 +91,7 @@ describe('Wallet', function() { assertNotEqual(wallet.getInternalAccount(), oldInternalAccount) }) - it('resets addresses', function(){ + it('resets addresses', function() { var wallet = new Wallet() wallet.generateAddress() wallet.generateChangeAddress() @@ -106,8 +106,8 @@ describe('Wallet', function() { }) }) - describe('generateAddress', function(){ - it('generate receiving addresses', function(){ + describe('generateAddress', function() { + it('generate receiving addresses', function() { var wallet = new Wallet(seed, networks.testnet) var expectedAddresses = [ "n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa", @@ -120,13 +120,13 @@ describe('Wallet', function() { }) }) - describe('generateChangeAddress', function(){ + describe('generateChangeAddress', function() { var wallet - beforeEach(function(){ + beforeEach(function() { wallet = new Wallet(seed) }) - it('generates change addresses', function(){ + it('generates change addresses', function() { var wallet = new Wallet(seed, networks.testnet) var expectedAddresses = ["mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"] @@ -135,13 +135,13 @@ describe('Wallet', function() { }) }) - describe('getPrivateKey', function(){ + describe('getPrivateKey', function() { var wallet - beforeEach(function(){ + beforeEach(function() { wallet = new Wallet(seed) }) - it('returns the private key at the given index of external account', function(){ + it('returns the private key at the given index of external account', function() { var wallet = new Wallet(seed, networks.testnet) assertEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).privKey) @@ -149,13 +149,13 @@ describe('Wallet', function() { }) }) - describe('getInternalPrivateKey', function(){ + describe('getInternalPrivateKey', function() { var wallet - beforeEach(function(){ + beforeEach(function() { wallet = new Wallet(seed) }) - it('returns the private key at the given index of internal account', function(){ + it('returns the private key at the given index of internal account', function() { var wallet = new Wallet(seed, networks.testnet) assertEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).privKey) @@ -163,13 +163,13 @@ describe('Wallet', function() { }) }) - describe('getPrivateKeyForAddress', function(){ + describe('getPrivateKeyForAddress', function() { var wallet - beforeEach(function(){ + beforeEach(function() { wallet = new Wallet(seed) }) - it('returns the private key for the given address', function(){ + it('returns the private key for the given address', function() { var wallet = new Wallet(seed, networks.testnet) wallet.generateChangeAddress() wallet.generateAddress() @@ -185,7 +185,7 @@ describe('Wallet', function() { ) }) - it('raises an error when address is not found', function(){ + it('raises an error when address is not found', function() { var wallet = new Wallet(seed, networks.testnet) assert.throws(function() { @@ -194,11 +194,11 @@ describe('Wallet', function() { }) }) - describe('Unspent Outputs', function(){ + describe('Unspent Outputs', function() { var utxo, expectedOutputKey var wallet - beforeEach(function(){ + beforeEach(function() { utxo = { "address" : "1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv", "hash": fakeTxId(6), @@ -210,12 +210,12 @@ describe('Wallet', function() { expectedOutputKey = utxo.hash + ":" + utxo.index }) - describe('on construction', function(){ - beforeEach(function(){ + describe('on construction', function() { + beforeEach(function() { wallet = new Wallet(seed, networks.bitcoin, [utxo]) }) - it('matches the expected behaviour', function(){ + it('matches the expected behaviour', function() { var output = wallet.outputs[expectedOutputKey] assert(output) @@ -224,29 +224,29 @@ describe('Wallet', function() { }) }) - describe('getBalance', function(){ - beforeEach(function(){ + describe('getBalance', function() { + beforeEach(function() { var utxo1 = cloneObject(utxo) utxo1.hash = fakeTxId(5) wallet = new Wallet(seed, networks.bitcoin, [utxo, utxo1]) }) - it('sums over utxo values', function(){ + it('sums over utxo values', function() { assert.equal(wallet.getBalance(), 40000) }) }) - describe('getUnspentOutputs', function(){ - beforeEach(function(){ + describe('getUnspentOutputs', function() { + beforeEach(function() { wallet = new Wallet(seed, networks.bitcoin, [utxo]) }) - it('parses wallet outputs to the expected format', function(){ + it('parses wallet outputs to the expected format', function() { assert.deepEqual(wallet.getUnspentOutputs(), [utxo]) }) - it("ignores pending spending outputs (outputs with 'to' property)", function(){ + it("ignores pending spending outputs (outputs with 'to' property)", function() { var output = wallet.outputs[expectedOutputKey] output.to = fakeTxId(0) + ':' + 0 output.pending = true @@ -256,11 +256,11 @@ describe('Wallet', function() { }) // FIXME: remove in 2.x.y - describe('setUnspentOutputs', function(){ + describe('setUnspentOutputs', function() { var utxo var expectedOutputKey - beforeEach(function(){ + beforeEach(function() { utxo = { hash: fakeTxId(0), index: 0, @@ -273,7 +273,7 @@ describe('Wallet', function() { wallet = new Wallet(seed, networks.bitcoin) }) - it('matches the expected behaviour', function(){ + it('matches the expected behaviour', function() { wallet.setUnspentOutputs([utxo]) var output = wallet.outputs[expectedOutputKey] @@ -282,9 +282,9 @@ describe('Wallet', function() { assert.equal(output.address, utxo.address) }) - describe('required fields', function(){ + describe('required fields', function() { ['index', 'address', 'hash', 'value'].forEach(function(field){ - it("throws an error when " + field + " is missing", function(){ + it("throws an error when " + field + " is missing", function() { delete utxo[field] assert.throws(function() { @@ -295,16 +295,16 @@ describe('Wallet', function() { }) }) - describe('Process transaction', function(){ + describe('Process transaction', function() { var wallet - beforeEach(function(){ + beforeEach(function() { wallet = new Wallet(seed) }) var addresses var tx - beforeEach(function(){ + beforeEach(function() { addresses = [ '115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi', '1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd', @@ -314,24 +314,24 @@ describe('Wallet', function() { tx = Transaction.fromHex(fixtureTx1Hex) }) - describe("processPendingTx", function(){ - it("incoming: sets the pending flag on output", function(){ + describe("processPendingTx", function() { + it("incoming: sets the pending flag on output", function() { wallet.addresses = [addresses[0]] wallet.processPendingTx(tx) verifyOutputAdded(0, true) }) - describe("when tx ins outpoint contains a known txhash:i", function(){ + describe("when tx ins outpoint contains a known txhash:i", function() { var spendTx - beforeEach(function(){ + beforeEach(function() { wallet.addresses = [addresses[0]] wallet.processConfirmedTx(tx) spendTx = Transaction.fromHex(fixtureTx2Hex) }) - it("outgoing: sets the pending flag and 'to' on output", function(){ + it("outgoing: sets the pending flag and 'to' on output", function() { var txIn = spendTx.ins[0] var txInId = new Buffer(txIn.hash) Array.prototype.reverse.call(txInId) @@ -347,7 +347,7 @@ describe('Wallet', function() { }) }) - describe('processConfirmedTx', function(){ + describe('processConfirmedTx', function() { it('does not throw on scripts with no corresponding Address', function() { var pubKey = wallet.getPrivateKey(0).pub var script = scripts.pubKeyOutput(pubKey) @@ -359,8 +359,8 @@ describe('Wallet', function() { wallet.processConfirmedTx(tx2) }) - describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function(){ - it("works for receive address", function(){ + describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function() { + it("works for receive address", function() { var totalOuts = outputCount() wallet.addresses = [addresses[0]] @@ -370,7 +370,7 @@ describe('Wallet', function() { verifyOutputAdded(0, false) }) - it("works for change address", function(){ + it("works for change address", function() { var totalOuts = outputCount() wallet.changeAddresses = [addresses[1]] @@ -380,26 +380,26 @@ describe('Wallet', function() { verifyOutputAdded(1, false) }) - function outputCount(){ + function outputCount() { return Object.keys(wallet.outputs).length } }) - describe("when tx ins outpoint contains a known txhash:i", function(){ + describe("when tx ins outpoint contains a known txhash:i", function() { var spendTx - beforeEach(function(){ + beforeEach(function() { wallet.addresses = [addresses[0]] // the address fixtureTx2 used as input wallet.processConfirmedTx(tx) spendTx = Transaction.fromHex(fixtureTx2Hex) }) - it("does not add to wallet.outputs", function(){ + it("does not add to wallet.outputs", function() { wallet.processConfirmedTx(spendTx) assert.deepEqual(wallet.outputs, {}) }) - it("deletes corresponding 'output'", function(){ + it("deletes corresponding 'output'", function() { var txIn = spendTx.ins[0] var txInId = new Buffer(txIn.hash) Array.prototype.reverse.call(txInId) @@ -413,7 +413,7 @@ describe('Wallet', function() { }) }) - it("does nothing when none of the involved addresses belong to the wallet", function(){ + it("does nothing when none of the involved addresses belong to the wallet", function() { wallet.processConfirmedTx(tx) assert.deepEqual(wallet.outputs, {}) }) @@ -432,12 +432,12 @@ describe('Wallet', function() { } }) - describe('createTx', function(){ + describe('createTx', function() { var wallet var address1, address2 var to, value - beforeEach(function(){ + beforeEach(function() { to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' value = 500000 @@ -471,15 +471,15 @@ describe('Wallet', function() { wallet.generateAddress() }) - describe('transaction fee', function(){ - it('allows fee to be specified', function(){ + describe('transaction fee', function() { + it('allows fee to be specified', function() { var fee = 30000 var tx = wallet.createTx(to, value, fee) assert.equal(getFee(wallet, tx), fee) }) - it('allows fee to be set to zero', function(){ + it('allows fee to be set to zero', function() { value = 510000 var fee = 0 var tx = wallet.createTx(to, value, fee) @@ -487,7 +487,7 @@ describe('Wallet', function() { assert.equal(getFee(wallet, tx), fee) }) - it('does not overestimate fees when network has dustSoftThreshold', function(){ + it('does not overestimate fees when network has dustSoftThreshold', function() { var utxo = { hash: fakeTxId(0), index: 0, @@ -516,8 +516,8 @@ describe('Wallet', function() { } }) - describe('choosing utxo', function(){ - it('takes fees into account', function(){ + describe('choosing utxo', function() { + it('takes fees into account', function() { var tx = wallet.createTx(to, value) assert.equal(tx.ins.length, 1) @@ -525,7 +525,7 @@ describe('Wallet', function() { assert.equal(tx.ins[0].index, 0) }) - it('uses confirmed outputs', function(){ + it('uses confirmed outputs', function() { var tx2 = new Transaction() tx2.addInput(fakeTxId(4), 0) tx2.addOutput(address2, 530000) @@ -538,7 +538,7 @@ describe('Wallet', function() { assert.equal(tx.ins[0].index, 0) }) - it('ignores pending outputs', function(){ + it('ignores pending outputs', function() { var tx2 = new Transaction() tx2.addInput(fakeTxId(4), 0) tx2.addOutput(address2, 530000) @@ -552,8 +552,8 @@ describe('Wallet', function() { }) }) - describe('changeAddress', function(){ - it('should allow custom changeAddress', function(){ + describe('changeAddress', function() { + it('should allow custom changeAddress', function() { var changeAddress = 'mfrFjnKZUvTcvdAK2fUX5D8v1Epu5H8JCk' var fromValue = 510000 var toValue = fromValue / 2 @@ -573,8 +573,8 @@ describe('Wallet', function() { }) }) - describe('transaction outputs', function(){ - it('includes the specified address and amount', function(){ + describe('transaction outputs', function() { + it('includes the specified address and amount', function() { var tx = wallet.createTx(to, value) assert.equal(tx.outs.length, 1) @@ -585,8 +585,8 @@ describe('Wallet', function() { assert.equal(out.value, value) }) - describe('change', function(){ - it('uses the last change address if there is any', function(){ + describe('change', function() { + it('uses the last change address if there is any', function() { var fee = 0 wallet.generateChangeAddress() wallet.generateChangeAddress() @@ -600,7 +600,7 @@ describe('Wallet', function() { assert.equal(out.value, 10000) }) - it('generates a change address if there is not any', function(){ + it('generates a change address if there is not any', function() { var fee = 0 assert.equal(wallet.changeAddresses.length, 0) @@ -614,7 +614,7 @@ describe('Wallet', function() { assert.equal(out.value, 10000) }) - it('skips change if it is not above dust threshold', function(){ + it('skips change if it is not above dust threshold', function() { var fee = 14570 var tx = wallet.createTx(to, value) assert.equal(tx.outs.length, 1) @@ -622,12 +622,12 @@ describe('Wallet', function() { }) }) - describe('signing', function(){ - afterEach(function(){ + describe('signing', function() { + afterEach(function() { TransactionBuilder.prototype.sign.restore() }) - it('signs the inputs with respective keys', function(){ + it('signs the inputs with respective keys', function() { var fee = 30000 sinon.spy(TransactionBuilder.prototype, "sign") @@ -638,8 +638,8 @@ describe('Wallet', function() { }) }) - describe('when value is below dust threshold', function(){ - it('throws an error', function(){ + describe('when value is below dust threshold', function() { + it('throws an error', function() { var value = 546 assert.throws(function() { @@ -648,8 +648,8 @@ describe('Wallet', function() { }) }) - describe('when there is not enough money', function(){ - it('throws an error', function(){ + describe('when there is not enough money', function() { + it('throws an error', function() { var value = 1400001 assert.throws(function() { From 735feab7babebb6f14e0f3c4484bf774832dcb78 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 17 Aug 2014 15:16:45 +1000 Subject: [PATCH 2/9] Wallet: remove txId:index storage for spent outputs --- src/wallet.js | 13 +++++++++---- test/wallet.js | 13 +++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index af28be0..8177a35 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -96,7 +96,7 @@ Wallet.prototype.processConfirmedTx = function(tx){ } Wallet.prototype.__processTx = function(tx, isPending) { - var txid = tx.getId() + var txId = tx.getId() tx.outs.forEach(function(txOut, i) { var address @@ -109,7 +109,7 @@ Wallet.prototype.__processTx = function(tx, isPending) { var myAddresses = this.addresses.concat(this.changeAddresses) if (myAddresses.indexOf(address) > -1) { - var output = txid + ':' + i + var output = txId + ':' + i this.outputs[output] = { from: output, @@ -131,8 +131,9 @@ Wallet.prototype.__processTx = function(tx, isPending) { if (!(output in this.outputs)) return if (isPending) { - this.outputs[output].to = txid + ':' + i this.outputs[output].pending = true + this.outputs[output].spent = true + } else { delete this.outputs[output] } @@ -206,7 +207,11 @@ Wallet.prototype.getUnspentOutputs = function() { for(var key in this.outputs){ var output = this.outputs[key] - if(!output.to) utxo.push(outputToUnspentOutput(output)) + + // Don't include pending spent outputs + if (!output.spent) { + utxo.push(outputToUnspentOutput(output)) + } } return utxo diff --git a/test/wallet.js b/test/wallet.js index 8e30d33..9917c83 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -246,10 +246,10 @@ describe('Wallet', function() { assert.deepEqual(wallet.getUnspentOutputs(), [utxo]) }) - it("ignores pending spending outputs (outputs with 'to' property)", function() { + it("ignores pending spending outputs (outputs with 'spent' property)", function() { var output = wallet.outputs[expectedOutputKey] - output.to = fakeTxId(0) + ':' + 0 output.pending = true + output.spent = true assert.deepEqual(wallet.getUnspentOutputs(), []) }) }) @@ -331,18 +331,19 @@ describe('Wallet', function() { spendTx = Transaction.fromHex(fixtureTx2Hex) }) - it("outgoing: sets the pending flag and 'to' on output", function() { + it("outgoing: sets the pending flag and 'spent' on output", function() { var txIn = spendTx.ins[0] var txInId = new Buffer(txIn.hash) Array.prototype.reverse.call(txInId) txInId = txInId.toString('hex') var key = txInId + ':' + txIn.index - assert(!wallet.outputs[key].pending) + var output = wallet.outputs[key] + assert(!output.pending) wallet.processPendingTx(spendTx) - assert(wallet.outputs[key].pending) - assert.equal(wallet.outputs[key].to, spendTx.getId() + ':' + 0) + assert(output.pending) + assert(output.spent, true) }) }) }) From 33955a7fb5a2bacb2cfe2237a15bab3f8db6284f Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 17 Aug 2014 16:42:00 +1000 Subject: [PATCH 3/9] Wallet: store txHash, vout separately instead of "from: txid:vout" --- src/wallet.js | 28 ++++++++++++++++++---------- test/wallet.js | 3 ++- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index 8177a35..26f8bfd 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -64,8 +64,7 @@ Wallet.prototype.createTx = function(to, value, fixedFee, changeAddress) { var utxo = utxos[i] addresses.push(utxo.address) - var outpoint = utxo.from.split(':') - txb.addInput(outpoint[0], parseInt(outpoint[1])) + txb.addInput(utxo.hash, utxo.index) var fee = fixedFee === undefined ? estimatePaddedFee(txb.buildIncomplete(), this.network) : fixedFee @@ -97,6 +96,7 @@ Wallet.prototype.processConfirmedTx = function(tx){ Wallet.prototype.__processTx = function(tx, isPending) { var txId = tx.getId() + var txHash = tx.getHash() tx.outs.forEach(function(txOut, i) { var address @@ -112,7 +112,8 @@ Wallet.prototype.__processTx = function(tx, isPending) { var output = txId + ':' + i this.outputs[output] = { - from: output, + hash: txHash, + index: i, value: txOut.value, address: address, pending: isPending @@ -233,12 +234,15 @@ Wallet.prototype.signWith = function(txb, addresses) { return txb } -function outputToUnspentOutput(output){ - var hashAndIndex = output.from.split(":") +function outputToUnspentOutput(output) { + var txid = new Buffer(output.hash) + + // hash is little-endian, we want big-endian + Array.prototype.reverse.call(txid) return { - hash: hashAndIndex[0], - index: parseInt(hashAndIndex[1]), + hash: txid.toString('hex'), + index: output.index, address: output.address, value: output.value, pending: output.pending @@ -271,11 +275,15 @@ function processUnspentOutputs(utxos) { var key = utxo.hash + ':' + utxo.index + // little-endian hash is what we use internally + Array.prototype.reverse(hash) + outputs[key] = { - from: key, address: address, - value: value, - pending: utxo.pending + hash: hash, + index: utxo.index, + pending: utxo.pending, + value: value } }) diff --git a/test/wallet.js b/test/wallet.js index 9917c83..1333926 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -422,9 +422,10 @@ describe('Wallet', function() { function verifyOutputAdded(index, pending) { var txOut = tx.outs[index] + var key = tx.getId() + ":" + index var output = wallet.outputs[key] - assert.equal(output.from, key) + assert.deepEqual(output.hash, tx.getHash()) assert.equal(output.value, txOut.value) assert.equal(output.pending, pending) From 71d4c78b884212edf9c356c0b736b19d2fde9c2e Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 17 Aug 2014 17:24:48 +1000 Subject: [PATCH 4/9] bufferutils: add Buffer reverse --- src/bufferutils.js | 7 +++++++ src/transaction.js | 12 +++--------- src/wallet.js | 6 ++---- test/bufferutils.js | 13 +++++++++++++ 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/bufferutils.js b/src/bufferutils.js index 05364e1..858a934 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -159,11 +159,18 @@ function writeVarInt(buffer, number, offset) { return size } +function reverse(buffer) { + var buffer2 = new Buffer(buffer) + Array.prototype.reverse.call(buffer2) + return buffer2 +} + module.exports = { pushDataSize: pushDataSize, readPushDataInt: readPushDataInt, readUInt64LE: readUInt64LE, readVarInt: readVarInt, + reverse: reverse, varIntSize: varIntSize, writePushDataInt: writePushDataInt, writeUInt64LE: writeUInt64LE, diff --git a/src/transaction.js b/src/transaction.js index a9a0899..91be756 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -37,10 +37,8 @@ Transaction.prototype.addInput = function(tx, index, sequence) { var hash if (typeof tx === 'string') { - hash = new Buffer(tx, 'hex') - // TxId hex is big-endian, we need little-endian - Array.prototype.reverse.call(hash) + hash = bufferutils.reverse(new Buffer(tx, 'hex')) } else if (tx instanceof Transaction) { hash = tx.getHash() @@ -211,12 +209,8 @@ Transaction.prototype.getHash = function () { } Transaction.prototype.getId = function () { - var buffer = this.getHash() - - // Big-endian is used for TxHash - Array.prototype.reverse.call(buffer) - - return buffer.toString('hex') + // TxHash is little-endian, we need big-endian + return bufferutils.reverse(this.getHash()).toString('hex') } Transaction.prototype.clone = function () { diff --git a/src/wallet.js b/src/wallet.js index 26f8bfd..f3f0adf 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -1,4 +1,5 @@ var assert = require('assert') +var bufferutils = require('./bufferutils') var crypto = require('crypto') var networks = require('./networks') @@ -123,10 +124,7 @@ Wallet.prototype.__processTx = function(tx, isPending) { tx.ins.forEach(function(txIn, i) { // copy and convert to big-endian hex - var txinId = new Buffer(txIn.hash) - Array.prototype.reverse.call(txinId) - txinId = txinId.toString('hex') - + var txinId = bufferutils.reverse(txIn.hash).toString('hex') var output = txinId + ':' + txIn.index if (!(output in this.outputs)) return diff --git a/test/bufferutils.js b/test/bufferutils.js index d7dbfa3..447d516 100644 --- a/test/bufferutils.js +++ b/test/bufferutils.js @@ -75,6 +75,19 @@ describe('bufferutils', function() { }) }) + describe('reverse', function() { + fixtures.valid.forEach(function(f) { + it('reverses ' + f.hex64 + ' correctly', function() { + var buffer = new Buffer(f.hex64, 'hex') + var buffer2 = bufferutils.reverse(buffer) + + Array.prototype.reverse.call(buffer) + + assert.deepEqual(buffer, buffer2) + }) + }) + }) + describe('varIntSize', function() { fixtures.valid.forEach(function(f) { it('determines the varIntSize of ' + f.dec + ' correctly', function() { From 02e71e430c0c2ca6ca5423842587c0c79c3580db Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 17 Aug 2014 17:35:07 +1000 Subject: [PATCH 5/9] Wallet: revert 2f00c9a --- src/wallet.js | 96 +++++++++++++++++++++----------------------------- test/wallet.js | 15 +++++--- 2 files changed, 50 insertions(+), 61 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index f3f0adf..f9b93cf 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -8,7 +8,7 @@ var HDNode = require('./hdnode') var TransactionBuilder = require('./transaction_builder') var Script = require('./script') -function Wallet(seed, network, unspents) { +function Wallet(seed, network) { seed = seed || crypto.randomBytes(32) network = network || networks.bitcoin @@ -24,7 +24,7 @@ function Wallet(seed, network, unspents) { this.addresses = [] this.changeAddresses = [] this.network = network - this.outputs = unspents ? processUnspentOutputs(unspents) : {} + this.outputs = {} // FIXME: remove in 2.x.y var me = this @@ -202,63 +202,35 @@ Wallet.prototype.getReceiveAddress = function() { } Wallet.prototype.getUnspentOutputs = function() { - var utxo = [] + var utxos = [] - for(var key in this.outputs){ + for (var key in this.outputs) { var output = this.outputs[key] // Don't include pending spent outputs if (!output.spent) { - utxo.push(outputToUnspentOutput(output)) + // hash is little-endian, we want big-endian + var txid = bufferutils.reverse(output.hash) + + utxos.push({ + hash: txid.toString('hex'), + index: output.index, + address: output.address, + value: output.value, + pending: output.pending + }) } } - return utxo + return utxos } -Wallet.prototype.setUnspentOutputs = function(utxo) { - console.warn('setUnspentOutputs is deprecated, please use the constructor option instead') +Wallet.prototype.setUnspentOutputs = function(utxos) { + utxos.forEach(function(utxo) { + var txid = utxo.hash + assert.equal(typeof txid, 'string', 'Expected txId, got ' + txid) - this.outputs = processUnspentOutputs(utxo) -} - -Wallet.prototype.signWith = function(txb, addresses) { - addresses.forEach(function(address, i) { - var privKey = this.getPrivateKeyForAddress(address) - - txb.sign(i, privKey) - }, this) - - return txb -} - -function outputToUnspentOutput(output) { - var txid = new Buffer(output.hash) - - // hash is little-endian, we want big-endian - Array.prototype.reverse.call(txid) - - return { - hash: txid.toString('hex'), - index: output.index, - address: output.address, - value: output.value, - pending: output.pending - } -} - -function estimatePaddedFee(tx, network) { - var tmpTx = tx.clone() - tmpTx.addOutput(Script.EMPTY, network.dustSoftThreshold || 0) - - return network.estimateFee(tmpTx) -} - -function processUnspentOutputs(utxos) { - var outputs = {} - - utxos.forEach(function(utxo){ - var hash = new Buffer(utxo.hash, 'hex') + var hash = bufferutils.reverse(new Buffer(txid, 'hex')) var index = utxo.index var address = utxo.address var value = utxo.value @@ -271,21 +243,33 @@ function processUnspentOutputs(utxos) { assert.doesNotThrow(function() { Address.fromBase58Check(address) }, 'Expected Base58 Address, got ' + address) assert.equal(typeof value, 'number', 'Expected number value, got ' + value) - var key = utxo.hash + ':' + utxo.index + var key = txid + ':' + index - // little-endian hash is what we use internally - Array.prototype.reverse(hash) - - outputs[key] = { + this.outputs[key] = { address: address, hash: hash, - index: utxo.index, + index: index, pending: utxo.pending, value: value } - }) + }, this) +} - return outputs +Wallet.prototype.signWith = function(tx, addresses) { + addresses.forEach(function(address, i) { + var privKey = this.getPrivateKeyForAddress(address) + + tx.sign(i, privKey) + }, this) + + return tx +} + +function estimatePaddedFee(tx, network) { + var tmpTx = tx.clone() + tmpTx.addOutput(Script.EMPTY, network.dustSoftThreshold || 0) + + return network.estimateFee(tmpTx) } function getCandidateOutputs(outputs/*, value*/) { diff --git a/test/wallet.js b/test/wallet.js index 1333926..c82c127 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -212,7 +212,8 @@ describe('Wallet', function() { describe('on construction', function() { beforeEach(function() { - wallet = new Wallet(seed, networks.bitcoin, [utxo]) + wallet = new Wallet(seed, networks.bitcoin) + wallet.setUnspentOutputs([utxo]) }) it('matches the expected behaviour', function() { @@ -229,7 +230,8 @@ describe('Wallet', function() { var utxo1 = cloneObject(utxo) utxo1.hash = fakeTxId(5) - wallet = new Wallet(seed, networks.bitcoin, [utxo, utxo1]) + wallet = new Wallet(seed, networks.bitcoin) + wallet.setUnspentOutputs([utxo, utxo1]) }) it('sums over utxo values', function() { @@ -239,7 +241,8 @@ describe('Wallet', function() { describe('getUnspentOutputs', function() { beforeEach(function() { - wallet = new Wallet(seed, networks.bitcoin, [utxo]) + wallet = new Wallet(seed, networks.bitcoin) + wallet.setUnspentOutputs([utxo]) }) it('parses wallet outputs to the expected format', function() { @@ -468,7 +471,8 @@ describe('Wallet', function() { } ] - wallet = new Wallet(seed, networks.testnet, utxos) + wallet = new Wallet(seed, networks.testnet) + wallet.setUnspentOutputs(utxos) wallet.generateAddress() wallet.generateAddress() }) @@ -497,7 +501,8 @@ describe('Wallet', function() { value: 500000 } - var wallet = new Wallet(seed, networks.litecoin, [utxo]) + var wallet = new Wallet(seed, networks.litecoin) + wallet.setUnspentOutputs([utxo]) wallet.generateAddress() value = 200000 From d24fdef585043f5e29f4568f0988d0dc46f96e28 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Sun, 17 Aug 2014 17:39:06 +1000 Subject: [PATCH 6/9] Wallet: consistent variable naming --- src/wallet.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index f9b93cf..f4faf7f 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -210,10 +210,10 @@ Wallet.prototype.getUnspentOutputs = function() { // Don't include pending spent outputs if (!output.spent) { // hash is little-endian, we want big-endian - var txid = bufferutils.reverse(output.hash) + var txId = bufferutils.reverse(output.hash) utxos.push({ - hash: txid.toString('hex'), + hash: txId.toString('hex'), index: output.index, address: output.address, value: output.value, @@ -227,10 +227,10 @@ Wallet.prototype.getUnspentOutputs = function() { Wallet.prototype.setUnspentOutputs = function(utxos) { utxos.forEach(function(utxo) { - var txid = utxo.hash - assert.equal(typeof txid, 'string', 'Expected txId, got ' + txid) + var txId = utxo.hash + assert.equal(typeof txId, 'string', 'Expected txId, got ' + txId) - var hash = bufferutils.reverse(new Buffer(txid, 'hex')) + var hash = bufferutils.reverse(new Buffer(txId, 'hex')) var index = utxo.index var address = utxo.address var value = utxo.value @@ -243,9 +243,9 @@ Wallet.prototype.setUnspentOutputs = function(utxos) { assert.doesNotThrow(function() { Address.fromBase58Check(address) }, 'Expected Base58 Address, got ' + address) assert.equal(typeof value, 'number', 'Expected number value, got ' + value) - var key = txid + ':' + index + var output = txId + ':' + index - this.outputs[key] = { + this.outputs[output] = { address: address, hash: hash, index: index, @@ -273,18 +273,20 @@ function estimatePaddedFee(tx, network) { } function getCandidateOutputs(outputs/*, value*/) { - var unspent = [] + var unspents = [] for (var key in outputs) { var output = outputs[key] - if (!output.pending) unspent.push(output) + + if (!output.pending) { + unspents.push(output) + } } - var sortByValueDesc = unspent.sort(function(o1, o2){ + // sorted by descending value + return unspents.sort(function(o1, o2) { return o2.value - o1.value }) - - return sortByValueDesc } module.exports = Wallet From 06f13db8d752fa3805db612a675a852dbc9a1413 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Mon, 18 Aug 2014 15:18:20 +1000 Subject: [PATCH 7/9] Wallet: rename outputs to unspentsMap --- src/wallet.js | 22 +++++++++++----------- test/wallet.js | 26 +++++++++++++------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index f4faf7f..00a0857 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -24,7 +24,7 @@ function Wallet(seed, network) { this.addresses = [] this.changeAddresses = [] this.network = network - this.outputs = {} + this.unspentMap = {} // FIXME: remove in 2.x.y var me = this @@ -41,7 +41,7 @@ function Wallet(seed, network) { me.addresses = [] me.changeAddresses = [] - me.outputs = {} + me.unspentMap = {} } this.getMasterKey = function() { return masterKey } @@ -53,7 +53,7 @@ function Wallet(seed, network) { Wallet.prototype.createTx = function(to, value, fixedFee, changeAddress) { assert(value > this.network.dustThreshold, value + ' must be above dust threshold (' + this.network.dustThreshold + ' Satoshis)') - var utxos = getCandidateOutputs(this.outputs, value) + var utxos = getCandidateOutputs(this.unspentMap, value) var accum = 0 var subTotal = value var addresses = [] @@ -112,7 +112,7 @@ Wallet.prototype.__processTx = function(tx, isPending) { if (myAddresses.indexOf(address) > -1) { var output = txId + ':' + i - this.outputs[output] = { + this.unspentMap[output] = { hash: txHash, index: i, value: txOut.value, @@ -127,14 +127,14 @@ Wallet.prototype.__processTx = function(tx, isPending) { var txinId = bufferutils.reverse(txIn.hash).toString('hex') var output = txinId + ':' + txIn.index - if (!(output in this.outputs)) return + if (!(output in this.unspentMap)) return if (isPending) { - this.outputs[output].pending = true - this.outputs[output].spent = true + this.unspentMap[output].pending = true + this.unspentMap[output].spent = true } else { - delete this.outputs[output] + delete this.unspentMap[output] } }, this) } @@ -204,8 +204,8 @@ Wallet.prototype.getReceiveAddress = function() { Wallet.prototype.getUnspentOutputs = function() { var utxos = [] - for (var key in this.outputs) { - var output = this.outputs[key] + for (var key in this.unspentMap) { + var output = this.unspentMap[key] // Don't include pending spent outputs if (!output.spent) { @@ -245,7 +245,7 @@ Wallet.prototype.setUnspentOutputs = function(utxos) { var output = txId + ':' + index - this.outputs[output] = { + this.unspentMap[output] = { address: address, hash: hash, index: index, diff --git a/test/wallet.js b/test/wallet.js index c82c127..917c3bf 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -217,7 +217,7 @@ describe('Wallet', function() { }) it('matches the expected behaviour', function() { - var output = wallet.outputs[expectedOutputKey] + var output = wallet.unspentMap[expectedOutputKey] assert(output) assert.equal(output.value, utxo.value) @@ -250,7 +250,7 @@ describe('Wallet', function() { }) it("ignores pending spending outputs (outputs with 'spent' property)", function() { - var output = wallet.outputs[expectedOutputKey] + var output = wallet.unspentMap[expectedOutputKey] output.pending = true output.spent = true assert.deepEqual(wallet.getUnspentOutputs(), []) @@ -279,7 +279,7 @@ describe('Wallet', function() { it('matches the expected behaviour', function() { wallet.setUnspentOutputs([utxo]) - var output = wallet.outputs[expectedOutputKey] + var output = wallet.unspentMap[expectedOutputKey] assert(output) assert.equal(output.value, utxo.value) assert.equal(output.address, utxo.address) @@ -341,7 +341,7 @@ describe('Wallet', function() { txInId = txInId.toString('hex') var key = txInId + ':' + txIn.index - var output = wallet.outputs[key] + var output = wallet.unspentMap[key] assert(!output.pending) wallet.processPendingTx(spendTx) @@ -363,7 +363,7 @@ describe('Wallet', function() { wallet.processConfirmedTx(tx2) }) - describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function() { + describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.unspentMap", function() { it("works for receive address", function() { var totalOuts = outputCount() @@ -385,7 +385,7 @@ describe('Wallet', function() { }) function outputCount() { - return Object.keys(wallet.outputs).length + return Object.keys(wallet.unspentMap).length } }) @@ -398,9 +398,9 @@ describe('Wallet', function() { spendTx = Transaction.fromHex(fixtureTx2Hex) }) - it("does not add to wallet.outputs", function() { + it("does not add to wallet.unspentMap", function() { wallet.processConfirmedTx(spendTx) - assert.deepEqual(wallet.outputs, {}) + assert.deepEqual(wallet.unspentMap, {}) }) it("deletes corresponding 'output'", function() { @@ -410,16 +410,16 @@ describe('Wallet', function() { txInId = txInId.toString('hex') var expected = txInId + ':' + txIn.index - assert(expected in wallet.outputs) + assert(expected in wallet.unspentMap) wallet.processConfirmedTx(spendTx) - assert(!(expected in wallet.outputs)) + assert(!(expected in wallet.unspentMap)) }) }) it("does nothing when none of the involved addresses belong to the wallet", function() { wallet.processConfirmedTx(tx) - assert.deepEqual(wallet.outputs, {}) + assert.deepEqual(wallet.unspentMap, {}) }) }) @@ -427,7 +427,7 @@ describe('Wallet', function() { var txOut = tx.outs[index] var key = tx.getId() + ":" + index - var output = wallet.outputs[key] + var output = wallet.unspentMap[key] assert.deepEqual(output.hash, tx.getHash()) assert.equal(output.value, txOut.value) assert.equal(output.pending, pending) @@ -514,7 +514,7 @@ describe('Wallet', function() { function getFee(wallet, tx) { var inputValue = tx.ins.reduce(function(memo, input){ var id = Array.prototype.reverse.call(input.hash).toString('hex') - return memo + wallet.outputs[id + ':' + input.index].value + return memo + wallet.unspentMap[id + ':' + input.index].value }, 0) return tx.outs.reduce(function(memo, output){ From b727a65ea0dde78b8806d8e1162e13b1fc03e6e5 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Mon, 18 Aug 2014 15:23:13 +1000 Subject: [PATCH 8/9] Wallet: refactor to use Array unspents solely, deprecating unspentsMap --- src/wallet.js | 236 +++++++++++++++++++++++++++++++------------------ test/wallet.js | 87 +++++++++--------- 2 files changed, 197 insertions(+), 126 deletions(-) diff --git a/src/wallet.js b/src/wallet.js index 00a0857..f25f7d8 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -24,9 +24,12 @@ function Wallet(seed, network) { this.addresses = [] this.changeAddresses = [] this.network = network + this.unspents = [] + + // FIXME: remove in 2.0.0 this.unspentMap = {} - // FIXME: remove in 2.x.y + // FIXME: remove in 2.0.0 var me = this this.newMasterKey = function(seed) { console.warn('newMasterKey is deprecated, please make a new Wallet instance instead') @@ -41,6 +44,7 @@ function Wallet(seed, network) { me.addresses = [] me.changeAddresses = [] + me.unspents = [] me.unspentMap = {} } @@ -50,27 +54,54 @@ function Wallet(seed, network) { this.getInternalAccount = function() { return internalAccount } } -Wallet.prototype.createTx = function(to, value, fixedFee, changeAddress) { +Wallet.prototype.createTransaction = function(to, value, options) { + // FIXME: remove in 2.0.0 + if (typeof options !== 'object') { + if (options !== undefined) { + console.warn('Non options object parameters are deprecated, use options object instead') + + options = { + fixedFee: arguments[2], + changeAddress: arguments[3] + } + } + } + + options = options || {} + assert(value > this.network.dustThreshold, value + ' must be above dust threshold (' + this.network.dustThreshold + ' Satoshis)') - var utxos = getCandidateOutputs(this.unspentMap, value) + var changeAddress = options.changeAddress + var fixedFee = options.fixedFee + var minConf = options.minConf === undefined ? 0 : options.minConf // FIXME: change minConf:1 by default in 2.0.0 + + // filter by minConf, then pending and sort by descending value + var unspents = this.unspents.filter(function(unspent) { + return unspent.confirmations >= minConf + }).filter(function(unspent) { + return !unspent.pending + }).sort(function(o1, o2) { + return o2.value - o1.value + }) + var accum = 0 - var subTotal = value var addresses = [] + var subTotal = value var txb = new TransactionBuilder() txb.addOutput(to, value) - for (var i = 0; i < utxos.length; ++i) { - var utxo = utxos[i] - addresses.push(utxo.address) + for (var i = 0; i < unspents.length; ++i) { + var unspent = unspents[i] + addresses.push(unspent.address) - txb.addInput(utxo.hash, utxo.index) + txb.addInput(unspent.txHash, unspent.index) var fee = fixedFee === undefined ? estimatePaddedFee(txb.buildIncomplete(), this.network) : fixedFee - accum += utxo.value + accum += unspent.value subTotal = value + fee + if (accum >= subTotal) { var change = accum - subTotal @@ -87,15 +118,20 @@ Wallet.prototype.createTx = function(to, value, fixedFee, changeAddress) { return this.signWith(txb, addresses).build() } +// FIXME: remove in 2.0.0 Wallet.prototype.processPendingTx = function(tx){ this.__processTx(tx, true) } +// FIXME: remove in 2.0.0 Wallet.prototype.processConfirmedTx = function(tx){ this.__processTx(tx, false) } +// FIXME: remove in 2.0.0 Wallet.prototype.__processTx = function(tx, isPending) { + console.warn('processTransaction is considered harmful, see issue #260 for more information') + var txId = tx.getId() var txHash = tx.getHash() @@ -110,31 +146,44 @@ Wallet.prototype.__processTx = function(tx, isPending) { var myAddresses = this.addresses.concat(this.changeAddresses) if (myAddresses.indexOf(address) > -1) { - var output = txId + ':' + i + var lookup = txId + ':' + i + if (lookup in this.unspentMap) return - this.unspentMap[output] = { - hash: txHash, - index: i, - value: txOut.value, + // its unique, add it + var unspent = { address: address, + confirmations: 0, // no way to determine this without more information + index: i, + txHash: txHash, + txId: txId, + value: txOut.value, pending: isPending } + + this.unspentMap[lookup] = unspent + this.unspents.push(unspent) } }, this) tx.ins.forEach(function(txIn, i) { // copy and convert to big-endian hex - var txinId = bufferutils.reverse(txIn.hash).toString('hex') - var output = txinId + ':' + txIn.index + var txInId = bufferutils.reverse(txIn.hash).toString('hex') - if (!(output in this.unspentMap)) return + var lookup = txInId + ':' + txIn.index + if (!(lookup in this.unspentMap)) return + + var unspent = this.unspentMap[lookup] if (isPending) { - this.unspentMap[output].pending = true - this.unspentMap[output].spent = true + unspent.pending = true + unspent.spent = true } else { - delete this.unspentMap[output] + delete this.unspentMap[lookup] + + this.unspents = this.unspents.filter(function(unspent2) { + return unspent !== unspent2 + }) } }, this) } @@ -157,9 +206,25 @@ Wallet.prototype.generateChangeAddress = function() { return this.getChangeAddress() } -Wallet.prototype.getBalance = function() { - return this.getUnspentOutputs().reduce(function(accum, output) { - return accum + output.value +Wallet.prototype.getAddress = function() { + if (this.addresses.length === 0) { + this.generateAddress() + } + + return this.addresses[this.addresses.length - 1] +} + +Wallet.prototype.getBalance = function(minConf) { + minConf = minConf || 0 + + return this.unspents.filter(function(unspent) { + return unspent.confirmations >= minConf + + // FIXME: remove spent filter in 2.0.0 + }).filter(function(unspent) { + return !unspent.spent + }).reduce(function(accum, unspent) { + return accum + unspent.value }, 0) } @@ -193,65 +258,77 @@ Wallet.prototype.getPrivateKeyForAddress = function(address) { assert(false, 'Unknown address. Make sure the address is from the keychain and has been generated') } -Wallet.prototype.getReceiveAddress = function() { - if (this.addresses.length === 0) { - this.generateAddress() - } +Wallet.prototype.getUnspentOutputs = function(minConf) { + minConf = minConf || 0 - return this.addresses[this.addresses.length - 1] -} + return this.unspents.filter(function(unspent) { + return unspent.confirmations >= minConf -Wallet.prototype.getUnspentOutputs = function() { - var utxos = [] + // FIXME: remove spent filter in 2.0.0 + }).filter(function(unspent) { + return !unspent.spent + }).map(function(unspent) { + return { + address: unspent.address, + confirmations: unspent.confirmations, + index: unspent.index, + txId: unspent.txId, + value: unspent.value, - for (var key in this.unspentMap) { - var output = this.unspentMap[key] - - // Don't include pending spent outputs - if (!output.spent) { - // hash is little-endian, we want big-endian - var txId = bufferutils.reverse(output.hash) - - utxos.push({ - hash: txId.toString('hex'), - index: output.index, - address: output.address, - value: output.value, - pending: output.pending - }) + // FIXME: remove in 2.0.0 + hash: unspent.txId, + pending: unspent.pending } - } - - return utxos + }) } -Wallet.prototype.setUnspentOutputs = function(utxos) { - utxos.forEach(function(utxo) { - var txId = utxo.hash - assert.equal(typeof txId, 'string', 'Expected txId, got ' + txId) +Wallet.prototype.setUnspentOutputs = function(unspents) { + this.unspentMap = {} + this.unspents = unspents.map(function(unspent) { + // FIXME: remove unspent.hash in 2.0.0 + var txId = unspent.txId || unspent.hash + var index = unspent.index - var hash = bufferutils.reverse(new Buffer(txId, 'hex')) - var index = utxo.index - var address = utxo.address - var value = utxo.value + // FIXME: remove in 2.0.0 + if (unspent.hash !== undefined) { + console.warn('unspent.hash is deprecated, use unspent.txId instead') + } - // FIXME: remove alternative in 2.x.y - if (index === undefined) index = utxo.outputIndex + // FIXME: remove in 2.0.0 + if (index === undefined) { + console.warn('unspent.outputIndex is deprecated, use unspent.index instead') + index = utxo.outputIndex + } - assert.equal(hash.length, 32, 'Expected hash length of 32, got ' + hash.length) + assert.equal(typeof txId, 'string', 'Expected txId, got ' + txId) + assert.equal(txId.length, 64, 'Expected valid txId, got ' + txId) + assert.doesNotThrow(function() { Address.fromBase58Check(unspent.address) }, 'Expected Base58 Address, got ' + unspent.address) assert.equal(typeof index, 'number', 'Expected number index, got ' + index) - assert.doesNotThrow(function() { Address.fromBase58Check(address) }, 'Expected Base58 Address, got ' + address) - assert.equal(typeof value, 'number', 'Expected number value, got ' + value) + assert.equal(typeof unspent.value, 'number', 'Expected number value, got ' + unspent.value) - var output = txId + ':' + index - - this.unspentMap[output] = { - address: address, - hash: hash, - index: index, - pending: utxo.pending, - value: value + // FIXME: remove branch in 2.0.0 + if (unspent.confirmations !== undefined) { + assert.equal(typeof unspent.confirmations, 'number', 'Expected number confirmations, got ' + unspent.confirmations) } + + var txHash = bufferutils.reverse(new Buffer(txId, 'hex')) + + var unspent = { + address: unspent.address, + confirmations: unspent.confirmations || 0, + index: index, + txHash: txHash, + txId: txId, + value: unspent.value, + + // FIXME: remove in 2.0.0 + pending: unspent.pending || false + } + + // FIXME: remove in 2.0.0 + this.unspentMap[txId + ':' + index] = unspent + + return unspent }, this) } @@ -272,21 +349,8 @@ function estimatePaddedFee(tx, network) { return network.estimateFee(tmpTx) } -function getCandidateOutputs(outputs/*, value*/) { - var unspents = [] - - for (var key in outputs) { - var output = outputs[key] - - if (!output.pending) { - unspents.push(output) - } - } - - // sorted by descending value - return unspents.sort(function(o1, o2) { - return o2.value - o1.value - }) -} +// FIXME: 1.0.0 shims, remove in 2.0.0 +Wallet.prototype.getReceiveAddress = Wallet.prototype.getAddress +Wallet.prototype.createTx = Wallet.prototype.createTransaction module.exports = Wallet diff --git a/test/wallet.js b/test/wallet.js index 917c3bf..f73d7be 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -1,4 +1,5 @@ var assert = require('assert') +var bufferutils = require('../src/bufferutils') var crypto = require('../src/crypto') var networks = require('../src/networks') var sinon = require('sinon') @@ -201,13 +202,12 @@ describe('Wallet', function() { beforeEach(function() { utxo = { "address" : "1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv", - "hash": fakeTxId(6), + "confirmations": 1, "index": 0, - "pending": true, - "value": 20000 + "txId": fakeTxId(6), + "value": 20000, + "pending": false } - - expectedOutputKey = utxo.hash + ":" + utxo.index }) describe('on construction', function() { @@ -217,11 +217,10 @@ describe('Wallet', function() { }) it('matches the expected behaviour', function() { - var output = wallet.unspentMap[expectedOutputKey] + var output = wallet.unspents[0] - assert(output) - assert.equal(output.value, utxo.value) assert.equal(output.address, utxo.address) + assert.equal(output.value, utxo.value) }) }) @@ -245,20 +244,32 @@ describe('Wallet', function() { wallet.setUnspentOutputs([utxo]) }) - it('parses wallet outputs to the expected format', function() { - assert.deepEqual(wallet.getUnspentOutputs(), [utxo]) + it('parses wallet unspents to the expected format', function() { + var outputs = wallet.getUnspentOutputs() + var output = outputs[0] + + assert.equal(utxo.address, output.address) + assert.equal(utxo.index, output.index) + assert.equal(utxo.value, output.value) + + // FIXME: remove in 2.0.0 + assert.equal(utxo.txId, output.hash) + assert.equal(utxo.pending, output.pending) + + // new in 2.0.0 + assert.equal(utxo.txId, output.txId) + assert.equal(utxo.confirmations, output.confirmations) }) - it("ignores pending spending outputs (outputs with 'spent' property)", function() { - var output = wallet.unspentMap[expectedOutputKey] - output.pending = true - output.spent = true + it("ignores spent unspents (outputs with 'spent' property)", function() { + var unspent = wallet.unspents[0] + unspent.pending = true + unspent.spent = true assert.deepEqual(wallet.getUnspentOutputs(), []) }) }) }) - // FIXME: remove in 2.x.y describe('setUnspentOutputs', function() { var utxo var expectedOutputKey @@ -271,16 +282,13 @@ describe('Wallet', function() { value: 500000 } - expectedOutputKey = utxo.hash + ":" + utxo.index - wallet = new Wallet(seed, networks.bitcoin) }) it('matches the expected behaviour', function() { wallet.setUnspentOutputs([utxo]) - var output = wallet.unspentMap[expectedOutputKey] - assert(output) + var output = wallet.unspents[0] assert.equal(output.value, utxo.value) assert.equal(output.address, utxo.address) }) @@ -340,13 +348,12 @@ describe('Wallet', function() { Array.prototype.reverse.call(txInId) txInId = txInId.toString('hex') - var key = txInId + ':' + txIn.index - var output = wallet.unspentMap[key] + var unspent = wallet.unspents[0] + assert(!unspent.pending) - assert(!output.pending) wallet.processPendingTx(spendTx) - assert(output.pending) - assert(output.spent, true) + assert(unspent.pending) + assert(unspent.spent, true) }) }) }) @@ -389,7 +396,7 @@ describe('Wallet', function() { } }) - describe("when tx ins outpoint contains a known txhash:i", function() { + describe("when tx ins contains a known txhash:i", function() { var spendTx beforeEach(function() { wallet.addresses = [addresses[0]] // the address fixtureTx2 used as input @@ -403,11 +410,9 @@ describe('Wallet', function() { assert.deepEqual(wallet.unspentMap, {}) }) - it("deletes corresponding 'output'", function() { + it("deletes corresponding 'unspent'", function() { var txIn = spendTx.ins[0] - var txInId = new Buffer(txIn.hash) - Array.prototype.reverse.call(txInId) - txInId = txInId.toString('hex') + var txInId = bufferutils.reverse(txIn.hash).toString('hex') var expected = txInId + ':' + txIn.index assert(expected in wallet.unspentMap) @@ -416,19 +421,20 @@ describe('Wallet', function() { assert(!(expected in wallet.unspentMap)) }) }) - - it("does nothing when none of the involved addresses belong to the wallet", function() { - wallet.processConfirmedTx(tx) - assert.deepEqual(wallet.unspentMap, {}) - }) }) + it("does nothing when none of the involved addresses belong to the wallet", function() { + wallet.processConfirmedTx(tx) + assert.deepEqual(wallet.unspentMap, {}) + }) + + function verifyOutputAdded(index, pending) { var txOut = tx.outs[index] var key = tx.getId() + ":" + index var output = wallet.unspentMap[key] - assert.deepEqual(output.hash, tx.getHash()) + assert.deepEqual(output.txHash, tx.getHash()) assert.equal(output.value, txOut.value) assert.equal(output.pending, pending) @@ -512,13 +518,14 @@ describe('Wallet', function() { }) function getFee(wallet, tx) { - var inputValue = tx.ins.reduce(function(memo, input){ - var id = Array.prototype.reverse.call(input.hash).toString('hex') - return memo + wallet.unspentMap[id + ':' + input.index].value + var inputValue = tx.ins.reduce(function(accum, input) { + var txId = bufferutils.reverse(input.hash).toString('hex') + + return accum + wallet.unspentMap[txId + ':' + input.index].value }, 0) - return tx.outs.reduce(function(memo, output){ - return memo - output.value + return tx.outs.reduce(function(accum, output) { + return accum - output.value }, inputValue) } }) From 6e1517482b024faf6176d0462d9844027592cd2c Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Mon, 18 Aug 2014 15:52:36 +1000 Subject: [PATCH 9/9] tests: avoid use of deprecated APIs --- test/wallet.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/test/wallet.js b/test/wallet.js index f73d7be..4e49ca8 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -458,19 +458,19 @@ describe('Wallet', function() { // set up 3 utxos var utxos = [ { - "hash": fakeTxId(1), + "txId": fakeTxId(1), "index": 0, "address": address1, "value": 400000 // not enough for value }, { - "hash": fakeTxId(2), + "txId": fakeTxId(2), "index": 1, "address": address1, "value": 500000 // enough for only value }, { - "hash": fakeTxId(3), + "txId": fakeTxId(3), "index": 0, "address" : address2, "value": 510000 // enough for value and fee @@ -486,7 +486,7 @@ describe('Wallet', function() { describe('transaction fee', function() { it('allows fee to be specified', function() { var fee = 30000 - var tx = wallet.createTx(to, value, fee) + var tx = wallet.createTx(to, value, { fixedFee: fee }) assert.equal(getFee(wallet, tx), fee) }) @@ -494,14 +494,14 @@ describe('Wallet', function() { it('allows fee to be set to zero', function() { value = 510000 var fee = 0 - var tx = wallet.createTx(to, value, fee) + var tx = wallet.createTx(to, value, { fixedFee: fee }) assert.equal(getFee(wallet, tx), fee) }) it('does not overestimate fees when network has dustSoftThreshold', function() { var utxo = { - hash: fakeTxId(0), + txId: fakeTxId(0), index: 0, address: "LeyySKbQrRRwodKEj1W4a8y3YQupPLw5os", value: 500000 @@ -573,7 +573,10 @@ describe('Wallet', function() { var toValue = fromValue / 2 var fee = 1e3 - var tx = wallet.createTx(to, toValue, fee, changeAddress) + var tx = wallet.createTx(to, toValue, { + fixedFee: fee, + changeAddress: changeAddress + }) assert.equal(tx.outs.length, 2) var outAddress0 = Address.fromOutputScript(tx.outs[0].script, networks.testnet) @@ -604,7 +607,7 @@ describe('Wallet', function() { var fee = 0 wallet.generateChangeAddress() wallet.generateChangeAddress() - var tx = wallet.createTx(to, value, fee) + var tx = wallet.createTx(to, value, { fixedFee: fee }) assert.equal(tx.outs.length, 2) var out = tx.outs[1] @@ -618,7 +621,7 @@ describe('Wallet', function() { var fee = 0 assert.equal(wallet.changeAddresses.length, 0) - var tx = wallet.createTx(to, value, fee) + var tx = wallet.createTx(to, value, { fixedFee: fee }) assert.equal(wallet.changeAddresses.length, 1) var out = tx.outs[1] @@ -645,7 +648,7 @@ describe('Wallet', function() { var fee = 30000 sinon.spy(TransactionBuilder.prototype, "sign") - var tx = wallet.createTx(to, value, fee) + var tx = wallet.createTx(to, value, { fixedFee: fee }) assert(TransactionBuilder.prototype.sign.calledWith(0, wallet.getPrivateKeyForAddress(address2))) assert(TransactionBuilder.prototype.sign.calledWith(1, wallet.getPrivateKeyForAddress(address1)))