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;
if (!options)
opitons = {};
options = {};
if (Buffer.isBuffer(options))
options = { privateKey: options };

View File

@ -730,11 +730,10 @@ MTX.prototype.isScripted = function isScripted() {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
if (input.script.code.length === 0)
return false;
if (input.witness.items.length === 0)
if (input.script.code.length === 0
&& input.witness.items.length === 0) {
return false;
}
}
return true;
@ -776,7 +775,6 @@ MTX.prototype.maxSize = function maxSize(options, force) {
input = this.inputs[i];
size = input.script.getSize();
total -= utils.sizeVarint(size) + size;
total += 1;
}
// Add size for signatures and public keys
@ -784,6 +782,7 @@ MTX.prototype.maxSize = function maxSize(options, force) {
input = this.inputs[i];
size = 0;
witness = false;
redeem = null;
// We're out of luck here.
// Just assume it's a p2pkh.
@ -817,10 +816,17 @@ MTX.prototype.maxSize = function maxSize(options, force) {
if (prev.isWitnessProgram()) {
witness = true;
// Now calculating vsize. The regular
// redeem script (if there was one)
// is now worth 4 points.
size *= 4;
// Now calculating vsize.
if (redeem) {
// The regular redeem script
// 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.
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) {
// Add one byte back for the 0-byte input script.
size += 1 * 4;
// Calculate vsize if
// we're a witness program.
size = (size + scale - 1) / scale | 0;
} else {
// Byte for varint
// size of input script.
size += utils.sizeVarint(size);
}
total += size;
@ -920,10 +926,10 @@ MTX.prototype.maxSize = function maxSize(options, force) {
* @param {Object?} options
* @param {String?} options.selection - Coin selection priority. Can
* 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.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
* transaction priority is high enough to be considered free.
* @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;
}
if (options.accurate)
fee = tx.getMinFee(size, options.rate);
if (options.round)
fee = tx.getRoundFee(size, options.rate);
else
fee = tx.getMaxFee(size, options.rate);
fee = tx.getMinFee(size, options.rate);
// Failed to get enough funds, add more coins.
if (!isFull())

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,16 @@ var network = bcoin.protocol.network;
var utils = bcoin.utils;
var assert = require('assert');
var KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt'
+ 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ';
KEY1 = { xprivkey: KEY1 };
var KEY2 = 'xprv9s21ZrQH143K3mqiSThzPtWAabQ22Pjp3uSNnZ53A5bQ4udp'
+ 'faKekc2m4AChLYH1XDzANhrSdxHYWUeTWjYJwFwWFyHkTMnMeAcW4JyRCZa';
KEY2 = { xprivkey: KEY2 };
var dummyInput = {
prevout: {
hash: constants.NULL_HASH,
@ -24,6 +34,18 @@ var dummyInput = {
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() {
var wdb = new bcoin.walletdb({
name: 'wallet-test',
@ -199,33 +221,33 @@ describe('Wallet', function() {
assert.ifError(err);
w.getBalance(function(err, balance) {
assert.ifError(err);
assert.equal(balance.total.toString(10), '22500');
assert.equal(balance.total, 22500);
wdb.addTX(t1, function(err) {
w.getBalance(function(err, balance) {
assert.ifError(err);
assert.equal(balance.total.toString(10), '73000');
assert.equal(balance.total, 73000);
wdb.addTX(t2, function(err) {
assert.ifError(err);
w.getBalance(function(err, balance) {
assert.ifError(err);
assert.equal(balance.total.toString(10), '47000');
assert.equal(balance.total, 47000);
wdb.addTX(t3, function(err) {
assert.ifError(err);
w.getBalance(function(err, balance) {
assert.ifError(err);
assert.equal(balance.total.toString(10), '22000');
assert.equal(balance.total, 22000);
wdb.addTX(f1, function(err) {
assert.ifError(err);
w.getBalance(function(err, balance) {
assert.ifError(err);
assert.equal(balance.total.toString(10), '11000');
assert.equal(balance.total, 11000);
w.getHistory(function(err, txs) {
assert(txs.some(function(tx) {
return tx.hash('hex') === f1.hash('hex');
}));
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) {
// return tx.hash('hex') === f1.hash('hex');
// }));
@ -253,7 +275,7 @@ describe('Wallet', function() {
assert.ifError(err);
dw.getBalance(function(err, balance) {
assert.ifError(err);
assert.equal(balance.total.toString(10), '11000');
assert.equal(balance.total, 11000);
cb();
});
});
@ -280,22 +302,23 @@ describe('Wallet', function() {
// Create new transaction
var t2 = bcoin.mtx().addOutput(w2, 5460);
w1.fill(t2, function(err) {
w1.fill(t2, { rate: 10000, round: true }, function(err) {
assert.ifError(err);
w1.sign(t2);
assert(t2.verify());
assert.equal(t2.getInputValue().toString(10), 16380);
assert.equal(t2.getInputValue(), 16380);
// If change < dust and is added to outputs:
// assert.equal(t2.getOutputValue().toString(10), 6380);
// If change < dust and is added to fee:
assert.equal(t2.getOutputValue().toString(10), 5460);
// assert.equal(t2.getOutputValue(), 6380);
// If change > dust and is added to fee:
assert.equal(t2.getOutputValue(), 5460);
assert.equal(t2.getFee(), 10920);
// Create new transaction
var t3 = bcoin.mtx().addOutput(w2, 15000);
w1.fill(t3, function(err) {
w1.fill(t3, { rate: 10000, round: true }, function(err) {
assert(err);
assert.equal(err.requiredFunds.toString(10), 25000);
assert.equal(err.requiredFunds, 25000);
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) {
wdb.create({}, function(err, w1) {
assert.ifError(err);
@ -483,7 +559,7 @@ describe('Wallet', function() {
var send = bcoin.mtx();
send.addOutput({ address: receive.getAddress(), value: 5460 });
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);
w1.sign(send);