itns-sidechain/test/wallet-pagination-test.js

1093 lines
32 KiB
JavaScript

'use strict';
const assert = require('bsert');
const Network = require('../lib/protocol/network');
const MTX = require('../lib/primitives/mtx');
const WalletDB = require('../lib/wallet/walletdb');
const consensus = require('../lib/protocol/consensus');
const util = require('../lib/utils/util');
const wutils = require('./util/wallet');
const {nextEntry} = wutils;
const {dummyInput} = require('./util/primitives');
/** @typedef {import('../lib/wallet/wallet')} Wallet */
const network = Network.get('main');
// single request per page.
const MAX_HISTORY = 20;
const DEFAULT = 'default';
const ALT_ACCOUNT = 'alt';
const UNCONFIRMED_HEIGHT = 0xffffffff;
const MAX_TIME = 0xffffffff;
const GENESIS_TIME = 1580745078;
describe('WalletDB Pagination', function() {
/** @type {WalletDB} */
let wdb;
/** @type {Wallet} */
let wallet;
let timeCounter = GENESIS_TIME;
const setupWDB = async () => {
wdb = new WalletDB({
maxHistoryTXs: MAX_HISTORY,
network
});
await wdb.open();
wallet = wdb.primary;
const altAccount = await wallet.createAccount({
name: ALT_ACCOUNT
});
assert(altAccount);
wallet.txdb.nowFn = () => timeCounter++;
};
const cleanupWDB = async () => {
timeCounter = GENESIS_TIME;
await wdb.wipe();
};
describe('Index unconfirm counts', function() {
beforeEach(setupWDB);
afterEach(cleanupWDB);
it('should increment unconfirmed count: -> unconfirmed tx', async () => {
const initUCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(initUCount.index, 0);
const mtx = await dummyTX(wallet);
const wids = await wdb.addTX(mtx.toTX());
assert(wids);
assert.strictEqual(wids.wids.size, 1);
const txCount = await wallet.txdb.getCountForTX(mtx.hash());
assert.strictEqual(txCount.index, initUCount.index);
assert.strictEqual(txCount.height, txCount.height);
const uCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(uCount.index, 1);
});
it('should not increment unconfirmed count: unconfirmed -> confirmed', async () => {
const totalTXs = 10;
const mtxs = [];
for (let i = 0; i < totalTXs; i++) {
const mtx = await dummyTX(wallet);
await wdb.addTX(mtx.toTX());
mtxs.push(mtx);
}
const initUCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(initUCount.index, totalTXs);
const entry = nextEntry(wdb);
await wdb.addBlock(entry, mtxs.map(mtx => mtx.toTX()));
const uCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(uCount.index, totalTXs);
});
it('should increment unconfirmed count: -> confirmed', async () => {
const totalTXs = 10;
// add some mock txs (Not part of test, just moving unconfirmed count)
for (let i = 0; i < totalTXs; i++) {
const mtx = await dummyTX(wallet);
await wdb.addTX(mtx.toTX());
}
const initUCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(initUCount.index, totalTXs);
const mtx = await dummyTX(wallet);
const entry = nextEntry(wdb);
await wdb.addBlock(entry, [mtx.toTX()]);
const txCount = await wallet.txdb.getCountForTX(mtx.hash());
// this is block index
assert.strictEqual(txCount.index, 0);
const uCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(uCount.index, totalTXs + 1);
});
it('should not increment count: confirmed -> unconfirmed', async () => {
const totalConfirmed = 5;
const totalUnconfirmed = 5;
const totalTXs = totalConfirmed + totalUnconfirmed;
const entries = [];
const mtxs = [];
const times = [];
// 5 directly confirmed
for (let i = 0; i < totalConfirmed; i++) {
const mtx = await dummyTX(wallet);
const entry = nextEntry(wdb);
await wdb.addBlock(entry, [mtx.toTX()]);
// timeCounter is incremented twice, once for wdb.add/txrecord
// creation and another on time index creation.
times.push(timeCounter - 1);
entries.push(entry);
mtxs.push(mtx);
}
// 5 unconfirmed -> confirmed
for (let i = 0; i < totalUnconfirmed; i++) {
const mtx = await dummyTX(wallet);
await wdb.addTX(mtx.toTX());
const entry = nextEntry(wdb);
await wdb.addBlock(entry, [mtx.toTX()]);
// timeCounter is incremented twice, once for wdb.add/txrecord
// creation and another on time index creation.
times.push(timeCounter - 1);
entries.push(entry);
mtxs.push(mtx);
}
const initUCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(initUCount.index, totalTXs);
for (const entry of entries.reverse())
await wdb.removeBlock(entry);
const countUAfter = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(countUAfter.index, totalTXs);
for (const [index, mtx] of mtxs.entries()) {
const count = await wallet.txdb.getCountForTX(mtx.hash());
assert.strictEqual(count.height, UNCONFIRMED_HEIGHT);
assert.strictEqual(count.index, index);
const txByTime = await wallet.listUnconfirmedByTime(-1, {
limit: 1,
time: times[index],
reverse: false
});
const txByTimeRev = await wallet.listUnconfirmedByTime(-1, {
limit: 1,
time: times[index],
reverse: true
});
assert.bufferEqual(txByTime[0].hash, mtx.hash());
assert.bufferEqual(txByTimeRev[0].hash, mtx.hash());
}
});
it('should not decrement count: unconfirmed -> erase', async () => {
const totalTXs = 10;
const mtxs = [];
for (let i = 0; i < 10; i++) {
const mtx = await dummyTX(wallet);
await wdb.addTX(mtx.toTX());
mtxs.push(mtx);
}
const initUCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(initUCount.index, totalTXs);
for (const mtx of mtxs)
await wallet.remove(mtx.hash());
const uCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(uCount.index, totalTXs);
});
it('should not decrement count: confirmed -> erase', async () => {
const totalTXs = 10;
const entries = [];
for (let i = 0; i < totalTXs; i++) {
const cbTX = await dummyTX(wallet);
cbTX.inputs[0].prevout.hash = consensus.ZERO_HASH;
cbTX.inputs[0].prevout.index = 0xffffffff;
const entry = nextEntry(wdb);
await wdb.addBlock(entry, [cbTX.toTX()]);
entries.push(entry);
}
const initUCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(initUCount.index, totalTXs);
// Coinbase txs will get erased after remove block.
for (const entry of entries.reverse())
await wdb.removeBlock(entry);
const uCount = await wallet.txdb.getLatestUnconfirmedTXCount();
assert.strictEqual(uCount.index, totalTXs);
const txs = await wallet.listHistory(-1, {
limit: MAX_HISTORY,
reverse: true
});
assert.strictEqual(txs.length, 0);
});
});
describe('Query TXs', function() {
// default unconfirmed
const defAcc = {
name: DEFAULT,
unconf: 40,
conf: 40,
unconfHashes: [],
unconfByTime: new Map(),
confHashes: [],
confByTime: new Map()
};
const altAcc = {
name: ALT_ACCOUNT,
unconf: 40,
conf: 40,
unconfHashes: [],
unconfByTime: new Map(),
confHashes: [],
confByTime: new Map()
};
const total = {
name: -1,
unconf: defAcc.unconf + altAcc.unconf,
conf: defAcc.conf + altAcc.conf,
unconfHashes: [],
unconfByTime: new Map(),
confHashes: [],
confByTime: new Map()
};
before(async () => {
await setupWDB();
assert(defAcc.unconf % 4 === 0);
assert(altAcc.unconf % 4 === 0);
assert(defAcc.conf % 2 === 0);
assert(altAcc.conf % 2 === 0);
const setupUnconfirmed = async (account) => {
// We grab time - 1, since we are incrementing after each insertion.
// half unconfirmed
for (let i = 0; i < account.unconf / 2; i++) {
const mtx = await dummyTX(wallet, account.name);
await wdb.addTX(mtx.toTX());
account.unconfHashes.push(mtx.hash());
total.unconfHashes.push(mtx.hash());
account.unconfByTime.set(timeCounter - 1, mtx.hash());
total.unconfByTime.set(timeCounter - 1, mtx.hash());
}
let entries = [];
// 1/4 confirmed -> unconfirmed
for (let i = 0; i < account.unconf / 4; i++) {
const mtx = await dummyTX(wallet, account.name);
const entry = nextEntry(wdb);
await wdb.addBlock(entry, [mtx.toTX()]);
entries.push(entry);
account.unconfHashes.push(mtx.hash());
total.unconfHashes.push(mtx.hash());
account.unconfByTime.set(timeCounter - 1, mtx.hash());
total.unconfByTime.set(timeCounter - 1, mtx.hash());
}
for (const entry of entries.reverse())
await wdb.removeBlock(entry);
// 1/4 unconfirmed -> confirmed -> unconfirmed
entries = [];
for (let i = 0; i < account.unconf / 4; i++) {
const mtx = await dummyTX(wallet, account.name);
await wdb.addTX(mtx.toTX());
const entry = nextEntry(wdb);
await wdb.addBlock(entry, [mtx.toTX()]);
entries.push(entry);
account.unconfHashes.push(mtx.hash());
total.unconfHashes.push(mtx.hash());
account.unconfByTime.set(timeCounter - 1, mtx.hash());
total.unconfByTime.set(timeCounter - 1, mtx.hash());
}
for (const entry of entries.reverse())
await wdb.removeBlock(entry);
};
const setupConfirmed = async (account) => {
const addConfirmedByTime = (mtp, mtx) => {
// account specific
const accList = account.confByTime.get(mtp) || [];
accList.push(mtx.hash());
account.confByTime.set(mtp, accList);
const totalList = total.confByTime.get(mtp) || [];
totalList.push(mtx.hash());
total.confByTime.set(mtp, totalList);
};
// half direct confirmed
for (let i = 0; i < account.conf / 2; i++) {
const mtx = await dummyTX(wallet, account.name);
const entry = nextEntry(wdb);
await wdb.addBlock(entry, [mtx.toTX()]);
const mtp = await wdb.getMedianTime(entry.height);
addConfirmedByTime(mtp, mtx);
account.confHashes.push(mtx.hash());
total.confHashes.push(mtx.hash());
}
// half unconfirmed -> confirmed
for (let i = 0; i < account.conf / 2; i++) {
const mtx = await dummyTX(wallet, account.name);
await wdb.addTX(mtx.toTX());
const entry = nextEntry(wdb);
await wdb.addBlock(entry, [mtx.toTX()]);
const mtp = await wdb.getMedianTime(entry.height);
addConfirmedByTime(mtp, mtx);
account.confHashes.push(mtx.hash());
total.confHashes.push(mtx.hash());
}
};
// Add 3 blocks to correct mtp.
for (let i = 0; i < 3; i++) {
const entry = nextEntry(wdb);
await wdb.addBlock(entry, []);
}
await setupUnconfirmed(defAcc);
await setupUnconfirmed(altAcc);
await setupConfirmed(defAcc);
await setupConfirmed(altAcc);
const balance = await wallet.getBalance();
assert.strictEqual(balance.tx, total.unconf + total.conf);
});
after(async () => {
await cleanupWDB();
});
it('should query unconfirmed (asc)', async () => {
const check = async (accountInfo, limit) => {
const history = await wallet.listUnconfirmed(accountInfo.name, {
limit: limit,
reverse: false
});
assert.strictEqual(history.length, limit);
assert.deepStrictEqual(
history.map(entry => entry.hash),
accountInfo.unconfHashes.slice(0, limit)
);
};
for (let i = 1; i < MAX_HISTORY; i++) {
await check(defAcc, i);
await check(altAcc, i);
await check(total, i);
}
});
it('should query unconfirmed (desc)', async () => {
const check = async (accountInfo, limit) => {
const history = await wallet.listUnconfirmed(accountInfo.name, {
limit: limit,
reverse: true
});
const hashes = accountInfo.unconfHashes;
assert.strictEqual(history.length, limit);
assert.deepStrictEqual(
history.map(entry => entry.hash).reverse(),
hashes.slice(hashes.length - limit)
);
};
for (let i = 1; i < MAX_HISTORY; i++) {
await check(defAcc, i);
await check(altAcc, i);
await check(total, i);
}
});
it('should query unconfirmed after (asc)', async () => {
const check = async (accountID, hashes) => {
const listAfter = await wallet.listUnconfirmedAfter(accountID, {
hash: hashes[0],
limit: MAX_HISTORY,
reverse: false
});
const len = Math.min(MAX_HISTORY, hashes.length - 1);
assert.strictEqual(listAfter.length, len);
assert.deepStrictEqual(
listAfter.map(entry => entry.hash),
hashes.slice(1, len + 1)
);
};
// slide the hashes
for (let i = 0; i < defAcc.unconfHashes.length; i++) {
await check(DEFAULT, defAcc.unconfHashes.slice(i), MAX_HISTORY);
}
for (let i = 0; i < altAcc.unconfHashes.length; i++) {
await check(ALT_ACCOUNT, altAcc.unconfHashes.slice(i), MAX_HISTORY);
}
for (let i = 0; i < total.unconfHashes.length; i++) {
await check(-1, total.unconfHashes.slice(i), MAX_HISTORY);
}
});
it('should query unconfirmed after (desc)', async () => {
const check = async (accountID, hashes) => {
const listAfter = await wallet.listUnconfirmedAfter(accountID, {
hash: hashes[0],
limit: MAX_HISTORY,
reverse: true
});
const len = Math.min(MAX_HISTORY, hashes.length - 1);
assert.strictEqual(listAfter.length, len);
const hashesSlice = hashes.slice(1, len + 1);
assert.deepStrictEqual(
listAfter.map(entry => entry.hash),
hashesSlice
);
};
for (let i = 1; i < defAcc.unconfHashes.length; i++)
await check(DEFAULT, defAcc.unconfHashes.slice(0, -i).reverse());
for (let i = 1; i < altAcc.unconfHashes.length; i++)
await check(ALT_ACCOUNT, altAcc.unconfHashes.slice(0, -i).reverse());
for (let i = 1; i < total.unconfHashes.length; i++)
await check(-1, total.unconfHashes.slice(0, -i).reverse());
});
it('should query unconfirmed by time (asc)', async () => {
const check = async (accountID, time, hashes) => {
const listByTime = await wallet.listUnconfirmedByTime(accountID, {
limit: MAX_HISTORY,
time: time,
reverse: false
});
const len = Math.min(MAX_HISTORY, hashes.length);
assert.strictEqual(listByTime.length, len);
assert.deepStrictEqual(
listByTime.map(entry => entry.hash),
hashes.slice(0, len)
);
};
const checkForAccount = async (accountInfo) => {
let i = 0;
for (const [time, hash] of accountInfo.unconfByTime.entries()) {
const checkHash = accountInfo.unconfHashes[i];
assert.bufferEqual(hash, checkHash);
await check(accountInfo.name, time, accountInfo.unconfHashes.slice(i));
i++;
}
};
await checkForAccount(defAcc);
await checkForAccount(altAcc);
await checkForAccount(total);
});
it('should query unconfirmed by time (desc)', async () => {
const check = async (accountID, time, hashes) => {
const listByTime = await wallet.listUnconfirmedByTime(accountID, {
limit: MAX_HISTORY,
time: time,
reverse: true
});
const len = Math.min(MAX_HISTORY, hashes.length);
assert.strictEqual(listByTime.length, len);
const hashesSlice = hashes.slice(0, len);
assert.deepStrictEqual(
listByTime.map(entry => entry.hash),
hashesSlice
);
};
const checkForAccount = async (accountInfo) => {
let i = accountInfo.unconfHashes.length;
const entries = Array.from(accountInfo.unconfByTime.entries());
for (const [time, hash] of entries.reverse()) {
const checkHash = accountInfo.unconfHashes[i - 1];
assert.bufferEqual(hash, checkHash);
const hashes = accountInfo.unconfHashes.slice(0, i).reverse();
await check(accountInfo.name, time, hashes);
i--;
}
};
for (const account of [defAcc, altAcc, total])
await checkForAccount(account);
for (const account of [defAcc, altAcc, total]) {
const hashes = account.unconfHashes;
const from = hashes.length - MAX_HISTORY;
await check(account.name, MAX_TIME, hashes.slice(from).reverse());
}
});
it('should query history (asc)', async () => {
const check = async (accountInfo, limit) => {
const history = await wallet.listHistory(accountInfo.name, {
limit: limit,
reverse: false
});
assert.strictEqual(history.length, limit);
assert.deepStrictEqual(
history.map(entry => entry.hash),
accountInfo.confHashes.slice(0, limit)
);
};
for (let i = 0; i < MAX_HISTORY; i++) {
await check(defAcc, i);
await check(altAcc, i);
await check(total, i);
}
});
it('should query history (desc)', async () => {
const check = async (accountInfo, limit) => {
const history = await wallet.listHistory(accountInfo.name, {
limit: limit,
reverse: true
});
// Unconfirmed txs are the newest ones.
const hashes = accountInfo.unconfHashes;
assert.strictEqual(history.length, limit);
assert.deepStrictEqual(
history.map(entry => entry.hash).reverse(),
hashes.slice(hashes.length - limit)
);
};
for (let i = 0; i < MAX_HISTORY; i++) {
await check(defAcc, i);
await check(altAcc, i);
await check(total, i);
}
});
it('should query history after (asc)', async () => {
const check = async (accountID, hashes) => {
const listAfter = await wallet.listHistoryAfter(accountID, {
hash: hashes[0],
limit: MAX_HISTORY,
reverse: false
});
const len = Math.min(MAX_HISTORY, hashes.length - 1);
assert.strictEqual(listAfter.length, len);
assert.deepStrictEqual(
listAfter.map(entry => entry.hash),
hashes.slice(1, len + 1)
);
};
for (const account of [defAcc, altAcc, total]) {
const all = account.confHashes.concat(account.unconfHashes);
for (let i = 0; i < all.length; i++)
await check(account.name, all.slice(i));
}
});
it('should query history after (desc)', async () => {
const check = async (accountID, hashes) => {
const listAfter = await wallet.listHistoryAfter(accountID, {
hash: hashes[0],
limit: MAX_HISTORY,
reverse: true
});
const len = Math.min(MAX_HISTORY, hashes.length - 1);
assert.strictEqual(listAfter.length, len);
const hashesSlice = hashes.slice(1, len + 1);
assert.deepStrictEqual(
listAfter.map(entry => entry.hash),
hashesSlice
);
};
for (const account of [defAcc, altAcc, total]) {
const all = account.confHashes.concat(account.unconfHashes);
for (let i = 1; i < all.length; i++)
await check(account.name, all.slice(0, -i).reverse());
}
});
it('should query history by time (asc)', async () => {
const check = async (accountID, time, hashes) => {
const listByTime = await wallet.listHistoryByTime(accountID, {
limit: MAX_HISTORY,
time: time,
reverse: false
});
const len = Math.min(MAX_HISTORY, hashes.length);
assert.strictEqual(listByTime.length, len);
assert.deepStrictEqual(
listByTime.map(entry => entry.hash),
hashes.slice(0, len)
);
};
for (const account of [defAcc, altAcc, total]) {
for (const [time, hashes] of account.confByTime.entries()) {
// Because mtp can be same for two blocks, we need to find the
// first confirmed tx in the list.
const first = hashes[0];
assert(first);
const index = bufIndexOf(first, account.confHashes);
assert.notStrictEqual(index, -1);
// historyByTime, even though it only indexes confirmed tx times,
// will continue to return unconfirmed txs after the confirmed txs.
const confirmed = account.confHashes.slice(index);
const unconfirmed = account.unconfHashes;
await check(account.name, time, confirmed.concat(unconfirmed));
}
}
});
it('should query history by time (desc)', async () => {
const check = async (accountID, time, hashes) => {
const listByTime = await wallet.listHistoryByTime(accountID, {
limit: MAX_HISTORY,
time: time,
reverse: true
});
const len = Math.min(MAX_HISTORY, hashes.length);
assert.strictEqual(listByTime.length, len);
const hashesSlice = hashes.slice(0, len);
assert.deepStrictEqual(
listByTime.map(entry => entry.hash),
hashesSlice
);
};
const checkForAccount = async (accountInfo) => {
const entries = Array.from(accountInfo.confByTime.entries());
for (const [time, hashes] of entries.reverse()) {
// Because mtp can be same for two blocks, we need to find the
// last confirmed tx in the list. (desc)
const last = hashes[hashes.length - 1];
assert(last);
const index = bufIndexOf(last, accountInfo.confHashes);
assert.notStrictEqual(index, -1);
// because we are going in reverse, we no longer need to
// include unconfirmed txs.
const checkHashes = accountInfo.confHashes.slice(0, index + 1).reverse();
await check(accountInfo.name, time, checkHashes);
}
};
for (const account of [defAcc, altAcc, total])
await checkForAccount(account);
// Because query by history only looks up first confirmed tx in the list,
// we wont encounter unconfirmed txs, even if they are newer.
for (const account of [defAcc, altAcc, total]) {
const hashes = account.confHashes;
const from = hashes.length - MAX_HISTORY;
await check(account.name, MAX_TIME, hashes.slice(from).reverse());
}
});
});
describe('Index/Query', function() {
beforeEach(setupWDB);
afterEach(cleanupWDB);
it('should fail to query more than max history', async () => {
const N = MAX_HISTORY + 1;
const methods = [{
method: wallet.listHistory,
args: [-1, {
limit: N,
reverse: false
}]
}, {
method: wallet.listHistoryByTime,
args: [-1, {
limit: N,
reverse: false,
time: GENESIS_TIME
}]
}, {
method: wallet.listHistoryAfter,
args: [-1, {
limit: N,
reverse: false,
hash: consensus.ZERO_HASH
}]
}, {
method: wallet.listHistoryFrom,
args: [-1, {
limit: N,
reverse: false,
hash: consensus.ZERO_HASH
}]
}, {
method: wallet.listUnconfirmed,
args: [-1, {
limit: N,
reverse: false
}]
}, {
method: wallet.listUnconfirmedByTime,
args: [-1, {
limit: N,
reverse: false,
time: GENESIS_TIME
}]
}, {
method: wallet.listUnconfirmedAfter,
args: [-1, {
limit: N,
reverse: false,
hash: consensus.ZERO_HASH
}]
}, {
method: wallet.listUnconfirmedFrom,
args: [-1, {
limit: N,
reverse: false,
hash: consensus.ZERO_HASH
}]
}];
for (const {method, args} of methods) {
let err;
try {
await method.call(wallet, ...args);
} catch (e) {
err = e;
}
assert(err);
assert.strictEqual(err.message, `Limit exceeds max of ${MAX_HISTORY}.`);
}
});
it('should query empty history (unconfirmed)', async () => {
const ucTXs = await wallet.listUnconfirmed(-1, {
limit: MAX_HISTORY,
reverse: false
});
assert.strictEqual(ucTXs.length, 0);
});
it('should query empty history (confirmed)', async () => {
const cTXs = await wallet.listHistory(-1, {
limit: MAX_HISTORY,
reverse: false
});
assert.strictEqual(cTXs.length, 0);
});
it('should query empty history by time (unconfirmed)', async () => {
const ucTXs = await wallet.listUnconfirmedByTime(-1, {
limit: MAX_HISTORY,
reverse: false,
time: GENESIS_TIME
});
assert.strictEqual(ucTXs.length, 0);
});
it('should query empty history by time (confirmed)', async () => {
const cTXs = await wallet.listHistoryByTime(-1, {
limit: MAX_HISTORY,
reverse: false,
time: GENESIS_TIME
});
assert.strictEqual(cTXs.length, 0);
});
it('should fail to query after (unconfirmed)', async () => {
let err;
try {
await wallet.listUnconfirmedAfter(-1, {
hash: consensus.ZERO_HASH,
limit: MAX_HISTORY,
reverse: false
});
} catch (e) {
err = e;
}
assert(err);
assert.strictEqual(err.message, 'Transaction not found.');
});
it('should fail to query after (unconfirmed) when it is confirmed', async () => {
// create confirmed entry.
const mtx = await dummyTX(wallet);
const entry = nextEntry(wdb);
await wdb.addBlock(entry, [mtx.toTX()]);
let err;
try {
await wallet.listUnconfirmedAfter(-1, {
hash: mtx.hash(),
limit: MAX_HISTORY,
reverse: false
});
} catch (e) {
err = e;
}
assert(err);
assert.strictEqual(err.message, 'Transaction is confirmed.');
});
it('should fail to query after (confirmed)', async () => {
let err;
try {
await wallet.listHistoryAfter(-1, {
hash: consensus.ZERO_HASH,
limit: MAX_HISTORY,
reverse: false
});
} catch (e) {
err = e;
}
assert(err);
assert.strictEqual(err.message, 'Transaction not found.');
});
it('should reindex newly unconfirmed txs after disconnect', async () => {
const N = 2;
const toConfirm = [];
for (let i = 0; i < N; i++) {
const address = await wdb.primary.receiveAddress();
const mtx = new MTX();
mtx.addInput(dummyInput());
mtx.addOutput(address, 1);
const tx = mtx.toTX();
toConfirm.push(tx);
await wdb.addTX(tx);
}
{
const unconfirmed = await wdb.primary.listUnconfirmed(0, {
limit: MAX_HISTORY,
reverse: false
});
assert.strictEqual(unconfirmed.length, N);
}
const entry = nextEntry(wdb);
await wdb.addBlock(entry, toConfirm);
{
const unconfirmed = await wdb.primary.listUnconfirmed(0, {
limit: MAX_HISTORY,
reverse: false
});
assert.strictEqual(unconfirmed.length, 0);
}
for (let i = 0; i < N; i++) {
const address = await wdb.primary.receiveAddress();
const mtx = new MTX();
mtx.addInput(dummyInput());
mtx.addOutput(address, 1);
const tx = mtx.toTX();
toConfirm.push(tx);
await wdb.addTX(tx);
}
{
const unconfirmed = await wdb.primary.listUnconfirmed(0, {
limit: MAX_HISTORY,
reverse: false
});
const all = await wdb.primary.listHistory(0, {
limit: MAX_HISTORY,
reverse: false
});
assert.strictEqual(unconfirmed.length, N);
assert.strictEqual(all.length, N * 2);
}
await wdb.removeBlock(entry);
{
const unconfirmed = await wdb.primary.listUnconfirmed(0, {
limit: MAX_HISTORY,
reverse: false
});
const all = await wdb.primary.listHistory(0, {
limit: MAX_HISTORY,
reverse: false
});
assert.strictEqual(unconfirmed.length, N * 2);
assert.strictEqual(all.length, N * 2);
}
});
it('should query confirmed by time when unconfirmed txs are present', async () => {
// calculate median time for the block
const wdbLike = {
state: {
height: 0
}
};
const entries = [null];
for (let i = 0; i < 3; i++) {
entries.push(nextEntry(wdbLike));
wdbLike.state.height++;
}
const mtp = entries[entries.length >>> 1].time;
wallet.txdb.nowFn = () => mtp;
const tx = await dummyTX(wallet);
await wdb.addTX(tx.toTX());
wallet.txdb.nowFn = util.now;
for (let i = 0; i < entries.length - 2; i++) {
await wdb.addBlock(entries[1 + i], []);
}
const lastEntry = entries[entries.length - 1];
const confTX = await dummyTX(wallet);
await wdb.addTX(confTX.toTX());
await wdb.addBlock(lastEntry, [confTX.toTX()]);
// check mtp
const wdbMTP = await wdb.getMedianTime(lastEntry.height);
assert.strictEqual(wdbMTP, mtp);
// Only return unconfirmed time
{
const txByTime = await wallet.listUnconfirmedByTime(-1, {
limit: 1,
time: mtp,
reverse: false
});
const txByTimeRev = await wallet.listUnconfirmedByTime(-1, {
limit: 1,
time: mtp,
reverse: true
});
assert.bufferEqual(txByTime[0].hash, tx.hash());
assert.bufferEqual(txByTimeRev[0].hash, tx.hash());
}
// History should return confirmed tx by time.
{
const txByTime = await wallet.listHistoryByTime(-1, {
limit: 1,
time: mtp,
reverse: false
});
const txByTimeRev = await wallet.listHistoryByTime(-1, {
limit: 1,
time: mtp,
reverse: true
});
assert.bufferEqual(txByTime[0].hash, confTX.hash());
assert.bufferEqual(txByTimeRev[0].hash, confTX.hash());
}
});
});
});
/**
* @param {Wallet} wallet
* @param {(String|Number)} [account]
* @param {Number} [value=10000]
* @returns {Promise<MTX>}
*/
async function dummyTX(wallet, account = 'default', value = 10000) {
const addr = await wallet.receiveAddress(account);
const mtx = new MTX();
mtx.addInput(dummyInput());
mtx.addOutput(addr, value);
return mtx;
};
/**
* Index of using buffers.
* @param {Buffer} needle
* @param {Buffer[]} haystack
* @returns {Number}
*/
function bufIndexOf(needle, haystack) {
for (let i = 0; i < haystack.length; i++) {
if (needle.equals(haystack[i]))
return i;
}
return -1;
}