diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 896455a1..a25a09b7 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -18,7 +18,6 @@ function TX(data, block) { this.lock = data.lock || 0; this.ts = data.ts || 0; this.block = null; - this.funds = new bn(0); this._hash = null; this._raw = data._raw || null; @@ -94,8 +93,6 @@ TX.prototype._input = function _input(i, index) { ex.script = input.script.length ? input.script : ex.script; } else { this.inputs.push(input); - if (input.out.tx) - this.funds.iadd(input.out.tx.outputs[input.out.index].value); index = this.inputs.length - 1; } @@ -250,6 +247,35 @@ TX.prototype.inputAddrs = function inputAddrs() { }); }; +TX.prototype.funds = function funds(side) { + if (side === 'in') { + var inputs = this.inputs.filter(function(input) { + return input.out.tx; + }); + + var acc = new bn(0); + if (inputs.length === 0) + return acc; + + inputs.reduce(function(acc, input) { + return acc.iadd(input.out.tx.outputs[input.out.index].value); + }, acc); + + return acc; + } + + // Output + var acc = new bn(0); + if (this.outputs.length === 0) + return acc; + + this.outputs.reduce(function(acc, output) { + return acc.iadd(output.value); + }, acc); + + return acc; +}; + TX.prototype.toJSON = function toJSON() { // Compact representation return { diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 42904a2c..82c90f68 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -1,6 +1,7 @@ var assert = require('assert'); var bcoin = require('../bcoin'); var hash = require('hash.js'); +var bn = require('bn.js'); var util = require('util'); var EventEmitter = require('events').EventEmitter; var utils = bcoin.utils; @@ -39,6 +40,11 @@ function Wallet(options, passphrase) { this.prefix = 'bt/' + this.getAddress() + '/'; this.tx = new bcoin.txPool(this); + + // Just a constants, actually + this.fee = 10000; + this.dust = 5460; + this._init(); } util.inherits(Wallet, EventEmitter); @@ -241,6 +247,76 @@ Wallet.prototype.balance = function balance() { return this.tx.balance(); }; +Wallet.prototype.fill = function fill(tx, cb) { + // NOTE: tx should be prefilled with all outputs + var cost = tx.funds('out'); + + // Use initial fee for starters + var fee = 1; + + // total = cost + fee + var total = cost.add(new bn(this.fee)); + + var lastAdded = -1; + function addInput(unspent, i) { + // Add new inputs until TX will have enough funds to cover both + // minimum post cost and fee + tx.input(unspent); + lastAdded = i; + return tx.funds('in').cmp(total) < 0; + } + + // Transfer `total` funds maximum + var unspent = this.unspent(); + unspent.every(addInput, this); + + // Add dummy output (for `left`) to calculate maximum TX size + tx.out(this, new bn(0)); + + // Change fee value if it is more than 1024 bytes + // (10000 satoshi for every 1024 bytes) + do { + // Calculate maximum possible size after signing + var byteSize = tx.maxSize(); + + var addFee = Math.ceil(byteSize / 1024) - fee; + total.iadd(new bn(addFee * this.fee)); + fee += addFee; + + // Failed to get enough funds, add more inputs + if (tx.funds('in').cmp(total) < 0) + unspent.slice(lastAdded + 1).every(addInput, this); + } while (tx.funds('in').cmp(total) < 0 && lastAdded < unspent.length); + + // Still failing to get enough funds, notify caller + if (tx.funds('in').cmp(total) < 0) { + var err = new Error('Not enough funds'); + err.minBalance = total; + return cb(err); + } + + // How much money is left after sending outputs + var left = tx.funds('in').sub(total); + + // Not enough money, transfer everything to owner + if (left.cmpn(this.dust) < 0) { + // NOTE: that this output is either `postCost` or one of the `dust` values + tx.outputs[tx.outputs.length - 2].value.iadd(left); + left = new bn(0); + } + + // Change or remove last output if there is some money left + if (left.cmpn(0) === 0) + tx.outputs.pop(); + else + tx.outputs[tx.outputs.length - 1].value = left; + + // Sign transaction + this.sign(tx); + + return tx; +}; + Wallet.prototype.toJSON = function toJSON() { return { v: 1, diff --git a/test/wallet-test.js b/test/wallet-test.js index f84b32bc..fb200a37 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -130,4 +130,23 @@ describe('Wallet', function() { return tx.hash('hex') === f1.hash('hex'); })); }); + + it('should fill tx with inputs', function() { + var w1 = bcoin.wallet(); + var w2 = bcoin.wallet(); + + // Coinbase + var t1 = bcoin.tx().out(w1, 5460).out(w1, 5460).out(w1, 5460).out(w1, 5460); + + // Fake TX should temporarly change output + w1.addTX(t1); + + // Create new transaction + var t2 = bcoin.tx().out(w2, 5460); + w1.sign(t2); + w1.fill(t2); + + assert.equal(t2.funds('in').toString(10), 16380); + assert.equal(t2.funds('out').toString(10), 6380); + }); });