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.');
|
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 (params.subtractFee != null) {
|
||||||
if (typeof params.subtractFee === 'number') {
|
if (typeof params.subtractFee === 'number') {
|
||||||
options.subtractFee = params.subtractFee;
|
options.subtractFee = params.subtractFee;
|
||||||
|
|||||||
@ -379,7 +379,7 @@ TXDB.prototype.removeInput = function removeInput(tx, index) {
|
|||||||
* @returns {Boolean}
|
* @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 hash = tx.hash('hex');
|
||||||
var spent = yield this.getSpent(hash, index);
|
var spent = yield this.getSpent(hash, index);
|
||||||
var stx, credit;
|
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.
|
// Crete the credit and add the undo coin.
|
||||||
credit = Credit.fromTX(tx, index, height);
|
credit = Credit.fromTX(tx, index, height);
|
||||||
|
credit.own = own;
|
||||||
|
|
||||||
this.spendCredit(credit, stx.tx, spent.index);
|
this.spendCredit(credit, stx.tx, spent.index);
|
||||||
|
|
||||||
@ -801,6 +802,7 @@ TXDB.prototype.insert = co(function* insert(wtx, block) {
|
|||||||
var hash = wtx.hash;
|
var hash = wtx.hash;
|
||||||
var height = block ? block.height : -1;
|
var height = block ? block.height : -1;
|
||||||
var details = new Details(this, wtx, block);
|
var details = new Details(this, wtx, block);
|
||||||
|
var own = false;
|
||||||
var updated = false;
|
var updated = false;
|
||||||
var i, input, output, coin;
|
var i, input, output, coin;
|
||||||
var prevout, credit, path, account;
|
var prevout, credit, path, account;
|
||||||
@ -878,6 +880,7 @@ TXDB.prototype.insert = co(function* insert(wtx, block) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updated = true;
|
updated = true;
|
||||||
|
own = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -893,12 +896,13 @@ TXDB.prototype.insert = co(function* insert(wtx, block) {
|
|||||||
|
|
||||||
// Attempt to resolve an input we
|
// Attempt to resolve an input we
|
||||||
// did not know was ours at the time.
|
// 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;
|
updated = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
credit = Credit.fromTX(tx, i, height);
|
credit = Credit.fromTX(tx, i, height);
|
||||||
|
credit.own = own;
|
||||||
|
|
||||||
this.pending.coin++;
|
this.pending.coin++;
|
||||||
this.pending.unconfirmed += output.value;
|
this.pending.unconfirmed += output.value;
|
||||||
@ -2809,6 +2813,7 @@ function Credit(coin, spent) {
|
|||||||
|
|
||||||
this.coin = coin || new Coin();
|
this.coin = coin || new Coin();
|
||||||
this.spent = spent || false;
|
this.spent = spent || false;
|
||||||
|
this.own = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2821,6 +2826,12 @@ Credit.prototype.fromRaw = function fromRaw(data) {
|
|||||||
var br = new BufferReader(data);
|
var br = new BufferReader(data);
|
||||||
this.coin.fromReader(br);
|
this.coin.fromReader(br);
|
||||||
this.spent = br.readU8() === 1;
|
this.spent = br.readU8() === 1;
|
||||||
|
this.own = true;
|
||||||
|
|
||||||
|
// Note: soft-fork
|
||||||
|
if (br.left() > 0)
|
||||||
|
this.own = br.readU8() === 1;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2840,7 +2851,7 @@ Credit.fromRaw = function fromRaw(data) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Credit.prototype.getSize = function getSize() {
|
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);
|
var bw = new StaticWriter(size);
|
||||||
this.coin.toWriter(bw);
|
this.coin.toWriter(bw);
|
||||||
bw.writeU8(this.spent ? 1 : 0);
|
bw.writeU8(this.spent ? 1 : 0);
|
||||||
|
bw.writeU8(this.own ? 1 : 0);
|
||||||
return bw.render();
|
return bw.render();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2867,6 +2879,7 @@ Credit.prototype.toRaw = function toRaw() {
|
|||||||
Credit.prototype.fromTX = function fromTX(tx, index, height) {
|
Credit.prototype.fromTX = function fromTX(tx, index, height) {
|
||||||
this.coin.fromTX(tx, index, height);
|
this.coin.fromTX(tx, index, height);
|
||||||
this.spent = false;
|
this.spent = false;
|
||||||
|
this.own = false;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1421,15 +1421,17 @@ Wallet.prototype._fund = co(function* fund(mtx, options) {
|
|||||||
if (!account.initialized)
|
if (!account.initialized)
|
||||||
throw new Error('Account is not initialized.');
|
throw new Error('Account is not initialized.');
|
||||||
|
|
||||||
coins = yield this.getCoins(options.account);
|
|
||||||
|
|
||||||
rate = options.rate;
|
rate = options.rate;
|
||||||
|
|
||||||
if (rate == null)
|
if (rate == null)
|
||||||
rate = yield this.db.estimateFee();
|
rate = yield this.db.estimateFee();
|
||||||
|
|
||||||
// Don't use any locked coins.
|
if (options.smart) {
|
||||||
coins = this.txdb.filterLocked(coins);
|
coins = yield this.getSmartCoins(options.account);
|
||||||
|
} else {
|
||||||
|
coins = yield this.getCoins(options.account);
|
||||||
|
coins = this.txdb.filterLocked(coins);
|
||||||
|
}
|
||||||
|
|
||||||
yield mtx.fund(coins, {
|
yield mtx.fund(coins, {
|
||||||
selection: options.selection,
|
selection: options.selection,
|
||||||
@ -2373,6 +2375,58 @@ Wallet.prototype.getCoins = co(function* getCoins(acct) {
|
|||||||
return yield this.txdb.getCoins(account);
|
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.
|
* Get all pending/unconfirmed transactions.
|
||||||
* @param {(String|Number)?} acct
|
* @param {(String|Number)?} acct
|
||||||
|
|||||||
@ -930,7 +930,7 @@ describe('Wallet', function() {
|
|||||||
assert(t2.verify());
|
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 w1 = yield walletdb.create();
|
||||||
var w2 = yield walletdb.create();
|
var w2 = yield walletdb.create();
|
||||||
var t1, t2;
|
var t1, t2;
|
||||||
@ -959,7 +959,7 @@ describe('Wallet', function() {
|
|||||||
assert.equal(t2.getFee(), 10000);
|
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 w1 = yield walletdb.create();
|
||||||
var w2 = yield walletdb.create();
|
var w2 = yield walletdb.create();
|
||||||
var options, t1, t2;
|
var options, t1, t2;
|
||||||
@ -993,6 +993,100 @@ describe('Wallet', function() {
|
|||||||
assert.equal(t2.getFee(), 10000);
|
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* () {
|
it('should get range of txs', co(function* () {
|
||||||
var w = wallet;
|
var w = wallet;
|
||||||
var txs = yield w.getRange({ start: util.now() - 1000 });
|
var txs = yield w.getRange({ start: util.now() - 1000 });
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user