smart coin selection.
This commit is contained in:
parent
cef85d7294
commit
304f0e7e75
@ -311,6 +311,22 @@ HTTPServer.prototype._init = function _init() {
|
||||
enforce(util.isUInt32(options.blocks), 'Blocks must be a number.');
|
||||
}
|
||||
|
||||
if (params.selection != null) {
|
||||
options.selection = params.selection;
|
||||
enforce(typeof options.selection === 'string',
|
||||
'selection must be a string.');
|
||||
}
|
||||
|
||||
if (params.smart != null) {
|
||||
if (typeof params.smart === 'string') {
|
||||
options.smart = Boolean(params.smart);
|
||||
} else {
|
||||
options.smart = params.smart;
|
||||
enforce(typeof options.smart === 'boolean',
|
||||
'smart must be a boolean.');
|
||||
}
|
||||
}
|
||||
|
||||
if (params.subtractFee != null) {
|
||||
if (typeof params.subtractFee === 'number') {
|
||||
options.subtractFee = params.subtractFee;
|
||||
|
||||
@ -379,7 +379,7 @@ TXDB.prototype.removeInput = function removeInput(tx, index) {
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
TXDB.prototype.resolveInput = co(function* resolveInput(tx, index, height, path) {
|
||||
TXDB.prototype.resolveInput = co(function* resolveInput(tx, index, height, path, own) {
|
||||
var hash = tx.hash('hex');
|
||||
var spent = yield this.getSpent(hash, index);
|
||||
var stx, credit;
|
||||
@ -399,6 +399,7 @@ TXDB.prototype.resolveInput = co(function* resolveInput(tx, index, height, path)
|
||||
|
||||
// Crete the credit and add the undo coin.
|
||||
credit = Credit.fromTX(tx, index, height);
|
||||
credit.own = own;
|
||||
|
||||
this.spendCredit(credit, stx.tx, spent.index);
|
||||
|
||||
@ -801,6 +802,7 @@ TXDB.prototype.insert = co(function* insert(wtx, block) {
|
||||
var hash = wtx.hash;
|
||||
var height = block ? block.height : -1;
|
||||
var details = new Details(this, wtx, block);
|
||||
var own = false;
|
||||
var updated = false;
|
||||
var i, input, output, coin;
|
||||
var prevout, credit, path, account;
|
||||
@ -878,6 +880,7 @@ TXDB.prototype.insert = co(function* insert(wtx, block) {
|
||||
}
|
||||
|
||||
updated = true;
|
||||
own = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -893,12 +896,13 @@ TXDB.prototype.insert = co(function* insert(wtx, block) {
|
||||
|
||||
// Attempt to resolve an input we
|
||||
// did not know was ours at the time.
|
||||
if (yield this.resolveInput(tx, i, height, path)) {
|
||||
if (yield this.resolveInput(tx, i, height, path, own)) {
|
||||
updated = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
credit = Credit.fromTX(tx, i, height);
|
||||
credit.own = own;
|
||||
|
||||
this.pending.coin++;
|
||||
this.pending.unconfirmed += output.value;
|
||||
@ -2809,6 +2813,7 @@ function Credit(coin, spent) {
|
||||
|
||||
this.coin = coin || new Coin();
|
||||
this.spent = spent || false;
|
||||
this.own = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2821,6 +2826,12 @@ Credit.prototype.fromRaw = function fromRaw(data) {
|
||||
var br = new BufferReader(data);
|
||||
this.coin.fromReader(br);
|
||||
this.spent = br.readU8() === 1;
|
||||
this.own = true;
|
||||
|
||||
// Note: soft-fork
|
||||
if (br.left() > 0)
|
||||
this.own = br.readU8() === 1;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@ -2840,7 +2851,7 @@ Credit.fromRaw = function fromRaw(data) {
|
||||
*/
|
||||
|
||||
Credit.prototype.getSize = function getSize() {
|
||||
return this.coin.getSize() + 1;
|
||||
return this.coin.getSize() + 2;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2853,6 +2864,7 @@ Credit.prototype.toRaw = function toRaw() {
|
||||
var bw = new StaticWriter(size);
|
||||
this.coin.toWriter(bw);
|
||||
bw.writeU8(this.spent ? 1 : 0);
|
||||
bw.writeU8(this.own ? 1 : 0);
|
||||
return bw.render();
|
||||
};
|
||||
|
||||
@ -2867,6 +2879,7 @@ Credit.prototype.toRaw = function toRaw() {
|
||||
Credit.prototype.fromTX = function fromTX(tx, index, height) {
|
||||
this.coin.fromTX(tx, index, height);
|
||||
this.spent = false;
|
||||
this.own = false;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
@ -1421,15 +1421,17 @@ Wallet.prototype._fund = co(function* fund(mtx, options) {
|
||||
if (!account.initialized)
|
||||
throw new Error('Account is not initialized.');
|
||||
|
||||
coins = yield this.getCoins(options.account);
|
||||
|
||||
rate = options.rate;
|
||||
|
||||
if (rate == null)
|
||||
rate = yield this.db.estimateFee();
|
||||
|
||||
// Don't use any locked coins.
|
||||
coins = this.txdb.filterLocked(coins);
|
||||
if (options.smart) {
|
||||
coins = yield this.getSmartCoins(options.account);
|
||||
} else {
|
||||
coins = yield this.getCoins(options.account);
|
||||
coins = this.txdb.filterLocked(coins);
|
||||
}
|
||||
|
||||
yield mtx.fund(coins, {
|
||||
selection: options.selection,
|
||||
@ -2373,6 +2375,58 @@ Wallet.prototype.getCoins = co(function* getCoins(acct) {
|
||||
return yield this.txdb.getCoins(account);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get all available credits.
|
||||
* @param {(String|Number)?} account
|
||||
* @returns {Promise} - Returns {@link Credit}[].
|
||||
*/
|
||||
|
||||
Wallet.prototype.getCredits = co(function* getCredits(acct) {
|
||||
var account = yield this.ensureIndex(acct);
|
||||
return yield this.txdb.getCredits(account);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get "smart" coins.
|
||||
* @param {(String|Number)?} account
|
||||
* @returns {Promise} - Returns {@link Coin}[].
|
||||
*/
|
||||
|
||||
Wallet.prototype.getSmartCoins = co(function* getSmartCoins(acct) {
|
||||
var credits = yield this.getCredits(acct);
|
||||
var coins = [];
|
||||
var i, credit, coin;
|
||||
|
||||
for (i = 0; i < credits.length; i++) {
|
||||
credit = credits[i];
|
||||
coin = credit.coin;
|
||||
|
||||
if (credit.spent)
|
||||
continue;
|
||||
|
||||
if (this.txdb.isLocked(coin))
|
||||
continue;
|
||||
|
||||
// Always used confirmed coins.
|
||||
if (coin.height !== -1) {
|
||||
coins.push(coin);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use unconfirmed only if they were
|
||||
// created as a result of one of our
|
||||
// _own_ transactions. i.e. they're
|
||||
// not low-fee and not in danger of
|
||||
// being double-spent by a bad actor.
|
||||
if (!credit.own)
|
||||
continue;
|
||||
|
||||
coins.push(coin);
|
||||
}
|
||||
|
||||
return coins;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get all pending/unconfirmed transactions.
|
||||
* @param {(String|Number)?} acct
|
||||
|
||||
@ -930,7 +930,7 @@ describe('Wallet', function() {
|
||||
assert(t2.verify());
|
||||
}));
|
||||
|
||||
it('should fill tx with inputs with subtract fee', co(function* () {
|
||||
it('should fill tx with inputs with subtract fee (1)', co(function* () {
|
||||
var w1 = yield walletdb.create();
|
||||
var w2 = yield walletdb.create();
|
||||
var t1, t2;
|
||||
@ -959,7 +959,7 @@ describe('Wallet', function() {
|
||||
assert.equal(t2.getFee(), 10000);
|
||||
}));
|
||||
|
||||
it('should fill tx with inputs with subtract fee', co(function* () {
|
||||
it('should fill tx with inputs with subtract fee (2)', co(function* () {
|
||||
var w1 = yield walletdb.create();
|
||||
var w2 = yield walletdb.create();
|
||||
var options, t1, t2;
|
||||
@ -993,6 +993,100 @@ describe('Wallet', function() {
|
||||
assert.equal(t2.getFee(), 10000);
|
||||
}));
|
||||
|
||||
it('should fill tx with smart coin selection', co(function* () {
|
||||
var w1 = yield walletdb.create();
|
||||
var w2 = yield walletdb.create();
|
||||
var found = false;
|
||||
var total = 0;
|
||||
var i, options, t1, t2, t3, block, coins, coin;
|
||||
|
||||
// Coinbase
|
||||
t1 = new MTX();
|
||||
t1.addInput(dummy());
|
||||
t1.addOutput(w1.getAddress(), 5460);
|
||||
t1.addOutput(w1.getAddress(), 5460);
|
||||
t1.addOutput(w1.getAddress(), 5460);
|
||||
t1.addOutput(w1.getAddress(), 5460);
|
||||
t1 = t1.toTX();
|
||||
|
||||
yield walletdb.addTX(t1);
|
||||
|
||||
// Coinbase
|
||||
t2 = new MTX();
|
||||
t2.addInput(dummy());
|
||||
t2.addOutput(w1.getAddress(), 5460);
|
||||
t2.addOutput(w1.getAddress(), 5460);
|
||||
t2.addOutput(w1.getAddress(), 5460);
|
||||
t2.addOutput(w1.getAddress(), 5460);
|
||||
t2 = t2.toTX();
|
||||
|
||||
block = nextBlock();
|
||||
|
||||
yield walletdb.addBlock(block, [t2]);
|
||||
|
||||
coins = yield w1.getSmartCoins();
|
||||
assert.equal(coins.length, 4);
|
||||
|
||||
for (i = 0; i < coins.length; i++) {
|
||||
coin = coins[i];
|
||||
assert.equal(coin.height, block.height);
|
||||
}
|
||||
|
||||
// Create a change output for ourselves.
|
||||
yield w1.send({
|
||||
subtractFee: true,
|
||||
rate: 1000,
|
||||
depth: 1,
|
||||
outputs: [{ address: w2.getAddress(), value: 1461 }]
|
||||
});
|
||||
|
||||
coins = yield w1.getSmartCoins();
|
||||
assert.equal(coins.length, 4);
|
||||
|
||||
for (i = 0; i < coins.length; i++) {
|
||||
coin = coins[i];
|
||||
if (coin.height === -1) {
|
||||
assert(!found);
|
||||
assert(coin.value < 5460);
|
||||
found = true;
|
||||
} else {
|
||||
assert.equal(coin.height, block.height);
|
||||
}
|
||||
total += coin.value;
|
||||
}
|
||||
|
||||
assert(found);
|
||||
|
||||
// Use smart selection
|
||||
options = {
|
||||
subtractFee: true,
|
||||
smart: true,
|
||||
rate: 10000,
|
||||
outputs: [{ address: w2.getAddress(), value: total }]
|
||||
};
|
||||
|
||||
t3 = yield w1.createTX(options);
|
||||
assert.equal(t3.inputs.length, 4);
|
||||
|
||||
found = false;
|
||||
for (i = 0; i < t3.inputs.length; i++) {
|
||||
coin = t3.view.getCoin(t3.inputs[i]);
|
||||
if (coin.height === -1) {
|
||||
assert(!found);
|
||||
assert(coin.value < 5460);
|
||||
found = true;
|
||||
} else {
|
||||
assert.equal(coin.height, block.height);
|
||||
}
|
||||
}
|
||||
|
||||
assert(found);
|
||||
|
||||
yield w1.sign(t3);
|
||||
|
||||
assert(t3.verify());
|
||||
}));
|
||||
|
||||
it('should get range of txs', co(function* () {
|
||||
var w = wallet;
|
||||
var txs = yield w.getRange({ start: util.now() - 1000 });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user