add less-than-dust to fee. recalculate fee after input scripts are templated.

This commit is contained in:
Christopher Jeffrey 2015-12-10 13:52:34 -08:00
parent 3f44538b46
commit d7eea0941e
3 changed files with 77 additions and 20 deletions

View File

@ -42,12 +42,13 @@ function TX(data, block) {
// ps = Pending Since
this.ps = this.ts === 0 ? +new Date() / 1000 : 0;
this.change = data.change || null;
this.fee = data.fee || 10000;
this.dust = 5460;
this.changeAddress = data.changeAddress || null;
}
module.exports = TX;
TX.fee = 10000;
TX.dust = 5460;
TX.prototype.clone = function clone() {
return new TX(this);
};
@ -155,6 +156,7 @@ TX.prototype.scriptInput = function(input, pub) {
// P2PKH and simple tx
if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) {
input.script = [ [], pub ];
this._recalculateFee();
return;
}
@ -171,6 +173,7 @@ TX.prototype.scriptInput = function(input, pub) {
n = n[0] || 0;
for (var i = 0; i < n; i++)
input.script[i + 1] = [];
this._recalculateFee();
return;
}
@ -187,6 +190,7 @@ TX.prototype.scriptInput = function(input, pub) {
input.script[i + 1] = [];
// P2SH requires the redeem script after signatures
input.script.push(pub);
this._recalculateFee();
return;
}
@ -568,7 +572,7 @@ TX.prototype.utxos = function utxos(unspent) {
var fee = 1;
// total = cost + fee
var total = cost.add(new bn(this.fee));
var total = cost.add(new bn(TX.fee));
var inputs = this.inputs.slice();
var utxos = [];
@ -587,7 +591,7 @@ TX.prototype.utxos = function utxos(unspent) {
// var unspent = wallet.unspent();
unspent.every(addInput, this);
// Add dummy output (for `left`) to calculate maximum TX size
// Add dummy output (for `change`) to calculate maximum TX size
this.output({ address: null, value: new bn(0) });
// Change fee value if it is more than 1024 bytes
@ -597,7 +601,7 @@ TX.prototype.utxos = function utxos(unspent) {
var byteSize = this.maxSize();
var addFee = Math.ceil(byteSize / 1024) - fee;
total.iadd(new bn(addFee * this.fee));
total.iadd(new bn(addFee * TX.fee));
fee += addFee;
// Failed to get enough funds, add more inputs
@ -609,28 +613,31 @@ TX.prototype.utxos = function utxos(unspent) {
if (this.funds('in').cmp(total) < 0) {
this.inputs = inputs;
this.outputs.pop();
this.cost = total;
this.total = total;
return null;
}
// How much money is left after sending outputs
var left = this.funds('in').sub(total);
var change = this.funds('in').sub(total);
// Clear the tx of everything we added.
this.inputs = inputs;
this.outputs.pop();
this.cost = total;
// Return necessary utxos and change.
return {
utxos: utxos,
change: left,
cost: total
change: change,
cost: cost,
fee: total.sub(cost),
total: total
};
};
TX.prototype.fillUnspent = function fillUnspent(unspent, change) {
var result = this.utxos(unspent);
TX.prototype.fillUnspent = function fillUnspent(unspent, changeAddress) {
var result = unspent.utxos ? unspent : this.utxos(unspent);
this.changeAddress = changeAddress || this.changeAddress;
if (!result)
return result;
@ -639,20 +646,63 @@ TX.prototype.fillUnspent = function fillUnspent(unspent, change) {
this.input(utxo, null);
}, this);
// Not enough money, transfer everything to owner
if (result.change.cmpn(this.dust) < 0) {
// NOTE: that this output is either `postCost` or one of the `dust` values
this.outputs[this.outputs.length - 1].value.iadd(result.change);
if (result.change.cmpn(TX.dust) < 0) {
// Do nothing. Change is added to fee.
assert(this.getFee().cmp(result.fee.add(result.change)) === 0);
// Adding change to outputs.
// this.outputs[this.outputs.length - 1].value.iadd(result.change);
this.changeOutput = null;
} else {
this.output({
address: change || this.change,
address: this.changeAddress,
value: result.change
});
this.changeOutput = this.outputs[this.outputs.length - 1];
}
return result;
};
TX.prototype._recalculateFee = function recalculateFee() {
var output = this.changeOutput;
if (!output) {
this.output({
address: this.changeAddress,
value: new bn(0)
});
output = this.outputs[this.outputs.length - 1];
}
var byteSize = this.maxSize();
var newFee = Math.ceil(byteSize / 1024) * TX.fee;
var currentFee = this.getFee().toNumber();
if (newFee === currentFee) {
if (!this.changeOutput)
this.outputs.pop();
return;
}
if (newFee > currentFee) {
if (output.value.cmpn(newFee - currentFee) < 0) {
this.outputs.pop();
this.changeOutput = null;
return;
}
output.value.isubn(newFee - currentFee);
} else {
output.value.iaddn(currentFee - newFee);
}
if (output.value.cmpn(TX.dust) < 0) {
this.outputs.pop();
this.changeOutput = null;
return;
}
this.changeOutput = output;
};
TX.prototype.inputAddrs = function inputAddrs() {
return this.inputs.filter(function(input) {
return bcoin.script.isPubkeyhashInput(input.script);
@ -663,6 +713,10 @@ TX.prototype.inputAddrs = function inputAddrs() {
});
};
TX.prototype.getFee = function getFee() {
return this.funds('in').sub(this.funds('out'));
};
TX.prototype.funds = function funds(side) {
if (side === 'in') {
var inputs = this.inputs.filter(function(input) {

View File

@ -406,7 +406,7 @@ Wallet.prototype.fill = function fill(tx, cb) {
var result = tx.fillUnspent(this.unspent(), this.getAddress());
if (!result) {
var err = new Error('Not enough funds');
err.minBalance = tx.cost;
err.minBalance = tx.total;
cb(err);
return null;
}

View File

@ -177,7 +177,10 @@ describe('Wallet', function() {
assert(t2.verify());
assert.equal(t2.funds('in').toString(10), 16380);
assert.equal(t2.funds('out').toString(10), 6380);
// If change < dust and is added to outputs:
// assert.equal(t2.funds('out').toString(10), 6380);
// If change < dust and is added to fee:
assert.equal(t2.funds('out').toString(10), 5460);
// Create new transaction
var t3 = bcoin.tx().out(w2, 15000);