3277 lines
90 KiB
JavaScript
3277 lines
90 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('bsert');
|
|
const {BufferMap} = require('buffer-map');
|
|
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 wcommon = require('../lib/wallet/common');
|
|
const WalletDB = require('../lib/wallet/walletdb');
|
|
const policy = require('../lib/protocol/policy');
|
|
const wutils = require('./util/wallet');
|
|
const primutils = require('./util/primitives');
|
|
const {randomP2PKAddress} = primutils;
|
|
const {DB_VALUE, DB_AGE, DB_ALL, DB_SWEEPDUST} = wcommon.coinSelectionTypes;
|
|
const {
|
|
nextBlock,
|
|
curBlock,
|
|
createInboundTXs,
|
|
fundWallet
|
|
} = wutils;
|
|
|
|
/** @typedef {import('../lib/wallet/wallet')} Wallet */
|
|
/** @typedef {import('../lib/primitives/tx')} TX */
|
|
/** @typedef {import('./util/primitives').CoinOptions} CoinOptions */
|
|
/** @typedef {wutils.OutputInfo} OutputInfo */
|
|
|
|
const UNCONFIRMED_HEIGHT = 0xffffffff;
|
|
|
|
// Use main instead of regtest because (deprecated)
|
|
// CoinSelector.MAX_FEE was network agnostic
|
|
const network = Network.get('main');
|
|
|
|
const DEFAULT_ACCOUNT = 'default';
|
|
const ALT_ACCOUNT = 'alt';
|
|
|
|
describe('Wallet Coin Selection', function() {
|
|
const TX_START_BAK = network.txStart;
|
|
/** @type {WalletDB?} */
|
|
let wdb;
|
|
/** @type {Wallet?} */
|
|
let wallet;
|
|
|
|
const beforeFn = async () => {
|
|
network.txStart = 0;
|
|
wdb = new WalletDB({ network });
|
|
|
|
await wdb.open();
|
|
await wdb.addBlock(nextBlock(wdb), []);
|
|
wallet = wdb.primary;
|
|
|
|
await wallet.createAccount({
|
|
name: ALT_ACCOUNT
|
|
});
|
|
};
|
|
|
|
const afterFn = async () => {
|
|
network.txStart = TX_START_BAK;
|
|
await wdb.close();
|
|
|
|
wdb = null;
|
|
wallet = null;
|
|
};
|
|
|
|
const indexes = [
|
|
'value-asc',
|
|
'value-desc',
|
|
'height-asc',
|
|
'height-desc'
|
|
];
|
|
|
|
for (const indexType of indexes) {
|
|
describe(`Coin Selection Indexes (${indexType})`, function() {
|
|
const TX_OPTIONS = [
|
|
{ value: 2e6, address: randomP2PKAddress() },
|
|
// address will be generated using wallet.
|
|
{ value: 1e6, covenant: { type: Covenant.types.OPEN } },
|
|
{ value: 5e6, covenant: { type: Covenant.types.REDEEM } },
|
|
{ value: 2e6 },
|
|
// alt account
|
|
{ value: 4e6, account: ALT_ACCOUNT },
|
|
{ value: 6e6, account: ALT_ACCOUNT, covenant: { type: Covenant.types.OPEN } },
|
|
{ value: 3e6, account: ALT_ACCOUNT, covenant: { type: Covenant.types.REDEEM } },
|
|
// non spendable coins must not get indexed.
|
|
{ value: 4e6, covenant: { type: Covenant.types.BID } },
|
|
{ value: 5e6, covenant: { type: Covenant.types.REVEAL } },
|
|
{ value: 6e6, covenant: { type: Covenant.types.REGISTER } },
|
|
{ value: 7e6, covenant: { type: Covenant.types.UPDATE } },
|
|
{ value: 8e6, covenant: { type: Covenant.types.RENEW } },
|
|
{ value: 9e6, covenant: { type: Covenant.types.TRANSFER } },
|
|
{ value: 10e6, covenant: { type: Covenant.types.FINALIZE } },
|
|
{ value: 11e6, covenant: { type: Covenant.types.REVOKE } }
|
|
];
|
|
|
|
const ACCT_0_COINS = 3;
|
|
const ACCT_0_FUNDS = 1e6 + 2e6 + 5e6;
|
|
const ACCT_1_COINS = 3;
|
|
const ACCT_1_FUNDS = 3e6 + 4e6 + 6e6;
|
|
const TOTAL_COINS = ACCT_0_COINS + ACCT_1_COINS;
|
|
const TOTAL_FUNDS = ACCT_0_FUNDS + ACCT_1_FUNDS;
|
|
|
|
let isSorted, getCredits;
|
|
const sumCredits = credits => credits.reduce((acc, c) => acc + c.coin.value, 0);
|
|
const checkWithLimits = async (credits, wallet, acct) => {
|
|
for (let i = 1; i < credits.length; i++) {
|
|
const creditsLimit = await getCredits(wallet, acct, {
|
|
limit: i
|
|
});
|
|
assert.strictEqual(creditsLimit.length, i);
|
|
assert(isSorted(creditsLimit), 'Credits not sorted.');
|
|
assert.deepStrictEqual(creditsLimit, credits.slice(0, i));
|
|
assert(sumCredits(creditsLimit) === sumCredits(credits.slice(0, i)));
|
|
}
|
|
};
|
|
|
|
before(() => {
|
|
switch (indexType) {
|
|
case 'value-asc':
|
|
isSorted = isSortedByValueAsc;
|
|
getCredits = (wallet, acct = 0, opts = {}) => {
|
|
return collectIter(wallet.getAccountCreditIterByValue(acct, opts));
|
|
};
|
|
break;
|
|
case 'value-desc':
|
|
isSorted = isSortedByValueDesc;
|
|
getCredits = (wallet, acct = 0, opts = {}) => {
|
|
return collectIter(wallet.getAccountCreditIterByValue(acct, {
|
|
...opts,
|
|
reverse: true
|
|
}));
|
|
};
|
|
break;
|
|
case 'height-asc':
|
|
isSorted = isSortedByHeightAsc;
|
|
getCredits = (wallet, acct = 0, opts = {}) => {
|
|
return collectIter(wallet.getAccountCreditIterByHeight(acct, opts));
|
|
};
|
|
break;
|
|
case 'height-desc':
|
|
isSorted = isSortedByHeightDesc;
|
|
getCredits = (wallet, acct = 0, opts = {}) => {
|
|
return collectIter(wallet.getAccountCreditIterByHeight(acct, {
|
|
...opts,
|
|
reverse: true
|
|
}));
|
|
};
|
|
break;
|
|
default:
|
|
throw new Error('Invalid index type.');
|
|
}
|
|
});
|
|
|
|
beforeEach(beforeFn);
|
|
afterEach(afterFn);
|
|
|
|
it('should index unconfirmed tx output', async () => {
|
|
const txs = await createInboundTXs(wallet, TX_OPTIONS);
|
|
|
|
for (const tx of txs)
|
|
await wallet.wdb.addTX(tx);
|
|
|
|
const credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
const credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
const both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert.strictEqual(credit.coin.height, -1);
|
|
assert.strictEqual(credit.spent, false);
|
|
}
|
|
});
|
|
|
|
it('should index unconfirmed tx input', async () => {
|
|
const currentBlock = curBlock(wdb);
|
|
await fundWallet(wallet, TX_OPTIONS, {
|
|
blockPerTX: true
|
|
});
|
|
|
|
const spendAll = await wallet.createTX({
|
|
hardFee: 0,
|
|
outputs: [{ value: TOTAL_FUNDS, address: randomP2PKAddress() }]
|
|
});
|
|
|
|
await wdb.addTX(spendAll.toTX());
|
|
|
|
// We still have the coin, even thought it is flagged: .spent = true
|
|
const credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
const credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
const both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert(credit.coin.height > currentBlock.height);
|
|
assert.strictEqual(credit.spent, true);
|
|
}
|
|
});
|
|
|
|
it('should index insert (block) tx output', async () => {
|
|
const currentBlock = curBlock(wdb);
|
|
await fundWallet(wallet, TX_OPTIONS, { blockPerTX: true });
|
|
|
|
const credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
const credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
const both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert(credit.coin.height > currentBlock.height);
|
|
assert.strictEqual(credit.spent, false);
|
|
}
|
|
});
|
|
|
|
it('should index insert (block) tx input', async () => {
|
|
await fundWallet(wallet, TX_OPTIONS, {
|
|
blockPerTX: false
|
|
});
|
|
const currentBlock = curBlock(wdb);
|
|
|
|
const spendAll = await wallet.createTX({
|
|
hardFee: 0,
|
|
outputs: [{ value: TOTAL_FUNDS, address: randomP2PKAddress() }]
|
|
});
|
|
|
|
let credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
let credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
let both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert.strictEqual(credit.coin.height, currentBlock.height);
|
|
assert.strictEqual(credit.spent, false);
|
|
}
|
|
|
|
await wdb.addBlock(nextBlock(wdb), [spendAll.toTX()]);
|
|
|
|
credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, 0);
|
|
|
|
credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, 0);
|
|
|
|
both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, 0);
|
|
});
|
|
|
|
it('should index confirm tx output', async () => {
|
|
const txs = await createInboundTXs(wallet, TX_OPTIONS);
|
|
for (const tx of txs)
|
|
await wdb.addTX(tx);
|
|
|
|
let credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
let credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
let both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert.strictEqual(credit.coin.height, -1);
|
|
assert.strictEqual(credit.spent, false);
|
|
}
|
|
|
|
await wdb.addBlock(nextBlock(wdb), txs);
|
|
const currentBlock = curBlock(wdb);
|
|
|
|
credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert.strictEqual(credit.coin.height, currentBlock.height);
|
|
assert.strictEqual(credit.spent, false);
|
|
}
|
|
});
|
|
|
|
it('should index confirm tx input', async () => {
|
|
const currentBlock = curBlock(wdb);
|
|
await fundWallet(wallet, TX_OPTIONS, {
|
|
blockPerTX: true
|
|
});
|
|
|
|
const spendAll = await wallet.createTX({
|
|
hardFee: 0,
|
|
outputs: [{ value: TOTAL_FUNDS, address: randomP2PKAddress() }]
|
|
});
|
|
const spendAllTX = spendAll.toTX();
|
|
|
|
await wdb.addTX(spendAllTX);
|
|
|
|
let credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
let credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
let both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert(credit.coin.height > currentBlock.height);
|
|
assert.strictEqual(credit.spent, true);
|
|
}
|
|
|
|
await wdb.addBlock(nextBlock(wdb), [spendAllTX]);
|
|
|
|
credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, 0);
|
|
|
|
credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, 0);
|
|
|
|
both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, 0);
|
|
});
|
|
|
|
it('should index disconnect tx output', async () => {
|
|
const currentBlock = curBlock(wdb);
|
|
await fundWallet(wallet, TX_OPTIONS, {
|
|
blockPerTX: true
|
|
});
|
|
|
|
let credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
let credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
let both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert(credit.coin.height > currentBlock.height);
|
|
assert.strictEqual(credit.spent, false);
|
|
}
|
|
|
|
// disconnect last block.
|
|
await wdb.rollback(currentBlock.height);
|
|
|
|
// Only thing that must change is the HEIGHT.
|
|
credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert.strictEqual(credit.coin.height, -1);
|
|
assert.strictEqual(credit.spent, false);
|
|
}
|
|
});
|
|
|
|
it('should index disconnect tx input', async () => {
|
|
const startingHeight = curBlock(wdb).height;
|
|
await fundWallet(wallet, TX_OPTIONS, { blockPerTX: true });
|
|
const createCoinHeight = curBlock(wdb).height;
|
|
|
|
const spendAll = await wallet.createTX({
|
|
hardFee: 0,
|
|
outputs: [{ value: TOTAL_FUNDS, address: randomP2PKAddress() }]
|
|
});
|
|
|
|
const spendAllTX = spendAll.toTX();
|
|
await wdb.addBlock(nextBlock(wdb), [spendAllTX]);
|
|
|
|
let credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, 0);
|
|
|
|
let credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, 0);
|
|
|
|
let both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, 0);
|
|
|
|
await wdb.rollback(createCoinHeight);
|
|
|
|
credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert(credit.coin.height > startingHeight);
|
|
assert.strictEqual(credit.spent, true);
|
|
}
|
|
});
|
|
|
|
it('should index erase tx output', async () => {
|
|
const txs = await createInboundTXs(wallet, TX_OPTIONS);
|
|
|
|
for (const tx of txs)
|
|
await wdb.addTX(tx);
|
|
|
|
let credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
let credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
let both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert.strictEqual(credit.coin.height, -1);
|
|
assert.strictEqual(credit.spent, false);
|
|
}
|
|
|
|
// double spend original txs.
|
|
const mtx = new MTX();
|
|
for (const tx of txs)
|
|
mtx.addInput(tx.inputs[0]);
|
|
mtx.addOutput(randomP2PKAddress(), 1e6);
|
|
|
|
await wdb.addBlock(nextBlock(wdb), [mtx.toTX()]);
|
|
|
|
credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, 0);
|
|
|
|
credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, 0);
|
|
|
|
both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, 0);
|
|
});
|
|
|
|
it('should index erase tx input', async () => {
|
|
const txs = await createInboundTXs(wallet, TX_OPTIONS);
|
|
for (const tx of txs)
|
|
await wdb.addTX(tx);
|
|
|
|
const spendAll = await wallet.createTX({
|
|
hardFee: 0,
|
|
outputs: [{ value: TOTAL_FUNDS, address: randomP2PKAddress() }]
|
|
});
|
|
|
|
await wdb.addTX(spendAll.toTX());
|
|
|
|
let credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
let credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
let both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert.strictEqual(credit.coin.height, -1);
|
|
assert.strictEqual(credit.spent, true);
|
|
}
|
|
|
|
// double spend original tx.
|
|
const mtx = new MTX();
|
|
for (const tx of txs)
|
|
mtx.addInput(tx.inputs[0]);
|
|
mtx.addOutput(randomP2PKAddress(), 1e6);
|
|
|
|
await wdb.addBlock(nextBlock(wdb), [mtx.toTX()]);
|
|
|
|
credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, 0);
|
|
|
|
credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, 0);
|
|
|
|
both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, 0);
|
|
});
|
|
|
|
it('should index erase (block) tx output', async () => {
|
|
const txOptions = [...TX_OPTIONS];
|
|
for (const opt of txOptions)
|
|
opt.coinbase = true;
|
|
|
|
const startingHeight = curBlock(wdb).height;
|
|
const txs = await fundWallet(wallet, txOptions, { blockPerTX: true });
|
|
|
|
for (const tx of txs)
|
|
assert(tx.isCoinbase());
|
|
|
|
let credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, ACCT_0_COINS);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === ACCT_0_FUNDS);
|
|
|
|
let credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, ACCT_1_COINS);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === ACCT_1_FUNDS);
|
|
|
|
let both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, TOTAL_COINS);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === TOTAL_FUNDS);
|
|
|
|
for (const credit of [...credits0, ...credits1, ...both]) {
|
|
assert(credit.coin.height > startingHeight);
|
|
assert.strictEqual(credit.spent, false);
|
|
}
|
|
|
|
await wdb.rollback(startingHeight);
|
|
|
|
credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, 0);
|
|
|
|
credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, 0);
|
|
|
|
both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, 0);
|
|
});
|
|
|
|
it('should index block and mempool', async () => {
|
|
const txOptionsConfirmed = [
|
|
{ value: 4e6 },
|
|
{ value: 7e6 },
|
|
{ value: 2e6, account: ALT_ACCOUNT },
|
|
{ value: 5e6, account: ALT_ACCOUNT }
|
|
];
|
|
await fundWallet(wallet, txOptionsConfirmed, {
|
|
blockPerTX: false
|
|
});
|
|
|
|
const txOptionsUnconfirmed = [
|
|
{ value: 8e6 },
|
|
{ value: 3e6 },
|
|
{ value: 6e6, account: ALT_ACCOUNT },
|
|
{ value: 1e6, account: ALT_ACCOUNT }
|
|
];
|
|
const txs = await createInboundTXs(wallet, txOptionsUnconfirmed, {
|
|
txPerOutput: false
|
|
});
|
|
await wdb.addTX(txs[0]);
|
|
|
|
const sum0 = 3e6 + 4e6 + 7e6 + 8e6;
|
|
const sum1 = 1e6 + 2e6 + 5e6 + 6e6;
|
|
|
|
const credits0 = await getCredits(wallet);
|
|
assert.strictEqual(credits0.length, 4);
|
|
assert(isSorted(credits0), 'Credits not sorted.');
|
|
assert(sumCredits(credits0) === sum0);
|
|
|
|
await checkWithLimits(credits0, wallet);
|
|
|
|
const credits1 = await getCredits(wallet, 1);
|
|
assert.strictEqual(credits1.length, 4);
|
|
assert(isSorted(credits1), 'Credits not sorted.');
|
|
assert(sumCredits(credits1) === sum1);
|
|
|
|
await checkWithLimits(credits1, wallet, 1);
|
|
|
|
const both = await getCredits(wallet, -1);
|
|
assert.strictEqual(both.length, 8);
|
|
assert(isSorted(both), 'Credits not sorted.');
|
|
assert(sumCredits(both) === sum0 + sum1);
|
|
|
|
await checkWithLimits(both, wallet, -1);
|
|
});
|
|
});
|
|
}
|
|
|
|
/** @type {OutputInfo[]} */
|
|
const PER_BLOCK_COINS = [
|
|
// confirmed per block.
|
|
{ value: 2e6 },
|
|
{ value: 2e6 },
|
|
{ value: 1e6, account: ALT_ACCOUNT },
|
|
{ value: 12e6 }, // LOCKED
|
|
{ value: 8e6 },
|
|
{ value: 10e6, account: ALT_ACCOUNT }, // LOCKED
|
|
{ value: 5e6, account: ALT_ACCOUNT }
|
|
];
|
|
|
|
/** @type {OutputInfo[]} */
|
|
const UNCONFIRMED_COINS = [
|
|
// unconfirmed
|
|
{ value: 3e6 }, // own
|
|
{ value: 6e6 },
|
|
{ value: 11e6 }, // LOCKED
|
|
{ value: 4e6, account: ALT_ACCOUNT }, // own
|
|
{ value: 7e6, account: ALT_ACCOUNT },
|
|
{ value: 9e6, account: ALT_ACCOUNT } // LOCKED
|
|
];
|
|
|
|
const LOCK = [9e6, 10e6, 11e6, 12e6];
|
|
const OWN = [
|
|
{ account: DEFAULT_ACCOUNT, value: 3e6 },
|
|
{ account: ALT_ACCOUNT, value: 4e6 }
|
|
];
|
|
|
|
const ACCT_0_CONFIRMED = 2e6 + 2e6 + 8e6; // 10e6
|
|
const ACCT_0_UNCONFIRMED = 3e6 + 6e6; // 9e6
|
|
const ACCT_0_FOREIGN = 6e6;
|
|
const ACCT_0_FUNDS = ACCT_0_CONFIRMED + ACCT_0_UNCONFIRMED; // 19e6
|
|
|
|
const ACCT_1_CONFIRMED = 1e6 + 5e6; // 6e6
|
|
const ACCT_1_UNCONFIRMED = 4e6 + 7e6; // 11e6
|
|
const ACCT_1_FOREIGN = 7e6;
|
|
const ACCT_1_FUNDS = ACCT_1_CONFIRMED + ACCT_1_UNCONFIRMED; // 17e6
|
|
|
|
/**
|
|
* @typedef {Object} SelectionTest
|
|
* @property {String} name
|
|
* @property {Object} options
|
|
* @property {Amount} value
|
|
* @property {Amount[]} [existingInputs] - use some coins that are resolved later.
|
|
* Use only unique value Coins.
|
|
* @property {CoinOptions[]} [existingCoins] - Coins that don't belong to the wallet,
|
|
* but are used in the mtx.
|
|
* @property {Amount[]} expectedOrdered
|
|
* @property {Object} [expectedSome] - Some of this must exist in mtx.
|
|
* * This is for AGE unconfirmed, which is not deterministic.
|
|
* @property {Number} expectedSome.count - Number of items that must exist.
|
|
* @property {Amount[]} expectedSome.items
|
|
*/
|
|
|
|
/** @type {Object<string, SelectionTest[]>} */
|
|
const SELECTION_TESTS = {
|
|
'value': [
|
|
// wallet by value
|
|
{
|
|
name: 'select 1 coin (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [8e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED,
|
|
expectedOrdered: [8e6, 5e6, 2e6, 2e6, 1e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [8e6, 5e6, 2e6, 2e6, 1e6, 7e6]
|
|
},
|
|
{
|
|
name: 'select all coins (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
expectedOrdered: [8e6, 5e6, 2e6, 2e6, 1e6, 7e6, 6e6, 4e6, 3e6]
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
requiredFunds: ACCT_0_FUNDS + ACCT_1_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
|
|
// default account by value
|
|
{
|
|
name: 'select 1 coin (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [8e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_0_CONFIRMED,
|
|
expectedOrdered: [8e6, 2e6, 2e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_0_CONFIRMED + 1e6,
|
|
expectedOrdered: [8e6, 2e6, 2e6, 6e6]
|
|
},
|
|
{
|
|
name: 'select all coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_0_FUNDS,
|
|
expectedOrdered: [8e6, 2e6, 2e6, 6e6, 3e6]
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_0_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS,
|
|
requiredFunds: ACCT_0_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
|
|
// alt account by value
|
|
{
|
|
name: 'select 1 coin (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [5e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_1_CONFIRMED,
|
|
expectedOrdered: [5e6, 1e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [5e6, 1e6, 7e6]
|
|
},
|
|
{
|
|
name: 'select all coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_1_FUNDS,
|
|
expectedOrdered: [5e6, 1e6, 7e6, 4e6]
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: ACCT_1_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_1_FUNDS,
|
|
requiredFunds: ACCT_1_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
}
|
|
],
|
|
'value + smart': [
|
|
// Test smart option.
|
|
// smart selection (wallet)
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'value',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [8e6, 5e6, 2e6, 2e6, 1e6, 4e6]
|
|
},
|
|
{
|
|
name: 'select all coins + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'value',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS - ACCT_0_FOREIGN - ACCT_1_FOREIGN,
|
|
expectedOrdered: [8e6, 5e6, 2e6, 2e6, 1e6, 4e6, 3e6]
|
|
},
|
|
{
|
|
name: 'throw funding error + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'value',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS + ACCT_1_FUNDS - ACCT_0_FOREIGN - ACCT_1_FOREIGN,
|
|
requiredFunds: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
// smart selection (default)
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_CONFIRMED + 1e6,
|
|
expectedOrdered: [8e6, 2e6, 2e6, 3e6]
|
|
},
|
|
{
|
|
name: 'select all coins + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS - ACCT_0_FOREIGN,
|
|
expectedOrdered: [8e6, 2e6, 2e6, 3e6]
|
|
},
|
|
{
|
|
name: 'throw funding error + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS - ACCT_0_FOREIGN,
|
|
requiredFunds: ACCT_0_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
// smart selection (alt)
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value',
|
|
smart: true
|
|
},
|
|
value: ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [5e6, 1e6, 4e6]
|
|
},
|
|
{
|
|
name: 'select all coins + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value',
|
|
smart: true
|
|
},
|
|
value: ACCT_1_FUNDS - ACCT_1_FOREIGN,
|
|
expectedOrdered: [5e6, 1e6, 4e6]
|
|
},
|
|
{
|
|
name: 'throw funding error + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value',
|
|
smart: true
|
|
},
|
|
value: ACCT_1_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_1_FUNDS - ACCT_1_FOREIGN,
|
|
requiredFunds: ACCT_1_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
}
|
|
],
|
|
// 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)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 1e6
|
|
}
|
|
],
|
|
expectedOrdered: [1e6, 8e6, 5e6]
|
|
},
|
|
// existing coins (default)
|
|
{
|
|
name: 'select coins + existing coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 1e6
|
|
}
|
|
],
|
|
expectedOrdered: [1e6, 8e6, 2e6]
|
|
},
|
|
// existing coins (alt)
|
|
{
|
|
name: 'select coins + existing coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'value'
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 1e6
|
|
}
|
|
],
|
|
expectedOrdered: [1e6, 5e6, 1e6, 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': [
|
|
// wallet by age
|
|
{
|
|
name: 'select 1 coin (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [2e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED,
|
|
expectedOrdered: [2e6, 2e6, 1e6, 8e6, 5e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [2e6, 2e6, 1e6, 8e6, 5e6],
|
|
expectedSome: {
|
|
count: 1,
|
|
items: [3e6, 6e6, 4e6, 7e6]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
expectedOrdered: [2e6, 2e6, 1e6, 8e6, 5e6],
|
|
expectedSome: {
|
|
count: 4,
|
|
items: [3e6, 6e6, 4e6, 7e6]
|
|
}
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
requiredFunds: ACCT_0_FUNDS + ACCT_1_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
|
|
// default account by age
|
|
{
|
|
name: 'select 1 coin (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [2e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_0_CONFIRMED,
|
|
expectedOrdered: [2e6, 2e6, 8e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_0_CONFIRMED + 1e6,
|
|
expectedOrdered: [2e6, 2e6, 8e6],
|
|
expectedSome: {
|
|
count: 1,
|
|
items: [3e6, 6e6]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_0_FUNDS,
|
|
expectedOrdered: [2e6, 2e6, 8e6],
|
|
expectedSome: {
|
|
count: 2,
|
|
items: [3e6, 6e6]
|
|
}
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_0_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS,
|
|
requiredFunds: ACCT_0_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
|
|
// alt account by age
|
|
{
|
|
name: 'select 1 coin (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [1e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_1_CONFIRMED,
|
|
expectedOrdered: [1e6, 5e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [1e6, 5e6],
|
|
expectedSome: {
|
|
count: 1,
|
|
items: [4e6, 7e6]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_1_FUNDS,
|
|
expectedOrdered: [1e6, 5e6],
|
|
expectedSome: {
|
|
count: 2,
|
|
items: [4e6, 7e6]
|
|
}
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: ACCT_1_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_1_FUNDS,
|
|
requiredFunds: ACCT_1_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
}
|
|
],
|
|
'age + smart': [
|
|
// Test smart option.
|
|
// smart selection (wallet)
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'age',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [2e6, 2e6, 1e6, 8e6, 5e6],
|
|
expectedSome: {
|
|
count: 1,
|
|
items: [3e6, 6e6, 4e6, 7e6]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'age',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS - ACCT_0_FOREIGN - ACCT_1_FOREIGN,
|
|
expectedOrdered: [2e6, 2e6, 1e6, 8e6, 5e6],
|
|
expectedSome: {
|
|
count: 2,
|
|
items: [3e6, 4e6]
|
|
}
|
|
},
|
|
{
|
|
name: 'throw funding error + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'age',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS + ACCT_1_FUNDS - ACCT_0_FOREIGN - ACCT_1_FOREIGN,
|
|
requiredFunds: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
// smart selection (default)
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_CONFIRMED + 1e6,
|
|
expectedOrdered: [2e6, 2e6, 8e6],
|
|
expectedSome: {
|
|
count: 1,
|
|
items: [3e6]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS - ACCT_0_FOREIGN,
|
|
expectedOrdered: [2e6, 2e6, 8e6],
|
|
expectedSome: {
|
|
count: 1,
|
|
items: [3e6]
|
|
}
|
|
},
|
|
{
|
|
name: 'throw funding error + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age',
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS - ACCT_0_FOREIGN,
|
|
requiredFunds: ACCT_0_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
// smart selection (alt)
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age',
|
|
smart: true
|
|
},
|
|
value: ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [1e6, 5e6],
|
|
expectedSome: {
|
|
count: 1,
|
|
items: [4e6]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age',
|
|
smart: true
|
|
},
|
|
value: ACCT_1_FUNDS - ACCT_1_FOREIGN,
|
|
expectedOrdered: [1e6, 5e6],
|
|
expectedSome: {
|
|
count: 1,
|
|
items: [4e6]
|
|
}
|
|
},
|
|
{
|
|
name: 'throw funding error + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age',
|
|
smart: true
|
|
},
|
|
value: ACCT_1_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_1_FUNDS - ACCT_1_FOREIGN,
|
|
requiredFunds: ACCT_1_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
}
|
|
],
|
|
// Existing coins = views + inputs
|
|
// Existing inputs = inputs (no view, needs extra resolving)
|
|
'age + existing inputs': [
|
|
// existing coins (wallet)
|
|
{
|
|
name: 'select coins + existing coins (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 1e6
|
|
}
|
|
],
|
|
expectedOrdered: [1e6, 2e6, 2e6, 1e6, 8e6]
|
|
},
|
|
// existing coins (default)
|
|
{
|
|
name: 'select coins + existing coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 1e6
|
|
}
|
|
],
|
|
expectedOrdered: [1e6, 2e6, 2e6, 8e6]
|
|
},
|
|
// existing coins (alt)
|
|
{
|
|
name: 'select coins + existing coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'age'
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 1e6
|
|
}
|
|
],
|
|
expectedOrdered: [1e6, 1e6, 5e6],
|
|
expectedSome: {
|
|
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': [
|
|
// wallet by all
|
|
{
|
|
name: 'select all coins (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'all'
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 9,
|
|
items: [
|
|
2e6, 2e6, 1e6, 8e6, 5e6,
|
|
3e6, 6e6, 4e6, 7e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
smart: true
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 7,
|
|
items: [
|
|
2e6, 2e6, 1e6, 8e6, 5e6,
|
|
3e6, 4e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + depth = 0 (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
depth: 0
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 9,
|
|
items: [
|
|
2e6, 2e6, 1e6, 8e6, 5e6,
|
|
3e6, 6e6, 4e6, 7e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + depth = 1 (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
depth: 1
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 5,
|
|
items: [
|
|
2e6, 2e6, 1e6, 8e6, 5e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + depth = 3 (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
depth: 3
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 4,
|
|
items: [
|
|
2e6, 2e6, 1e6, 8e6
|
|
]
|
|
}
|
|
},
|
|
|
|
// wallet by default
|
|
{
|
|
name: 'select all coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all'
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 5,
|
|
items: [
|
|
2e6, 2e6, 8e6,
|
|
3e6, 6e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
smart: true
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 4,
|
|
items: [
|
|
2e6, 2e6, 8e6,
|
|
3e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + depth = 0 (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
depth: 0
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 5,
|
|
items: [
|
|
2e6, 2e6, 8e6,
|
|
3e6, 6e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + depth = 1 (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
depth: 1
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 3,
|
|
items: [
|
|
2e6, 2e6, 8e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + depth = 4 (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
depth: 4
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 2,
|
|
items: [
|
|
2e6, 2e6
|
|
]
|
|
}
|
|
},
|
|
|
|
// wallet by alt
|
|
{
|
|
name: 'select all coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all'
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 4,
|
|
items: [
|
|
1e6, 5e6,
|
|
4e6, 7e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
smart: true
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 3,
|
|
items: [
|
|
1e6, 5e6,
|
|
4e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + depth = 0 (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
depth: 0
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 4,
|
|
items: [
|
|
1e6, 5e6,
|
|
4e6, 7e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + depth = 1 (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
depth: 1
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 2,
|
|
items: [
|
|
1e6, 5e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all coins + depth = 4 (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all',
|
|
depth: 4
|
|
},
|
|
value: 1e6, // should select all regardless.
|
|
expectedOrdered: [],
|
|
expectedSome: {
|
|
count: 1,
|
|
items: [
|
|
1e6
|
|
]
|
|
}
|
|
}
|
|
],
|
|
// Existing coins = views + inputs
|
|
// Existing inputs = inputs (no view, needs extra resolving)
|
|
'all + existing inputs': [
|
|
{
|
|
name: 'select all + existing coin (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: 'all'
|
|
},
|
|
value: 2e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 1e6
|
|
}
|
|
],
|
|
expectedOrdered: [1e6],
|
|
expectedSome: {
|
|
count: 9,
|
|
items: [
|
|
2e6, 2e6, 1e6, 8e6, 5e6,
|
|
3e6, 6e6, 4e6, 7e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all + existing coin (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all'
|
|
},
|
|
value: 2e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 1e6
|
|
}
|
|
],
|
|
expectedOrdered: [1e6],
|
|
expectedSome: {
|
|
count: 5,
|
|
items: [
|
|
2e6, 2e6, 8e6,
|
|
3e6, 6e6
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: 'select all + existing coin (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: 'all'
|
|
},
|
|
value: 2e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 3e6
|
|
}
|
|
],
|
|
expectedOrdered: [3e6],
|
|
expectedSome: {
|
|
count: 4,
|
|
items: [
|
|
1e6, 5e6,
|
|
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.'
|
|
}
|
|
}
|
|
],
|
|
'sweepdust': [
|
|
// wallet by sweep
|
|
{
|
|
name: 'select 1 coin (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [1e6]
|
|
},
|
|
{
|
|
name: 'select 1 coin, minvalue (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 1e6 + 1
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [2e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED,
|
|
expectedOrdered: [1e6, 2e6, 2e6, 5e6, 8e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins, minvalue (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 1e6 + 1
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED - 1e6,
|
|
expectedOrdered: [2e6, 2e6, 5e6, 8e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [1e6, 2e6, 2e6, 5e6, 8e6, 3e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed, minvalue (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED - 5e6 + 1e6,
|
|
expectedOrdered: [5e6, 8e6, 3e6]
|
|
},
|
|
{
|
|
name: 'select all coins (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
expectedOrdered: [1e6, 2e6, 2e6, 5e6, 8e6, 3e6, 4e6, 6e6, 7e6]
|
|
},
|
|
{
|
|
name: 'select all coins, minvalue (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS - 5e6,
|
|
expectedOrdered: [5e6, 8e6, 3e6, 4e6, 6e6, 7e6]
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
requiredFunds: ACCT_0_FUNDS + ACCT_1_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error, filterall (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 100e6
|
|
},
|
|
value: 1e6,
|
|
error: {
|
|
availableFunds: 0,
|
|
requiredFunds: 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
|
|
// default account by value
|
|
{
|
|
name: 'select 1 coin (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [2e6]
|
|
},
|
|
{
|
|
name: 'select 1 coin, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1
|
|
},
|
|
value: 2e6,
|
|
expectedOrdered: [8e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_0_CONFIRMED,
|
|
expectedOrdered: [2e6, 2e6, 8e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1
|
|
},
|
|
value: 8e6,
|
|
expectedOrdered: [8e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_0_CONFIRMED + 1e6,
|
|
expectedOrdered: [2e6, 2e6, 8e6, 3e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 3e6 + 1
|
|
},
|
|
value: 8e6 + 1e6,
|
|
expectedOrdered: [8e6, 6e6]
|
|
},
|
|
{
|
|
name: 'select all coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_0_FUNDS,
|
|
expectedOrdered: [2e6, 2e6, 8e6, 3e6, 6e6]
|
|
},
|
|
{
|
|
name: 'select all coins, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 3e6 + 1
|
|
},
|
|
value: 8e6 + 6e6,
|
|
expectedOrdered: [8e6, 6e6]
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_0_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS,
|
|
requiredFunds: ACCT_0_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1
|
|
},
|
|
value: ACCT_0_FUNDS + 1e6 - (4e6),
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS - 4e6,
|
|
requiredFunds: ACCT_0_FUNDS - 4e6 + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
|
|
// alt account by value
|
|
{
|
|
name: 'select 1 coin (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [1e6]
|
|
},
|
|
{
|
|
name: 'select 1 coin, minvalue (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 1e6 + 1
|
|
},
|
|
value: 1e6,
|
|
expectedOrdered: [5e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_1_CONFIRMED,
|
|
expectedOrdered: [1e6, 5e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed coins, minvalue (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 1e6 + 1
|
|
},
|
|
value: ACCT_1_CONFIRMED - 1e6,
|
|
expectedOrdered: [5e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [1e6, 5e6, 4e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 4e6 + 1
|
|
},
|
|
value: ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [5e6, 7e6]
|
|
},
|
|
{
|
|
name: 'select all coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_1_FUNDS,
|
|
expectedOrdered: [1e6, 5e6, 4e6, 7e6]
|
|
},
|
|
{
|
|
name: 'select all coins, minvalue (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 4e6 + 1
|
|
},
|
|
value: ACCT_1_FUNDS - 5e6,
|
|
expectedOrdered: [5e6, 7e6]
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: ACCT_1_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_1_FUNDS,
|
|
requiredFunds: ACCT_1_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
{
|
|
// test locked filters.
|
|
name: 'throw funding error (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 4e6 + 1
|
|
},
|
|
value: ACCT_1_FUNDS + 1e6,
|
|
error: {
|
|
availableFunds: ACCT_1_FUNDS - 5e6,
|
|
requiredFunds: ACCT_1_FUNDS + 1e6,
|
|
type: 'FundingError'
|
|
}
|
|
}
|
|
],
|
|
'sweepdust + smart': [
|
|
// Test smart option.
|
|
// smart selection (wallet)
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [1e6, 2e6, 2e6, 5e6, 8e6, 3e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart, minvalue (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 3e6 + 1,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_CONFIRMED + ACCT_1_CONFIRMED + 1e6 - 5e6,
|
|
expectedOrdered: [5e6, 8e6, 4e6]
|
|
},
|
|
{
|
|
name: 'select all coins + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS - ACCT_0_FOREIGN - ACCT_1_FOREIGN,
|
|
expectedOrdered: [1e6, 2e6, 2e6, 5e6, 8e6, 3e6, 4e6]
|
|
},
|
|
{
|
|
name: 'select all coins + smart, minvalue (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 3e6 + 1,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS - ACCT_0_FOREIGN - ACCT_1_FOREIGN - 5e6 - 3e6,
|
|
expectedOrdered: [5e6, 8e6, 4e6]
|
|
},
|
|
{
|
|
name: 'throw funding error + smart (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS + ACCT_1_FUNDS - ACCT_0_FOREIGN - ACCT_1_FOREIGN,
|
|
requiredFunds: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
{
|
|
name: 'throw funding error + smart, minvalue (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 3e6 + 1,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS + ACCT_1_FUNDS - ACCT_0_FOREIGN - ACCT_1_FOREIGN - 5e6 - 3e6,
|
|
requiredFunds: ACCT_0_FUNDS + ACCT_1_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
// smart selection (default)
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_CONFIRMED + 1e6,
|
|
expectedOrdered: [2e6, 2e6, 8e6, 3e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_CONFIRMED + 1e6 - 4e6,
|
|
expectedOrdered: [8e6, 3e6]
|
|
},
|
|
{
|
|
name: 'select all coins + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS - ACCT_0_FOREIGN,
|
|
expectedOrdered: [2e6, 2e6, 8e6, 3e6]
|
|
},
|
|
{
|
|
name: 'select all coins + smart, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS - ACCT_0_FOREIGN - 4e6,
|
|
expectedOrdered: [8e6, 3e6]
|
|
},
|
|
{
|
|
name: 'throw funding error + smart (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS - ACCT_0_FOREIGN,
|
|
requiredFunds: ACCT_0_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
{
|
|
name: 'throw funding error + smart, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1,
|
|
smart: true
|
|
},
|
|
value: ACCT_0_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_0_FUNDS - ACCT_0_FOREIGN - 4e6,
|
|
requiredFunds: ACCT_0_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
// smart selection (alt)
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
smart: true
|
|
},
|
|
value: ACCT_1_CONFIRMED + 1e6,
|
|
expectedOrdered: [1e6, 5e6, 4e6]
|
|
},
|
|
{
|
|
name: 'select all confirmed and an unconfirmed + smart, minvalue (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 1e6 + 1,
|
|
smart: true
|
|
},
|
|
value: ACCT_1_CONFIRMED + 1e6 - 1e6,
|
|
expectedOrdered: [5e6, 4e6]
|
|
},
|
|
{
|
|
name: 'select all coins + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
smart: true
|
|
},
|
|
value: ACCT_1_FUNDS - ACCT_1_FOREIGN,
|
|
expectedOrdered: [1e6, 5e6, 4e6]
|
|
},
|
|
{
|
|
name: 'select all coins + smart, minvalue (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 1e6 + 1,
|
|
smart: true
|
|
},
|
|
value: ACCT_1_FUNDS - ACCT_1_FOREIGN - 1e6,
|
|
expectedOrdered: [5e6, 4e6]
|
|
},
|
|
{
|
|
name: 'throw funding error + smart (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
smart: true
|
|
},
|
|
value: ACCT_1_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_1_FUNDS - ACCT_1_FOREIGN,
|
|
requiredFunds: ACCT_1_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
},
|
|
{
|
|
name: 'throw funding error + smart, minvalue (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 1e6 + 1,
|
|
smart: true
|
|
},
|
|
value: ACCT_1_FUNDS,
|
|
error: {
|
|
availableFunds: ACCT_1_FUNDS - ACCT_1_FOREIGN - 1e6,
|
|
requiredFunds: ACCT_1_FUNDS,
|
|
type: 'FundingError'
|
|
}
|
|
}
|
|
],
|
|
// Existing coins = views + inputs
|
|
// Existing inputs = inputs (no view, needs extra resolving)
|
|
'sweepdust + existing coins and inputs': [
|
|
// existing coins (wallet)
|
|
{
|
|
name: 'select coins + existing coins (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 8e6
|
|
}
|
|
],
|
|
expectedOrdered: [8e6, 1e6, 2e6]
|
|
},
|
|
{
|
|
name: 'select coins + existing coins, minvalue (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 8e6
|
|
}
|
|
],
|
|
expectedOrdered: [8e6, 5e6]
|
|
},
|
|
// existing coins (default)
|
|
{
|
|
name: 'select coins + existing coins (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 7e6
|
|
}
|
|
],
|
|
expectedOrdered: [7e6, 2e6, 2e6]
|
|
},
|
|
{
|
|
name: 'select coins + existing coins, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 7e6
|
|
}
|
|
],
|
|
expectedOrdered: [7e6, 8e6]
|
|
},
|
|
// existing coins (alt)
|
|
{
|
|
name: 'select coins + existing coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 10e6,
|
|
existingCoins: [
|
|
{
|
|
height: -1,
|
|
value: 1e6
|
|
}
|
|
],
|
|
expectedOrdered: [1e6, 1e6, 5e6, 4e6]
|
|
},
|
|
{
|
|
name: 'select coins + existing inputs (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 10e6,
|
|
existingInputs: [5e6],
|
|
expectedOrdered: [5e6, 1e6, 2e6, 2e6]
|
|
},
|
|
{
|
|
name: 'select coins + existing inputs, minvalue (wallet)',
|
|
options: {
|
|
account: -1,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1
|
|
},
|
|
value: 10e6,
|
|
existingInputs: [5e6],
|
|
expectedOrdered: [5e6, 8e6]
|
|
},
|
|
// existing coins (default)
|
|
{
|
|
name: 'select coins + existing inputs (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 10e6,
|
|
existingInputs: [3e6],
|
|
expectedOrdered: [3e6, 2e6, 2e6, 8e6]
|
|
},
|
|
{
|
|
name: 'select coins + existing inputs, minvalue (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 2e6 + 1
|
|
},
|
|
value: 10e6,
|
|
existingInputs: [2e6, 3e6],
|
|
expectedOrdered: [2e6, 3e6, 8e6]
|
|
},
|
|
// existing coins (alt)
|
|
{
|
|
name: 'select coins + existing coins (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 10e6,
|
|
existingInputs: [4e6],
|
|
expectedOrdered: [4e6, 1e6, 5e6]
|
|
},
|
|
{
|
|
name: 'select coins + existing coins, minvalue (alt)',
|
|
options: {
|
|
account: ALT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST,
|
|
sweepdustMinValue: 1e6 + 1
|
|
},
|
|
value: 9e6,
|
|
existingInputs: [4e6],
|
|
expectedOrdered: [4e6, 5e6]
|
|
},
|
|
// fail existing inputs (cross account)
|
|
{
|
|
name: 'fail cross account existing inputs (default)',
|
|
options: {
|
|
account: DEFAULT_ACCOUNT,
|
|
hardFee: 0,
|
|
selection: DB_SWEEPDUST
|
|
},
|
|
value: 10e6,
|
|
existingInputs: [5e6], // this belongs to alt account
|
|
error: {
|
|
message: 'Could not resolve preferred inputs.'
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
const reselect = (tests, selection) => {
|
|
return tests.map((t) => {
|
|
const options = {
|
|
...t.options,
|
|
selection
|
|
};
|
|
|
|
return {
|
|
...t,
|
|
options
|
|
};
|
|
});
|
|
};
|
|
|
|
// Selection `value` and `dbvalue` are the same.
|
|
SELECTION_TESTS['dbvalue'] = reselect(SELECTION_TESTS['value'], DB_VALUE);
|
|
SELECTION_TESTS['dbvalue + smart'] = reselect(SELECTION_TESTS['value + smart'], DB_VALUE);
|
|
SELECTION_TESTS['dbvalue + existing coins and inputs'] = reselect(
|
|
SELECTION_TESTS['value + existing coins and inputs'], DB_VALUE);
|
|
|
|
// Same with `age` and `dbage`.
|
|
SELECTION_TESTS['db-age'] = reselect(SELECTION_TESTS['age'], DB_AGE);
|
|
SELECTION_TESTS['db-age + smart'] = reselect(SELECTION_TESTS['age + smart'], DB_AGE);
|
|
SELECTION_TESTS['db-age + existing inputs'] = reselect(
|
|
SELECTION_TESTS['age + existing inputs'], DB_AGE);
|
|
|
|
SELECTION_TESTS['db-all'] = reselect(SELECTION_TESTS['all'], DB_ALL);
|
|
SELECTION_TESTS['db-all + existing inputs'] = reselect(
|
|
SELECTION_TESTS['all + existing inputs'], DB_ALL);
|
|
|
|
for (const [name, testCase] of Object.entries(SELECTION_TESTS)) {
|
|
describe(`Wallet Coin Selection by ${name}`, function() {
|
|
// fund wallet.
|
|
const valueByCoin = new BufferMap();
|
|
// This is used for OWN and LOCK descriptions.
|
|
// The values must be unique in the UTXO set.
|
|
const coinByValue = new Map();
|
|
|
|
/**
|
|
* Fund the same coin in multiple different ways.
|
|
* @param {OutputInfo} output
|
|
* @returns {OutputInfo[]}
|
|
*/
|
|
|
|
const fundCoinOptions = (output) => {
|
|
const spendables = [
|
|
Covenant.types.NONE,
|
|
Covenant.types.OPEN,
|
|
Covenant.types.REDEEM
|
|
];
|
|
|
|
const nonSpendables = [
|
|
Covenant.types.BID,
|
|
Covenant.types.REVEAL,
|
|
Covenant.types.REGISTER,
|
|
Covenant.types.UPDATE,
|
|
Covenant.types.RENEW,
|
|
Covenant.types.TRANSFER,
|
|
Covenant.types.FINALIZE,
|
|
Covenant.types.REVOKE
|
|
];
|
|
|
|
const account = output.account || 0;
|
|
const value = output.value;
|
|
const oneSpendable = spendables[Math.floor(Math.random() * spendables.length)];
|
|
|
|
return [{ value, account, covenant: { type: oneSpendable }}]
|
|
.concat(nonSpendables.map(t => ({ value, account, covenant: { type: t }})));
|
|
};
|
|
|
|
// NOTE: tests themselves don't modify the wallet state, so before instead
|
|
// of beforeEach should be fine.
|
|
before(async () => {
|
|
await beforeFn();
|
|
|
|
valueByCoin.clear();
|
|
coinByValue.clear();
|
|
|
|
for (const coinOptions of PER_BLOCK_COINS) {
|
|
const outputInfos = fundCoinOptions(coinOptions);
|
|
const txs = await fundWallet(wallet, outputInfos, {
|
|
txPerOutput: true
|
|
});
|
|
|
|
for (const [i, tx] of txs.entries()) {
|
|
if (tx.outputs.length !== 1)
|
|
continue;
|
|
|
|
if (tx.output(0).isUnspendable() || tx.output(0).covenant.isNonspendable())
|
|
continue;
|
|
|
|
const coin = Coin.fromTX(tx, 0, i + 1);
|
|
valueByCoin.set(coin.toKey(), tx.output(0).value);
|
|
coinByValue.set(tx.output(0).value, coin);
|
|
}
|
|
}
|
|
|
|
for (const coinOptions of UNCONFIRMED_COINS) {
|
|
const options = fundCoinOptions(coinOptions);
|
|
const txs = await createInboundTXs(wallet, options);
|
|
|
|
for (const tx of txs) {
|
|
await wallet.wdb.addTX(tx);
|
|
|
|
if (tx.outputs.length !== 1)
|
|
continue;
|
|
|
|
if (tx.output(0).isUnspendable() || tx.output(0).covenant.isNonspendable())
|
|
continue;
|
|
|
|
const coin = Coin.fromTX(tx, 0, -1);
|
|
valueByCoin.set(coin.toKey(), tx.output(0).value);
|
|
coinByValue.set(tx.output(0).value, coin);
|
|
}
|
|
}
|
|
|
|
for (const value of LOCK) {
|
|
const coin = coinByValue.get(value);
|
|
wallet.lockCoin(coin);
|
|
}
|
|
|
|
for (const {account, value} of OWN) {
|
|
const coin = coinByValue.get(value);
|
|
const mtx = new MTX();
|
|
mtx.addOutput(await wallet.receiveAddress(account), value);
|
|
mtx.addCoin(coin);
|
|
await wallet.finalize(mtx);
|
|
await wallet.sign(mtx);
|
|
const tx = mtx.toTX();
|
|
await wdb.addTX(tx);
|
|
|
|
valueByCoin.delete(coin.toKey());
|
|
coinByValue.delete(coin.value);
|
|
|
|
const ownedCoin = Coin.fromTX(mtx, 0, -1);
|
|
valueByCoin.set(ownedCoin.toKey(), mtx.output(0).value);
|
|
coinByValue.set(mtx.output(0).value, ownedCoin);
|
|
}
|
|
});
|
|
|
|
after(afterFn);
|
|
|
|
for (const fundingTest of testCase) {
|
|
it(`should ${fundingTest.name}`, async () => {
|
|
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);
|
|
valueByCoin.set(coin.toKey(), coin.value);
|
|
mtx.addCoin(coin);
|
|
}
|
|
}
|
|
|
|
let err;
|
|
|
|
try {
|
|
await wallet.fund(mtx, fundingTest.options);
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
if (fundingTest.error) {
|
|
assert(err);
|
|
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;
|
|
}
|
|
|
|
assert(!err, err);
|
|
|
|
const inputVals = mtx.inputs.map(({prevout}) => valueByCoin.get(prevout.toKey()));
|
|
|
|
assert(inputVals.length >= fundingTest.expectedOrdered.length,
|
|
'Not enough inputs selected.');
|
|
|
|
assert.deepStrictEqual(
|
|
inputVals.slice(0, fundingTest.expectedOrdered.length),
|
|
fundingTest.expectedOrdered);
|
|
|
|
const left = inputVals.slice(fundingTest.expectedOrdered.length);
|
|
|
|
if (!fundingTest.expectedSome) {
|
|
assert(left.length === 0, 'Extra inputs selected.');
|
|
return;
|
|
}
|
|
|
|
let count = fundingTest.expectedSome.count;
|
|
const items = fundingTest.expectedSome.items.slice();
|
|
|
|
for (const value of left) {
|
|
assert(items.includes(value), `Value ${value} not in expected.`);
|
|
assert(count > 0, 'Too many inputs selected.');
|
|
|
|
const idx = items.indexOf(value);
|
|
items.splice(idx, 1);
|
|
count--;
|
|
}
|
|
|
|
assert(count === 0, 'Not enough inputs selected.');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
describe('Selection types', function() {
|
|
beforeEach(beforeFn);
|
|
afterEach(afterFn);
|
|
|
|
it('should select all spendable coins', async () => {
|
|
const spendableCovs = [
|
|
Covenant.types.NONE,
|
|
Covenant.types.OPEN,
|
|
Covenant.types.REDEEM
|
|
];
|
|
|
|
const nonSpendableCovs = [
|
|
Covenant.types.BID,
|
|
Covenant.types.REVEAL,
|
|
Covenant.types.REGISTER,
|
|
Covenant.types.UPDATE,
|
|
Covenant.types.RENEW,
|
|
Covenant.types.TRANSFER,
|
|
Covenant.types.FINALIZE,
|
|
Covenant.types.REVOKE
|
|
];
|
|
|
|
const mkopt = type => ({ value: 1e6, covenant: { type }});
|
|
await fundWallet(wallet, [...nonSpendableCovs, ...spendableCovs].map(mkopt));
|
|
|
|
const coins = await wallet.getCoins();
|
|
assert.strictEqual(coins.length, spendableCovs.length + nonSpendableCovs.length);
|
|
|
|
const spendables = await collectIter(wallet.getAccountCreditIterByValue(0));
|
|
assert.strictEqual(spendables.length, spendableCovs.length);
|
|
|
|
const mtx = new MTX();
|
|
await wallet.fund(mtx, {
|
|
selection: 'all'
|
|
});
|
|
|
|
assert.strictEqual(mtx.inputs.length, spendableCovs.length);
|
|
});
|
|
|
|
it('should select coin by descending value', async () => {
|
|
const values = [1e6, 4e6, 3e6, 5e6, 2e6];
|
|
await fundWallet(wallet, values.map(value => ({ value })));
|
|
|
|
const mtx = new MTX();
|
|
mtx.addOutput(randomP2PKAddress(), 9e6);
|
|
|
|
await wallet.fund(mtx, {
|
|
selection: 'value',
|
|
hardFee: 0
|
|
});
|
|
|
|
// 4 + 5
|
|
assert.strictEqual(mtx.inputs.length, 2);
|
|
assert.strictEqual(mtx.outputs.length, 1);
|
|
assert.strictEqual(mtx.outputs[0].value, 9e6);
|
|
});
|
|
|
|
it('should select coins by descending age', async () => {
|
|
const values = [1e6, 2e6, 3e6, 4e6, 5e6];
|
|
|
|
for (const value of values)
|
|
await fundWallet(wallet, [{ value }]);
|
|
|
|
const mtx = new MTX();
|
|
mtx.addOutput(randomP2PKAddress(), 9e6);
|
|
await wallet.fund(mtx, {
|
|
selection: 'age',
|
|
hardFee: 0
|
|
});
|
|
|
|
// 1 + 2 + 3 + 4 = 10
|
|
assert.strictEqual(mtx.inputs.length, 4);
|
|
assert.strictEqual(mtx.outputs.length, 2);
|
|
assert.strictEqual(mtx.outputs[0].value, 9e6);
|
|
assert.strictEqual(mtx.outputs[1].value, 1e6);
|
|
});
|
|
});
|
|
|
|
describe('Fees', function() {
|
|
before(beforeFn);
|
|
after(afterFn);
|
|
|
|
it('should fund wallet', async () => {
|
|
const vals = [100e6, 10e6, 1e6, 0.1e6, 0.01e6];
|
|
await fundWallet(wallet, vals.map(value => ({ value })));
|
|
const bal = await wallet.getBalance();
|
|
assert.strictEqual(bal.confirmed, 111.11e6);
|
|
});
|
|
|
|
it('should pay default fee rate for small tx', async () => {
|
|
const address = await wallet.receiveAddress();
|
|
const mtx = new MTX();
|
|
mtx.addOutput(address, 5e6);
|
|
await wallet.fund(mtx);
|
|
await wallet.sign(mtx);
|
|
|
|
assert.strictEqual(mtx.inputs.length, 1);
|
|
assert.strictEqual(mtx.outputs.length, 2);
|
|
|
|
const rate = mtx.getRate();
|
|
const fee = mtx.getFee();
|
|
|
|
assert.strictEqual(rate, network.feeRate);
|
|
assert(rate < network.maxFeeRate);
|
|
assert(fee > network.minRelay);
|
|
});
|
|
|
|
it('should pay default fee rate for maximum policy weight TX', async () => {
|
|
const address = await wallet.receiveAddress();
|
|
const mtx = new MTX();
|
|
for (let i = 0; i < 3120; i++) {
|
|
mtx.addOutput(address, 500);
|
|
}
|
|
// Add nulldata output to add precise amount of extra weight
|
|
mtx.addOutput(
|
|
{
|
|
version: 31,
|
|
hash: Buffer.alloc(38)
|
|
},
|
|
0
|
|
);
|
|
await wallet.fund(mtx);
|
|
await wallet.sign(mtx);
|
|
|
|
// This is as close as we can get to
|
|
// policy.MAX_TX_WEIGHT (400000) using standard wallet
|
|
assert.strictEqual(mtx.getWeight(), 399997);
|
|
assert.strictEqual(mtx.inputs.length, 1);
|
|
|
|
const rate = mtx.getRate();
|
|
const fee = mtx.getFee();
|
|
|
|
assert.strictEqual(fee, 10e6); // 10 HNS
|
|
|
|
assert.strictEqual(rate, network.feeRate);
|
|
assert(rate < network.maxFeeRate);
|
|
assert(fee > network.minRelay);
|
|
});
|
|
|
|
it('should fail to pay absurd fee rate for small tx', async () => {
|
|
const address = await wallet.receiveAddress();
|
|
let err;
|
|
|
|
try {
|
|
await wallet.send({
|
|
outputs: [{
|
|
address,
|
|
value: 5e6
|
|
}],
|
|
rate: (policy.ABSURD_FEE_FACTOR + 1) * network.minRelay
|
|
});
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
assert(err, 'Error not thrown.');
|
|
assert.strictEqual(err.message, 'Fee exceeds absurd limit.');
|
|
});
|
|
|
|
it('should pay fee just under the absurd limit', async () => {
|
|
const address = await wallet.receiveAddress();
|
|
const tx = await wallet.send({
|
|
outputs: [{
|
|
address,
|
|
value: 5e6
|
|
}],
|
|
rate: 10000 * network.minRelay
|
|
});
|
|
const view = await wallet.getWalletCoinView(tx);
|
|
assert.strictEqual(
|
|
tx.getRate(view),
|
|
policy.ABSURD_FEE_FACTOR * network.minRelay
|
|
);
|
|
});
|
|
|
|
it('should fail to pay too-low fee rate for small tx', async () => {
|
|
const address = await wallet.receiveAddress();
|
|
await assert.rejects(
|
|
wallet.send({
|
|
outputs: [{
|
|
address,
|
|
value: 5e6
|
|
}],
|
|
rate: network.minRelay - 1
|
|
}),
|
|
{message: 'Fee is below minimum relay limit.'}
|
|
);
|
|
});
|
|
|
|
it('should pay fee at the minimum relay limit', async () => {
|
|
const address = await wallet.receiveAddress();
|
|
const tx = await wallet.send({
|
|
outputs: [{
|
|
address,
|
|
value: 5e6
|
|
}],
|
|
rate: network.minRelay
|
|
});
|
|
const view = await wallet.getWalletCoinView(tx);
|
|
assert.strictEqual(tx.getRate(view), network.minRelay);
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Collect iterator items.
|
|
* @template T
|
|
* @param {AsyncGenerator<T>} iter
|
|
* @returns {Promise<T[]>}
|
|
*/
|
|
|
|
async function collectIter(iter) {
|
|
const items = [];
|
|
|
|
for await (const item of iter)
|
|
items.push(item);
|
|
|
|
return items;
|
|
}
|
|
|
|
/**
|
|
* @param {Credit[]} credits
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
function isSortedByValueAsc(credits) {
|
|
for (let i = 1; i < credits.length; i++) {
|
|
const prev = credits[i - 1].coin;
|
|
const cur = credits[i].coin;
|
|
|
|
if (prev.height === -1 && cur.height !== -1)
|
|
return false;
|
|
|
|
if (prev.height !== -1 && cur.height === -1)
|
|
continue;
|
|
|
|
if (prev.value > cur.value)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param {Credit[]} credits
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
function isSortedByValueDesc(credits) {
|
|
for (let i = 1; i < credits.length; i++) {
|
|
const prev = credits[i - 1].coin;
|
|
const cur = credits[i].coin;
|
|
|
|
if (prev.height === -1 && cur.height !== -1)
|
|
return false;
|
|
|
|
if (prev.height !== -1 && cur.height === -1)
|
|
continue;
|
|
|
|
if (prev.value < cur.value)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param {Credit[]} credits
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
function isSortedByHeightAsc(credits) {
|
|
for (let i = 1; i < credits.length; i++) {
|
|
let prevHeight = credits[i - 1].coin.height;
|
|
let curHeight = credits[i].coin.height;
|
|
|
|
if (prevHeight === -1)
|
|
prevHeight = UNCONFIRMED_HEIGHT;
|
|
|
|
if (curHeight === -1)
|
|
curHeight = UNCONFIRMED_HEIGHT;
|
|
|
|
if (prevHeight > curHeight)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param {Credit[]} credits
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
function isSortedByHeightDesc(credits) {
|
|
for (let i = 1; i < credits.length; i++) {
|
|
let prevHeight = credits[i - 1].coin.height;
|
|
let curHeight = credits[i].coin.height;
|
|
|
|
if (prevHeight === -1)
|
|
prevHeight = UNCONFIRMED_HEIGHT;
|
|
|
|
if (curHeight === -1)
|
|
curHeight = UNCONFIRMED_HEIGHT;
|
|
|
|
if (prevHeight < curHeight)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|