improve fee calculation. tests.

This commit is contained in:
Christopher Jeffrey 2016-05-20 04:17:05 -07:00
parent 72e5d5fbc7
commit f013ddb353
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
7 changed files with 134 additions and 50 deletions

View File

@ -800,7 +800,7 @@ HDPrivateKey._generate = function _generate(options, network) {
var privateKey, entropy; var privateKey, entropy;
if (!options) if (!options)
opitons = {}; options = {};
if (Buffer.isBuffer(options)) if (Buffer.isBuffer(options))
options = { privateKey: options }; options = { privateKey: options };

View File

@ -730,11 +730,10 @@ MTX.prototype.isScripted = function isScripted() {
for (i = 0; i < this.inputs.length; i++) { for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i]; input = this.inputs[i];
if (input.script.code.length === 0) if (input.script.code.length === 0
return false; && input.witness.items.length === 0) {
if (input.witness.items.length === 0)
return false; return false;
}
} }
return true; return true;
@ -776,7 +775,6 @@ MTX.prototype.maxSize = function maxSize(options, force) {
input = this.inputs[i]; input = this.inputs[i];
size = input.script.getSize(); size = input.script.getSize();
total -= utils.sizeVarint(size) + size; total -= utils.sizeVarint(size) + size;
total += 1;
} }
// Add size for signatures and public keys // Add size for signatures and public keys
@ -784,6 +782,7 @@ MTX.prototype.maxSize = function maxSize(options, force) {
input = this.inputs[i]; input = this.inputs[i];
size = 0; size = 0;
witness = false; witness = false;
redeem = null;
// We're out of luck here. // We're out of luck here.
// Just assume it's a p2pkh. // Just assume it's a p2pkh.
@ -817,10 +816,17 @@ MTX.prototype.maxSize = function maxSize(options, force) {
if (prev.isWitnessProgram()) { if (prev.isWitnessProgram()) {
witness = true; witness = true;
// Now calculating vsize. The regular // Now calculating vsize.
// redeem script (if there was one) if (redeem) {
// is now worth 4 points. // The regular redeem script
size *= 4; // is now worth 4 points.
size += utils.sizeVarint(size);
size *= 4;
} else {
// Add one varint byte back
// for the 0-byte input script.
size += 1 * 4;
}
// Add 2 bytes for flag and marker. // Add 2 bytes for flag and marker.
if (!hadWitness) if (!hadWitness)
@ -898,14 +904,14 @@ MTX.prototype.maxSize = function maxSize(options, force) {
} }
} }
// Byte for varint size of input script or witness.
size += utils.sizeVarint(size);
// Calculate vsize if we're a witness program.
if (witness) { if (witness) {
// Add one byte back for the 0-byte input script. // Calculate vsize if
size += 1 * 4; // we're a witness program.
size = (size + scale - 1) / scale | 0; size = (size + scale - 1) / scale | 0;
} else {
// Byte for varint
// size of input script.
size += utils.sizeVarint(size);
} }
total += size; total += size;
@ -920,10 +926,10 @@ MTX.prototype.maxSize = function maxSize(options, force) {
* @param {Object?} options * @param {Object?} options
* @param {String?} options.selection - Coin selection priority. Can * @param {String?} options.selection - Coin selection priority. Can
* be `age`, `random`, or `all`. (default=age). * be `age`, `random`, or `all`. (default=age).
* @param {Boolean} options.accurate - Whether to use "accurate"
* fee calculatation rather than rounding to the nearest kilobyte.
* See {@link TX#getMinFee} vs. {@link TX#getMaxFee}.
* @param {Boolean} options.confirmed - Select only confirmed coins. * @param {Boolean} options.confirmed - Select only confirmed coins.
* @param {Boolean} options.round - Whether to round to the nearest
* kilobyte for fee calculation.
* See {@link TX#getMinFee} vs. {@link TX#getRoundFee}.
* @param {Boolean} options.free - Do not apply a fee if the * @param {Boolean} options.free - Do not apply a fee if the
* transaction priority is high enough to be considered free. * transaction priority is high enough to be considered free.
* @param {Amount?} options.fee - Use a hard fee rather than calculating one. * @param {Amount?} options.fee - Use a hard fee rather than calculating one.
@ -1042,10 +1048,10 @@ MTX.prototype.selectCoins = function selectCoins(coins, options) {
tryFree = false; tryFree = false;
} }
if (options.accurate) if (options.round)
fee = tx.getMinFee(size, options.rate); fee = tx.getRoundFee(size, options.rate);
else else
fee = tx.getMaxFee(size, options.rate); fee = tx.getMinFee(size, options.rate);
// Failed to get enough funds, add more coins. // Failed to get enough funds, add more coins.
if (!isFull()) if (!isFull())

View File

@ -428,7 +428,7 @@ main.minRelay = 10000;
* @default * @default
*/ */
main.feeRate = 40000; main.feeRate = 50000;
/** /**
* Default min rate. * Default min rate.
@ -444,7 +444,7 @@ main.minRate = 10000;
* @default * @default
*/ */
main.maxRate = 40000; main.maxRate = 50000;
/* /*
* Testnet (v3) * Testnet (v3)

View File

@ -1540,7 +1540,7 @@ TX.prototype.getRate = function getRate(size) {
* @returns {Amount} fee * @returns {Amount} fee
*/ */
TX.prototype.getMaxFee = function getMaxFee(size, rate) { TX.prototype.getRoundFee = function getRoundFee(size, rate) {
var fee; var fee;
if (size == null) if (size == null)

View File

@ -677,9 +677,9 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
* @param {Object?} options * @param {Object?} options
* @param {String?} options.selection - Coin selection priority. Can * @param {String?} options.selection - Coin selection priority. Can
* be `age`, `random`, or `all`. (default=age). * be `age`, `random`, or `all`. (default=age).
* @param {Boolean} options.accurate - Whether to use "accurate" * @param {Boolean} options.round - Whether to round to the nearest
* fee calculatation rather than rounding to the nearest kilobyte. * kilobyte for fee calculation.
* See {@link TX#getMinFee} vs. {@link TX#getMaxFee}. * See {@link TX#getMinFee} vs. {@link TX#getRoundFee}.
* @param {Rate} options.rate - Rate used for fee calculation. * @param {Rate} options.rate - Rate used for fee calculation.
* @param {Boolean} options.confirmed - Select only confirmed coins. * @param {Boolean} options.confirmed - Select only confirmed coins.
* @param {Boolean} options.free - Do not apply a fee if the * @param {Boolean} options.free - Do not apply a fee if the
@ -709,14 +709,16 @@ Wallet.prototype.fill = function fill(tx, options, callback) {
try { try {
tx.fill(coins, { tx.fill(coins, {
selection: options.selection || 'age', selection: options.selection || 'age',
accurate: options.accurate, round: options.round,
confirmed: options.confirmed, confirmed: options.confirmed,
free: options.free, free: options.free,
fee: options.fee, fee: options.fee,
subtractFee: options.subtractFee, subtractFee: options.subtractFee,
changeAddress: self.changeAddress.getAddress(), changeAddress: self.changeAddress.getAddress(),
height: self.network.height, height: self.network.height,
rate: self.network.getMinRelay(), rate: options.rate != null
? options.rate
: self.network.getRate(),
wallet: self, wallet: self,
m: self.m, m: self.m,
n: self.n n: self.n

View File

@ -100,27 +100,27 @@ describe('Mempool', function() {
assert.ifError(err); assert.ifError(err);
mempool.getBalance(function(err, balance) { mempool.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '0'); assert.equal(balance.total, 0);
mempool.addTX(t1, function(err) { mempool.addTX(t1, function(err) {
assert.ifError(err); assert.ifError(err);
mempool.getBalance(function(err, balance) { mempool.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '60000'); assert.equal(balance.total, 60000);
mempool.addTX(t2, function(err) { mempool.addTX(t2, function(err) {
assert.ifError(err); assert.ifError(err);
mempool.getBalance(function(err, balance) { mempool.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '50000'); assert.equal(balance.total, 50000);
mempool.addTX(t3, function(err) { mempool.addTX(t3, function(err) {
assert.ifError(err); assert.ifError(err);
mempool.getBalance(function(err, balance) { mempool.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '22000'); assert.equal(balance.total, 22000);
mempool.addTX(f1, function(err) { mempool.addTX(f1, function(err) {
assert.ifError(err); assert.ifError(err);
mempool.getBalance(function(err, balance) { mempool.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '20000'); assert.equal(balance.total, 20000);
mempool.getHistory(function(err, txs) { mempool.getHistory(function(err, txs) {
assert(txs.some(function(tx) { assert(txs.some(function(tx) {
return tx.hash('hex') === f1.hash('hex'); return tx.hash('hex') === f1.hash('hex');

View File

@ -5,6 +5,16 @@ var network = bcoin.protocol.network;
var utils = bcoin.utils; var utils = bcoin.utils;
var assert = require('assert'); var assert = require('assert');
var KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt'
+ 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ';
KEY1 = { xprivkey: KEY1 };
var KEY2 = 'xprv9s21ZrQH143K3mqiSThzPtWAabQ22Pjp3uSNnZ53A5bQ4udp'
+ 'faKekc2m4AChLYH1XDzANhrSdxHYWUeTWjYJwFwWFyHkTMnMeAcW4JyRCZa';
KEY2 = { xprivkey: KEY2 };
var dummyInput = { var dummyInput = {
prevout: { prevout: {
hash: constants.NULL_HASH, hash: constants.NULL_HASH,
@ -24,6 +34,18 @@ var dummyInput = {
sequence: 0xffffffff sequence: 0xffffffff
}; };
assert.range = function range(value, lo, hi, message) {
if (!(value >= lo && value <= hi)) {
throw new assert.AssertionError({
message: message,
actual: value,
expected: lo + ', ' + hi,
operator: '>= && <=',
stackStartFunction: range
});
}
};
describe('Wallet', function() { describe('Wallet', function() {
var wdb = new bcoin.walletdb({ var wdb = new bcoin.walletdb({
name: 'wallet-test', name: 'wallet-test',
@ -199,33 +221,33 @@ describe('Wallet', function() {
assert.ifError(err); assert.ifError(err);
w.getBalance(function(err, balance) { w.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '22500'); assert.equal(balance.total, 22500);
wdb.addTX(t1, function(err) { wdb.addTX(t1, function(err) {
w.getBalance(function(err, balance) { w.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '73000'); assert.equal(balance.total, 73000);
wdb.addTX(t2, function(err) { wdb.addTX(t2, function(err) {
assert.ifError(err); assert.ifError(err);
w.getBalance(function(err, balance) { w.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '47000'); assert.equal(balance.total, 47000);
wdb.addTX(t3, function(err) { wdb.addTX(t3, function(err) {
assert.ifError(err); assert.ifError(err);
w.getBalance(function(err, balance) { w.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '22000'); assert.equal(balance.total, 22000);
wdb.addTX(f1, function(err) { wdb.addTX(f1, function(err) {
assert.ifError(err); assert.ifError(err);
w.getBalance(function(err, balance) { w.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '11000'); assert.equal(balance.total, 11000);
w.getHistory(function(err, txs) { w.getHistory(function(err, txs) {
assert(txs.some(function(tx) { assert(txs.some(function(tx) {
return tx.hash('hex') === f1.hash('hex'); return tx.hash('hex') === f1.hash('hex');
})); }));
var w2 = bcoin.wallet.fromJSON(w.toJSON()); var w2 = bcoin.wallet.fromJSON(w.toJSON());
// assert.equal(w2.getBalance().toString(10), '11000'); // assert.equal(w2.getBalance(), 11000);
// assert(w2.getHistory().some(function(tx) { // assert(w2.getHistory().some(function(tx) {
// return tx.hash('hex') === f1.hash('hex'); // return tx.hash('hex') === f1.hash('hex');
// })); // }));
@ -253,7 +275,7 @@ describe('Wallet', function() {
assert.ifError(err); assert.ifError(err);
dw.getBalance(function(err, balance) { dw.getBalance(function(err, balance) {
assert.ifError(err); assert.ifError(err);
assert.equal(balance.total.toString(10), '11000'); assert.equal(balance.total, 11000);
cb(); cb();
}); });
}); });
@ -280,22 +302,23 @@ describe('Wallet', function() {
// Create new transaction // Create new transaction
var t2 = bcoin.mtx().addOutput(w2, 5460); var t2 = bcoin.mtx().addOutput(w2, 5460);
w1.fill(t2, function(err) { w1.fill(t2, { rate: 10000, round: true }, function(err) {
assert.ifError(err); assert.ifError(err);
w1.sign(t2); w1.sign(t2);
assert(t2.verify()); assert(t2.verify());
assert.equal(t2.getInputValue().toString(10), 16380); assert.equal(t2.getInputValue(), 16380);
// If change < dust and is added to outputs: // If change < dust and is added to outputs:
// assert.equal(t2.getOutputValue().toString(10), 6380); // assert.equal(t2.getOutputValue(), 6380);
// If change < dust and is added to fee: // If change > dust and is added to fee:
assert.equal(t2.getOutputValue().toString(10), 5460); assert.equal(t2.getOutputValue(), 5460);
assert.equal(t2.getFee(), 10920);
// Create new transaction // Create new transaction
var t3 = bcoin.mtx().addOutput(w2, 15000); var t3 = bcoin.mtx().addOutput(w2, 15000);
w1.fill(t3, function(err) { w1.fill(t3, { rate: 10000, round: true }, function(err) {
assert(err); assert(err);
assert.equal(err.requiredFunds.toString(10), 25000); assert.equal(err.requiredFunds, 25000);
cb(); cb();
}); });
}); });
@ -304,6 +327,59 @@ describe('Wallet', function() {
}); });
}); });
it('should fill tx with inputs with accurate fee', function(cb) {
wdb.create({ master: KEY1 }, function(err, w1) {
assert.ifError(err);
wdb.create({ master: KEY2 }, function(err, w2) {
assert.ifError(err);
// Coinbase
var t1 = bcoin.mtx()
.addOutput(w1, 5460)
.addOutput(w1, 5460)
.addOutput(w1, 5460)
.addOutput(w1, 5460);
t1.addInput(dummyInput);
// Fake TX should temporarly change output
wdb.addTX(t1, function(err) {
assert.ifError(err);
// Create new transaction
var t2 = bcoin.mtx().addOutput(w2, 5460);
w1.fill(t2, { rate: 10000 }, function(err) {
assert.ifError(err);
w1.sign(t2);
assert(t2.verify());
assert.equal(t2.getInputValue(), 16380);
// Should now have a change output:
assert.equal(t2.getOutputValue(), 11130);
assert.equal(t2.getFee(), 5250);
assert.equal(t2.getCost(), 2084);
assert.equal(t2.getBaseSize(), 521);
assert.equal(t2.getSize(), 521);
assert.equal(t2.getVirtualSize(), 521);
// Create new transaction
wdb.addTX(t2, function(err) {
assert.ifError(err);
var t3 = bcoin.mtx().addOutput(w2, 15000);
w1.fill(t3, { rate: 10000 }, function(err) {
assert(err);
cb();
});
});
});
});
});
});
});
it('should sign multiple inputs using different keys', function(cb) { it('should sign multiple inputs using different keys', function(cb) {
wdb.create({}, function(err, w1) { wdb.create({}, function(err, w1) {
assert.ifError(err); assert.ifError(err);
@ -483,7 +559,7 @@ describe('Wallet', function() {
var send = bcoin.mtx(); var send = bcoin.mtx();
send.addOutput({ address: receive.getAddress(), value: 5460 }); send.addOutput({ address: receive.getAddress(), value: 5460 });
assert(!send.verify(null, true, flags)); assert(!send.verify(null, true, flags));
w1.fill(send, { m: w1.m, n: w1.n }, function(err) { w1.fill(send, { rate: 10000, round: true }, function(err) {
assert.ifError(err); assert.ifError(err);
w1.sign(send); w1.sign(send);