2018-08-20 17:21:30 +12:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
(function(){
|
|
|
|
|
|
|
|
|
|
angular
|
|
|
|
|
.module('bitcoincom.services')
|
|
|
|
|
.factory('walletHistoryService', walletHistoryService);
|
|
|
|
|
|
|
|
|
|
function walletHistoryService(configService, storageService, lodash, $log, txFormatService) {
|
2018-08-21 12:43:03 +12:00
|
|
|
//var PAGE_SIZE = 50;
|
|
|
|
|
var PAGE_SIZE = 20; // For dev only
|
2018-08-20 17:21:30 +12:00
|
|
|
// How much to overlap on each end of the page, for mitigating inconsistent sort order.
|
2018-08-21 12:43:03 +12:00
|
|
|
var PAGE_OVERLAP_FRACTION = 0.2;
|
|
|
|
|
var PAGE_OVERLAP = Math.floor(PAGE_SIZE * PAGE_OVERLAP_FRACTION);
|
|
|
|
|
// The amount of transactions in the new overlapping resultset that we already know about.
|
|
|
|
|
// If we know about at least this many, then there are probably no gaps.
|
|
|
|
|
var MIN_KNOWN_TX_OVERLAP = Math.floor(PAGE_OVERLAP * 0.5);
|
2018-08-20 17:21:30 +12:00
|
|
|
|
|
|
|
|
var SAFE_CONFIRMATIONS = 6;
|
|
|
|
|
|
2018-08-21 21:36:55 +02:00
|
|
|
var allTransactionsFetched = false;
|
2018-08-20 17:21:30 +12:00
|
|
|
var service = {
|
|
|
|
|
getCachedTxHistory: getCachedTxHistory,
|
2018-08-21 12:43:03 +12:00
|
|
|
updateLocalTxHistoryByPage: updateLocalTxHistoryByPage,
|
2018-08-20 17:21:30 +12:00
|
|
|
};
|
|
|
|
|
return service;
|
|
|
|
|
|
2018-08-21 21:36:55 +02:00
|
|
|
/*
|
|
|
|
|
function hasAllTransactionsFetched(walletId, cachedTxs, newTxs) {
|
|
|
|
|
var cachedTxIds = {};
|
|
|
|
|
cachedTxs.forEach(function forCachedTx(tx){
|
|
|
|
|
cachedTxIds[tx.txid] = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var someTransactionWereNew = false;
|
|
|
|
|
var overlappingTxsCount = 0;
|
|
|
|
|
|
|
|
|
|
newTxs.forEach(function forNewTx(tx){
|
|
|
|
|
if (cachedTxIds[tx.txid]) {
|
|
|
|
|
overlappingTxsCount++;
|
|
|
|
|
} else {
|
|
|
|
|
someTransactionWereNew = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log('pagination Overlapping transactions:', overlappingTxsCount);
|
|
|
|
|
if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good
|
|
|
|
|
if (!someTransactionWereNew && overlappingTxsCount === newTxs.length) {
|
|
|
|
|
console.log("We probably have all of the transactions fetched!!")
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.log("Something went wrong")
|
|
|
|
|
return true; // Something went wrong, so stop fetching..
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
2018-08-21 12:43:03 +12:00
|
|
|
function addEarlyTransactions(walletId, cachedTxs, newTxs) {
|
|
|
|
|
|
|
|
|
|
var cachedTxIds = {};
|
|
|
|
|
cachedTxs.forEach(function forCachedTx(tx){
|
|
|
|
|
cachedTxIds[tx.txid] = true;
|
|
|
|
|
});
|
|
|
|
|
|
2018-08-22 19:28:24 +12:00
|
|
|
var someTransactionsWereNew = false;
|
2018-08-21 12:43:03 +12:00
|
|
|
var overlappingTxsCount = 0;
|
|
|
|
|
|
|
|
|
|
newTxs.forEach(function forNewTx(tx){
|
|
|
|
|
if (cachedTxIds[tx.txid]) {
|
|
|
|
|
overlappingTxsCount++;
|
|
|
|
|
} else {
|
2018-08-22 19:28:24 +12:00
|
|
|
someTransactionsWereNew = true;
|
2018-08-21 12:43:03 +12:00
|
|
|
cachedTxs.push(tx);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2018-08-22 19:28:24 +12:00
|
|
|
console.log('pagination Early transactions overlapping:', overlappingTxsCount);
|
2018-08-21 12:43:03 +12:00
|
|
|
if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good
|
2018-08-22 19:28:24 +12:00
|
|
|
if (someTransactionsWereNew) {
|
2018-08-21 12:43:03 +12:00
|
|
|
console.log('pagination someTransactionsWereNew');
|
|
|
|
|
saveTxHistory(walletId, cachedTxs);
|
2018-08-21 21:36:55 +02:00
|
|
|
} else if (overlappingTxsCount === newTxs.length) {
|
|
|
|
|
console.log('We probably have all transactions now');
|
|
|
|
|
allTransactionsFetched = true;
|
2018-08-21 12:43:03 +12:00
|
|
|
}
|
|
|
|
|
return cachedTxs;
|
|
|
|
|
} else {
|
|
|
|
|
// We might be missing some txs.
|
|
|
|
|
console.error('We might be missing some txs in the history.');
|
|
|
|
|
// Our history is wrong, so remove it - we could instead, try to fetch data that was not so early.
|
|
|
|
|
storageService.removeTxHistory(walletId, function onRemoveTxHistory(){});
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-22 19:28:24 +12:00
|
|
|
function addLatestTransactions(walletId, cachedTxs, newTxs) {
|
|
|
|
|
var cachedTxIds = {};
|
|
|
|
|
cachedTxs.forEach(function forCachedTx(tx){
|
|
|
|
|
cachedTxIds[tx.txid] = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var someTransactionsWereNew = false;
|
|
|
|
|
var overlappingTxsCount = 0;
|
|
|
|
|
var uniqueNewTxs = [];
|
|
|
|
|
|
|
|
|
|
newTxs.forEach(function forNewTx(tx){
|
|
|
|
|
if (cachedTxIds[tx.txid]) {
|
|
|
|
|
overlappingTxsCount++;
|
|
|
|
|
} else {
|
|
|
|
|
someTransactionWereNew = true;
|
|
|
|
|
uniqueNewTxs.push(tx);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log('pagination Latest transactions overlapping:', overlappingTxsCount);
|
|
|
|
|
if (overlappingTxsCount >= MIN_KNOWN_TX_OVERLAP) { // We are good
|
|
|
|
|
if (someTransactionsWereNew) {
|
|
|
|
|
console.log('pagination someTransactionsWereNew');
|
|
|
|
|
var allTxs = uniqueNewTxs.concat(cachedTxs);
|
|
|
|
|
saveTxHistory(walletId, allTxs);
|
|
|
|
|
return allTxs;
|
|
|
|
|
} else {
|
|
|
|
|
return cachedTxs;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// We might be missing some txs.
|
|
|
|
|
// Our history is wrong, so just include the latest ones
|
|
|
|
|
saveTxHistory(walletId, newTxs);
|
|
|
|
|
return newTxs;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-21 12:43:03 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only clear the cache once we have received new transactions from the server.
|
|
|
|
|
/**
|
|
|
|
|
* @param {function(err, txs)} cb - transactions is always an array, may be empty
|
|
|
|
|
*/
|
|
|
|
|
function fetchTxHistoryByPage(wallet, start, cb) {
|
|
|
|
|
var skip = Math.max(0, start - PAGE_OVERLAP);
|
|
|
|
|
var limit = PAGE_SIZE;
|
|
|
|
|
|
|
|
|
|
var opts = {
|
|
|
|
|
skip: skip,
|
|
|
|
|
limit: limit
|
|
|
|
|
};
|
|
|
|
|
wallet.getTxHistory(opts, function onTxHistory(err, txsFromServer) {
|
|
|
|
|
if (err) {
|
|
|
|
|
return cb(err, []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (txsFromServer.length === 0) {
|
|
|
|
|
return cb(null, []);
|
|
|
|
|
}
|
|
|
|
|
console.log('pagination Transactions fetched:', txsFromServer.length);
|
|
|
|
|
|
|
|
|
|
var processedTxs = processNewTxs(wallet, txsFromServer);
|
|
|
|
|
|
|
|
|
|
return cb(null, processedTxs);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-20 17:21:30 +12:00
|
|
|
/**
|
|
|
|
|
* @param {string} walletId
|
2018-08-21 12:43:03 +12:00
|
|
|
* @param {function(error, txs)} cb - txs is always an array, may be empty
|
2018-08-20 17:21:30 +12:00
|
|
|
*/
|
|
|
|
|
function getCachedTxHistory(walletId, cb) {
|
|
|
|
|
storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){
|
|
|
|
|
if (err) {
|
2018-08-21 12:43:03 +12:00
|
|
|
return cb(err, []);
|
2018-08-20 17:21:30 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!txHistoryString) {
|
2018-08-21 12:43:03 +12:00
|
|
|
return cb(null, []);
|
2018-08-20 17:21:30 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
var txHistory = JSON.parse(txHistoryString);
|
|
|
|
|
return cb(null, txHistory);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
$log.error('Failed to parse tx history.', e);
|
2018-08-21 12:43:03 +12:00
|
|
|
return cb(e, []);
|
2018-08-20 17:21:30 +12:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function processNewTxs(wallet, txs) {
|
|
|
|
|
var now = Math.floor(Date.now() / 1000);
|
|
|
|
|
var txHistoryUnique = {};
|
|
|
|
|
var processedTxs = [];
|
|
|
|
|
wallet.hasUnsafeConfirmed = false;
|
|
|
|
|
|
|
|
|
|
lodash.each(txs, function(tx) {
|
|
|
|
|
tx = txFormatService.processTx(wallet.coin, tx);
|
|
|
|
|
|
|
|
|
|
// no future transactions...
|
|
|
|
|
if (tx.time > now)
|
|
|
|
|
tx.time = now;
|
|
|
|
|
|
|
|
|
|
if (tx.confirmations >= SAFE_CONFIRMATIONS) {
|
|
|
|
|
tx.safeConfirmed = SAFE_CONFIRMATIONS + '+';
|
|
|
|
|
} else {
|
|
|
|
|
tx.safeConfirmed = false;
|
|
|
|
|
wallet.hasUnsafeConfirmed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tx.note) {
|
|
|
|
|
delete tx.note.encryptedEditedByName;
|
|
|
|
|
delete tx.note.encryptedBody;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!txHistoryUnique[tx.txid]) {
|
|
|
|
|
processedTxs.push(tx);
|
|
|
|
|
txHistoryUnique[tx.txid] = true;
|
|
|
|
|
} else {
|
|
|
|
|
$log.debug('Ignoring duplicate TX in history: ' + tx.txid)
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return processedTxs;
|
|
|
|
|
};
|
|
|
|
|
|
2018-08-21 12:43:03 +12:00
|
|
|
function saveTxHistory(walletId, processedTxs) {
|
|
|
|
|
console.log('pagination Saving transactions:', processedTxs.length);
|
2018-08-20 17:21:30 +12:00
|
|
|
storageService.setTxHistory(processedTxs, walletId, function onSetTxHistory(error){
|
2018-08-21 12:43:03 +12:00
|
|
|
if (error) {
|
|
|
|
|
$log.error('pagination Failed to save tx history.', error);
|
|
|
|
|
} else {
|
|
|
|
|
console.log('pagination Save successful.');
|
|
|
|
|
}
|
2018-08-20 17:21:30 +12:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-08-21 12:43:03 +12:00
|
|
|
function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) {
|
|
|
|
|
|
|
|
|
|
if (flushCacheOnNew) {
|
2018-08-22 19:28:24 +12:00
|
|
|
console.log('pagination Getting latest txs, will then flush cache.');
|
2018-08-21 12:43:03 +12:00
|
|
|
fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){
|
|
|
|
|
if (err) {
|
|
|
|
|
return cb(err, txs);
|
|
|
|
|
}
|
|
|
|
|
saveTxHistory(wallet.id, txs);
|
|
|
|
|
return cb(null, txs);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2018-08-22 19:28:24 +12:00
|
|
|
console.log('pagination Getting txs to add to cache.');
|
2018-08-21 12:43:03 +12:00
|
|
|
getCachedTxHistory(wallet.id, function onCachedHistory(err, cachedTxs){
|
|
|
|
|
if (err) {
|
|
|
|
|
$log.error('Failed to get cached tx history.', err);
|
|
|
|
|
return cb(err, []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var start = getLatest ? 0 : cachedTxs.length;
|
|
|
|
|
console.log('pagination Transaction fetch start:', start);
|
|
|
|
|
fetchTxHistoryByPage(wallet, start, function onFetchHistory(err, fetchedTxs){
|
|
|
|
|
if (err) {
|
|
|
|
|
return cb(err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fetchedTxs.length === 0) {
|
2018-08-21 21:36:55 +02:00
|
|
|
return cb(null, cachedTxs, true /*fetchedAllTransactions*/);
|
2018-08-21 12:43:03 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var txs = [];
|
|
|
|
|
if (getLatest) {
|
|
|
|
|
txs = addLatestTransactions(wallet.id, cachedTxs, fetchedTxs);
|
|
|
|
|
} else {
|
|
|
|
|
txs = addEarlyTransactions(wallet.id, cachedTxs, fetchedTxs);
|
2018-08-21 21:36:55 +02:00
|
|
|
return cb(null, txs, allTransactionsFetched/*, hasAllTransactionsFetched(wallet.id, cachedTxs, fetchedTxs)*/);
|
2018-08-21 12:43:03 +12:00
|
|
|
}
|
|
|
|
|
return cb(null, txs);
|
|
|
|
|
});
|
|
|
|
|
|
2018-08-21 21:36:55 +02:00
|
|
|
|
2018-08-21 12:43:03 +12:00
|
|
|
});
|
|
|
|
|
}
|
2018-08-20 17:21:30 +12:00
|
|
|
}
|
|
|
|
|
|
2018-08-21 12:43:03 +12:00
|
|
|
|
|
|
|
|
|
2018-08-20 17:21:30 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
})();
|