diff --git a/lib/utils/coinselector.js b/lib/utils/coinselector.js index e2f0d77d..48c4d59d 100644 --- a/lib/utils/coinselector.js +++ b/lib/utils/coinselector.js @@ -458,7 +458,7 @@ class CoinSelector { // Ensure all preferred inputs first. await this.resolveInputCoins(); - if (this.isFull()) + if (this.isFull() && !this.selectAll) return; for (;;) { diff --git a/test/wallet-coinselection-test.js b/test/wallet-coinselection-test.js index 6eaa3574..c96fb575 100644 --- a/test/wallet-coinselection-test.js +++ b/test/wallet-coinselection-test.js @@ -6,6 +6,7 @@ const Network = require('../lib/protocol/network'); const MTX = require('../lib/primitives/mtx'); const Covenant = require('../lib/primitives/covenant'); const Coin = require('../lib/primitives/coin'); +const Input = require('../lib/primitives/input'); const WalletDB = require('../lib/wallet/walletdb'); const policy = require('../lib/protocol/policy'); const wutils = require('./util/wallet'); @@ -18,6 +19,7 @@ const {coinbaseInput, dummyInput, randomP2PKAddress} = primutils; /** @typedef {import('../lib/primitives/output')} Output */ /** @typedef {import('../lib/primitives/tx')} TX */ /** @typedef {import('./util/primitives').CoinOptions} CoinOptions */ +/** @typedef {import('../lib/types').Amount} Amount */ const UNCONFIRMED_HEIGHT = 0xffffffff; @@ -674,12 +676,14 @@ describe('Wallet Coin Selection', function() { * @typedef {Object} SelectionTest * @property {String} name * @property {Object} options - * @property {Number} value - * @property {CoinOptions[]} existingCoins - * @property {Number[]} expectedOrdered + * @property {Amount} value + * @property {Amount[]} [existingInputs] - use some coins that are resolved later. + * Use only unique value Coins. + * @property {CoinOptions[]} [existingCoins] + * @property {Amount[]} expectedOrdered * @property {Object} [expectedSome] - Some of this must exist in mtx. * @property {Number} expectedSome.count - Number of items that must exist. - * @property {Number[]} expectedSome.items + * @property {Amount[]} expectedSome.items */ /** @type {Object} */ @@ -973,7 +977,9 @@ describe('Wallet Coin Selection', function() { } } ], - 'value + existing inputs': [ + // Existing coins = views + inputs + // Existing inputs = inputs (no view, needs extra resolving) + 'value + existing coins and inputs': [ // existing coins (wallet) { name: 'select coins + existing coins (wallet)', @@ -1028,6 +1034,55 @@ describe('Wallet Coin Selection', function() { count: 1, items: [4e6, 7e6] } + }, + { + name: 'select coins + existing inputs (wallet)', + options: { + account: -1, + hardFee: 0, + selection: 'value' + }, + value: 10e6, + existingInputs: [5e6], + expectedOrdered: [5e6, 8e6] + }, + // existing coins (default) + { + name: 'select coins + existing inputs (default)', + options: { + account: DEFAULT_ACCOUNT, + hardFee: 0, + selection: 'value' + }, + value: 10e6, + existingInputs: [3e6], + expectedOrdered: [3e6, 8e6] + }, + // existing coins (alt) + { + name: 'select coins + existing coins (alt)', + options: { + account: ALT_ACCOUNT, + hardFee: 0, + selection: 'value' + }, + value: 10e6, + existingInputs: [4e6], + expectedOrdered: [4e6, 5e6, 1e6] + }, + // fail existing inputs (cross account) + { + name: 'fail cross account existing inputs (default)', + options: { + account: DEFAULT_ACCOUNT, + hardFee: 0, + selection: 'value' + }, + value: 10e6, + existingInputs: [5e6], // this belongs to alt account + error: { + message: 'Could not resolve preferred inputs.' + } } ], 'age': [ @@ -1367,6 +1422,8 @@ describe('Wallet Coin Selection', function() { } } ], + // Existing coins = views + inputs + // Existing inputs = inputs (no view, needs extra resolving) 'age + existing inputs': [ // existing coins (wallet) { @@ -1422,6 +1479,55 @@ describe('Wallet Coin Selection', function() { count: 1, items: [4e6, 7e6] } + }, + { + name: 'select coins + existing inputs (wallet)', + options: { + account: -1, + hardFee: 0, + selection: 'age' + }, + value: 10e6, + existingInputs: [5e6], + expectedOrdered: [5e6, 2e6, 2e6, 1e6] + }, + // existing coins (default) + { + name: 'select coins + existing inputs (default)', + options: { + account: DEFAULT_ACCOUNT, + hardFee: 0, + selection: 'age' + }, + value: 10e6, + existingInputs: [3e6], + expectedOrdered: [3e6, 2e6, 2e6, 8e6] + }, + // existing coins (alt) + { + name: 'select coins + existing coins (alt)', + options: { + account: ALT_ACCOUNT, + hardFee: 0, + selection: 'age' + }, + value: 10e6, + existingInputs: [4e6], + expectedOrdered: [4e6, 1e6, 5e6] + }, + // fail existing inputs (cross account) + { + name: 'fail cross account existing inputs (default)', + options: { + account: DEFAULT_ACCOUNT, + hardFee: 0, + selection: 'age' + }, + value: 10e6, + existingInputs: [5e6], // this belongs to alt account + error: { + message: 'Could not resolve preferred inputs.' + } } ], 'all': [ @@ -1692,9 +1798,11 @@ describe('Wallet Coin Selection', function() { } } ], + // Existing coins = views + inputs + // Existing inputs = inputs (no view, needs extra resolving) 'all + existing inputs': [ { - name: 'select 1 coin (wallet)', + name: 'select all + existing coin (wallet)', options: { account: -1, hardFee: 0, @@ -1717,7 +1825,7 @@ describe('Wallet Coin Selection', function() { } }, { - name: 'select 1 coin (default)', + name: 'select all + existing coin (default)', options: { account: DEFAULT_ACCOUNT, hardFee: 0, @@ -1740,7 +1848,7 @@ describe('Wallet Coin Selection', function() { } }, { - name: 'select 1 coin (alt)', + name: 'select all + existing coin (alt)', options: { account: ALT_ACCOUNT, hardFee: 0, @@ -1750,10 +1858,10 @@ describe('Wallet Coin Selection', function() { existingCoins: [ { height: -1, - value: 1e6 + value: 3e6 } ], - expectedOrdered: [1e6], + expectedOrdered: [3e6], expectedSome: { count: 4, items: [ @@ -1761,6 +1869,88 @@ describe('Wallet Coin Selection', function() { 4e6, 7e6 ] } + }, + { + name: 'select all + existing input (wallet)', + options: { + account: -1, + hardFee: 0, + selection: 'all' + }, + value: 2e6, + existingInputs: [8e6], + expectedOrdered: [8e6], + expectedSome: { + count: 8, + items: [ + 2e6, 2e6, 1e6, 5e6, + 3e6, 6e6, 4e6, 7e6 + ] + } + }, + { + name: 'select all + existing input (default)', + options: { + account: DEFAULT_ACCOUNT, + hardFee: 0, + selection: 'all' + }, + value: 2e6, + existingInputs: [8e6], + expectedOrdered: [8e6], + expectedSome: { + count: 4, + items: [ + 2e6, 2e6, + 3e6, 6e6 + ] + } + }, + { + name: 'select all + existing input (alt)', + options: { + account: ALT_ACCOUNT, + hardFee: 0, + selection: 'all' + }, + value: 2e6, + existingInputs: [5e6], + expectedOrdered: [5e6], + expectedSome: { + count: 3, + items: [1e6, 4e6, 7e6] + } + }, + { + name: 'select all + existing input + estimate (wallet)', + options: { + account: -1, + selection: 'all', + rate: 5e7 + }, + value: 2e6, + existingInputs: [8e6], + expectedOrdered: [8e6], + expectedSome: { + count: 8, + items: [ + 2e6, 2e6, 1e6, 5e6, + 3e6, 6e6, 4e6, 7e6 + ] + } + }, + { + name: 'fail cross account existing inputs (default)', + options: { + account: DEFAULT_ACCOUNT, + hardFee: 0, + selection: 'all' + }, + value: 2e6, + existingInputs: [5e6], // this belongs to alt account + error: { + message: 'Could not resolve preferred inputs.' + } } ] }; @@ -1882,6 +2072,16 @@ describe('Wallet Coin Selection', function() { const mtx = new MTX(); mtx.addOutput(randomP2PKAddress(), fundingTest.value); + if (fundingTest.existingInputs) { + for (const inputVal of fundingTest.existingInputs) { + const coin = coinByValue.get(inputVal); + assert(coin, `Coin not found for value ${inputVal}.`); + + const input = Input.fromCoin(coin); + mtx.addInput(input); + } + } + if (fundingTest.existingCoins) { for (const coinOptions of fundingTest.existingCoins) { const coin = primutils.makeCoin(coinOptions); @@ -1903,6 +2103,9 @@ describe('Wallet Coin Selection', function() { assert.strictEqual(err.type, fundingTest.error.type); assert.strictEqual(err.availableFunds, fundingTest.error.availableFunds); assert.strictEqual(err.requiredFunds, fundingTest.error.requiredFunds); + + if (fundingTest.error.message) + assert.strictEqual(err.message, fundingTest.error.message); return; } @@ -2150,7 +2353,7 @@ describe('Wallet Coin Selection', function() { * @typedef {Object} OutputInfo * @property {String} [address] * @property {Number} [account=0] - address generation account. - * @property {Number} [value] + * @property {Amount} [value] * @property {covenantTypes} [covenant] * @property {Boolean} [coinbase=false] */