Skip to content

Commit 7491e6f

Browse files
perf(wallet api): improve wallet transaction list performance
BREAKING CHANGE: no longer page based on _id
1 parent b2917e6 commit 7491e6f

File tree

5 files changed

+56
-92
lines changed

5 files changed

+56
-92
lines changed

packages/bitcore-client/bin/wallet-transaction-list

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ const main = async () => {
1515
const { name, path, startDate, endDate } = program;
1616
try {
1717
const wallet = await Wallet.loadWallet({ name, path });
18-
const txStream = await wallet.listTransactions({ startDate, endDate });
19-
for(let tx of txStream) {
20-
console.log(tx);
21-
}
18+
wallet.listTransactions({ startDate, endDate }).pipe(process.stdout);
2219
} catch (e) {
2320
console.error(e);
2421
}

packages/bitcore-client/lib/client.js

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const request = require('request-promise-native');
2+
const requestStream = require('request');
23
const bitcoreLib = require('bitcore-lib');
34
const secp256k1 = require('secp256k1');
45
const stream = require('stream');
@@ -62,37 +63,13 @@ Client.prototype.getCoins = async function (params) {
6263
};
6364

6465
Client.prototype.listTransactions = async function(params) {
65-
const getTransactions = ({pubKey, startDate, endDate, since}) => {
66-
let url = `${this.baseUrl}/wallet/${pubKey}/transactions?startDate=${startDate}&endDate=${endDate}&paging=_id&limit=1000`;
67-
if(since) {
68-
url += `&since=${since}`;
69-
}
70-
const signature = this.sign({ method: 'GET', url});
71-
return request.get(url, {
72-
headers: { 'x-signature': signature },
73-
json: true
74-
});
75-
};
76-
77-
let totalResults = [];
78-
let since = '';
79-
let splitResults = null;
80-
do {
81-
try {
82-
let results = await getTransactions({...params, since });
83-
if(!results) {
84-
throw new Error('No more results');
85-
}
86-
splitResults = results.split('\n').filter(r => r!= '');
87-
totalResults = totalResults.concat(splitResults);
88-
const last = JSON.parse(splitResults[splitResults.length - 1]);
89-
since = last.id;
90-
} catch (e) {
91-
splitResults = null;
92-
}
93-
}
94-
while(splitResults && splitResults != []);
95-
return totalResults;
66+
const { pubKey, startDate, endDate } = params;
67+
const url = `${this.baseUrl}/wallet/${pubKey}/transactions?startDate=${startDate}&endDate=${endDate}`;
68+
const signature = this.sign({ method: 'GET', url });
69+
return requestStream.get(url, {
70+
headers: { 'x-signature': signature },
71+
json: true
72+
});
9673
};
9774

9875
Client.prototype.getFee = async function (params) {

packages/bitcore-node/src/models/transaction.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export class Transaction extends BaseModel<ITransaction> {
4242
this.collection.createIndex({ blockHeight: 1, chain: 1, network: 1 }, { background: true });
4343
this.collection.createIndex({ blockHash: 1 }, { background: true });
4444
this.collection.createIndex({ blockTimeNormalized: 1, chain: 1, network: 1 }, { background: true });
45-
this.collection.createIndex({ wallets: 1, blockTimeNormalized: 1, _id: -1 }, { background: true, sparse: true });
45+
this.collection.createIndex({ wallets: 1, blockTimeNormalized: 1 }, { background: true, sparse: true });
46+
this.collection.createIndex({ wallets: 1, blockHeight: 1 }, { background: true, sparse: true });
4647
}
4748

4849
async batchImport(params: {

packages/bitcore-node/src/providers/chain-state/internal/internal.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -282,36 +282,43 @@ export class InternalStateProvider implements CSP.IChainStateService {
282282
}
283283

284284
async streamWalletTransactions(params: CSP.StreamWalletTransactionsParams) {
285-
let { chain, network, wallet, stream, args } = params;
286-
let finalQuery: any = {
287-
chain: chain,
285+
const { chain, network, wallet, stream, args } = params;
286+
const query: any = {
287+
chain,
288288
network,
289289
wallets: wallet._id
290290
};
291+
const options: any = {};
291292
if (args) {
292-
if (args.startBlock) {
293-
finalQuery.blockHeight = { $gte: Number(args.startBlock) };
294-
}
295-
if (args.endBlock) {
296-
finalQuery.blockHeight = finalQuery.blockHeight || {};
297-
finalQuery.blockHeight.$lte = Number(args.endBlock);
298-
}
299-
if (args.startDate) {
300-
const startDate = new Date(args.startDate);
301-
if (startDate.getTime()) {
302-
finalQuery.blockTimeNormalized = { $gte: new Date(args.startDate) };
293+
if (args.startBlock || args.endBlock) {
294+
options.sort = {blockHeight: args.direction || -1};
295+
if (args.startBlock) {
296+
query.blockHeight = { $gte: Number(args.startBlock) };
303297
}
304-
}
305-
if (args.endDate) {
306-
const endDate = new Date(args.endDate);
307-
if (endDate.getTime()) {
308-
finalQuery.blockTimeNormalized = finalQuery.blockTimeNormalized || {};
309-
finalQuery.blockTimeNormalized.$lt = new Date(args.endDate);
298+
if (args.endBlock) {
299+
query.blockHeight = query.blockHeight || {};
300+
query.blockHeight.$lte = Number(args.endBlock);
301+
}
302+
} else {
303+
options.sort = { blockTimeNormalized: args.direction || -1 };
304+
if (args.startDate) {
305+
const startDate = new Date(args.startDate);
306+
if (startDate.getTime()) {
307+
query.blockTimeNormalized = { $gte: new Date(args.startDate) };
308+
}
309+
}
310+
if (args.endDate) {
311+
const endDate = new Date(args.endDate);
312+
if (endDate.getTime()) {
313+
query.blockTimeNormalized = query.blockTimeNormalized || {};
314+
query.blockTimeNormalized.$lt = new Date(args.endDate);
315+
}
310316
}
311317
}
312318
}
313-
let transactionStream = TransactionModel.getTransactions({ query: finalQuery, options: args });
314-
let listTransactionsStream = new ListTransactionsStream(wallet);
319+
320+
const transactionStream = TransactionModel.collection.find(query, options).addCursorFlag('noCursorTimeout', true);
321+
const listTransactionsStream = new ListTransactionsStream(wallet);
315322
transactionStream.pipe(listTransactionsStream).pipe(stream);
316323
}
317324

packages/bitcore-node/src/providers/chain-state/internal/transforms.ts

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export class ListTransactionsStream extends Transform {
88
}
99

1010
async _transform(transaction, _, done) {
11-
var self = this;
1211
transaction.inputs = await CoinModel.collection
1312
.find(
1413
{
@@ -32,34 +31,20 @@ export class ListTransactionsStream extends Transform {
3231
.addCursorFlag('noCursorTimeout', true)
3332
.toArray();
3433

35-
var wallet = this.wallet._id!.toString();
36-
var totalInputs = transaction.inputs.reduce((total, input) => {
37-
return total + input.value;
38-
}, 0);
39-
var totalOutputs = transaction.outputs.reduce((total, output) => {
40-
return total + output.value;
41-
}, 0);
42-
var fee = totalInputs - totalOutputs;
43-
var sending = transaction.inputs.some(function(input) {
44-
var contains = false;
45-
input.wallets.forEach(function(inputWallet) {
46-
if (inputWallet.equals(wallet)) {
47-
contains = true;
48-
}
34+
const wallet = this.wallet._id!.toString();
35+
const sending = transaction.inputs.some((input) => {
36+
return input.wallets.some((inputWallet) => {
37+
return inputWallet.equals(wallet);
4938
});
50-
return contains;
5139
});
5240

5341
if (sending) {
54-
transaction.outputs.forEach(function(output) {
55-
var sendingToOurself = false;
56-
output.wallets.forEach(function(outputWallet) {
57-
if (outputWallet.equals(wallet)) {
58-
sendingToOurself = true;
59-
}
42+
transaction.outputs.forEach((output) => {
43+
const sendingToOurself = output.wallets.some((outputWallet) => {
44+
return outputWallet.equals(wallet);
6045
});
6146
if (!sendingToOurself) {
62-
self.push(
47+
this.push(
6348
JSON.stringify({
6449
id: transaction._id,
6550
txid: transaction.txid,
@@ -74,7 +59,7 @@ export class ListTransactionsStream extends Transform {
7459
}) + '\n'
7560
);
7661
} else {
77-
self.push(
62+
this.push(
7863
JSON.stringify({
7964
id: transaction._id,
8065
txid: transaction.txid,
@@ -90,29 +75,26 @@ export class ListTransactionsStream extends Transform {
9075
);
9176
}
9277
});
93-
if (fee > 0) {
94-
self.push(
78+
if (transaction.fee > 0) {
79+
this.push(
9580
JSON.stringify({
9681
id: transaction._id,
9782
txid: transaction.txid,
9883
category: 'fee',
99-
satoshis: -fee,
84+
satoshis: -transaction.fee,
10085
height: transaction.blockHeight,
10186
blockTime: transaction.blockTimeNormalized
10287
}) + '\n'
10388
);
10489
}
10590
return done();
10691
} else {
107-
transaction.outputs.forEach(function(output) {
108-
var weReceived = false;
109-
output.wallets.forEach(function(outputWallet) {
110-
if (outputWallet.equals(wallet)) {
111-
weReceived = true;
112-
}
92+
transaction.outputs.forEach((output) => {
93+
const weReceived = output.wallets.some((outputWallet) => {
94+
return outputWallet.equals(wallet);
11395
});
11496
if (weReceived) {
115-
self.push(
97+
this.push(
11698
JSON.stringify({
11799
id: transaction._id,
118100
txid: transaction.txid,

0 commit comments

Comments
 (0)