|
|
@ -463,7 +463,7 @@ WalletService.prototype.getWalletFromIdentifier = function(opts, cb) { |
|
|
|
bc.getTransaction(opts.identifier, function(err, tx) { |
|
|
|
if (err || !tx) return nextCoinNetwork(false); |
|
|
|
var outputs = _.first(self._normalizeTxHistory(tx)).outputs; |
|
|
|
var toAddresses = _.pluck(outputs, 'address'); |
|
|
|
var toAddresses = _.map(outputs, 'address'); |
|
|
|
async.detect(toAddresses, function(addressStr, nextAddress) { |
|
|
|
self.storage.fetchAddressByCoin(coinNetwork.coin, addressStr, function(err, address) { |
|
|
|
if (err || !address) return nextAddress(false); |
|
|
@ -873,7 +873,7 @@ WalletService.prototype.savePreferences = function(opts, cb) { |
|
|
|
}, |
|
|
|
}]; |
|
|
|
|
|
|
|
opts = _.pick(opts, _.pluck(preferences, 'name')); |
|
|
|
opts = _.pick(opts, _.map(preferences, 'name')); |
|
|
|
try { |
|
|
|
_.each(preferences, function(preference) { |
|
|
|
var value = opts[preference.name]; |
|
|
@ -1131,7 +1131,7 @@ WalletService.prototype._getUtxosForCurrentWallet = function(opts, cb) { |
|
|
|
}); |
|
|
|
}, |
|
|
|
function(next) { |
|
|
|
addressStrs = _.pluck(allAddresses, 'address'); |
|
|
|
addressStrs = _.map(allAddresses, 'address'); |
|
|
|
if (!opts.coin) return next(); |
|
|
|
|
|
|
|
coin = opts.coin; |
|
|
@ -1141,7 +1141,6 @@ WalletService.prototype._getUtxosForCurrentWallet = function(opts, cb) { |
|
|
|
next(); |
|
|
|
}, |
|
|
|
function(next) { |
|
|
|
|
|
|
|
self._getUtxos(coin, addressStrs, function(err, utxos) { |
|
|
|
if (err) return next(err); |
|
|
|
|
|
|
@ -1155,7 +1154,7 @@ WalletService.prototype._getUtxosForCurrentWallet = function(opts, cb) { |
|
|
|
self.getPendingTxs({}, function(err, txps) { |
|
|
|
if (err) return next(err); |
|
|
|
|
|
|
|
var lockedInputs = _.map(_.flatten(_.pluck(txps, 'inputs')), utxoKey); |
|
|
|
var lockedInputs = _.map(_.flatten(_.map(txps, 'inputs')), utxoKey); |
|
|
|
_.each(lockedInputs, function(input) { |
|
|
|
if (utxoIndex[input]) { |
|
|
|
utxoIndex[input].locked = true; |
|
|
@ -1175,7 +1174,7 @@ WalletService.prototype._getUtxosForCurrentWallet = function(opts, cb) { |
|
|
|
limit: 100 |
|
|
|
}, function(err, txs) { |
|
|
|
if (err) return next(err); |
|
|
|
var spentInputs = _.map(_.flatten(_.pluck(txs, 'inputs')), utxoKey); |
|
|
|
var spentInputs = _.map(_.flatten(_.map(txs, 'inputs')), utxoKey); |
|
|
|
_.each(spentInputs, function(input) { |
|
|
|
if (utxoIndex[input]) { |
|
|
|
utxoIndex[input].spent = true; |
|
|
@ -1242,97 +1241,109 @@ WalletService.prototype._totalizeUtxos = function(utxos) { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
WalletService.prototype._getBalanceFromAddresses = function(opts, cb) { |
|
|
|
WalletService.prototype._getBalanceFromAddresses = function(opts, cb, i) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
var opts = opts || {}; |
|
|
|
|
|
|
|
self._getUtxosForCurrentWallet({ |
|
|
|
coin: opts.coin, |
|
|
|
addresses: opts.addresses |
|
|
|
}, function(err, utxos) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
var balance = self._totalizeUtxos(utxos); |
|
|
|
// This lock is to prevent server starvation on big wallets
|
|
|
|
self._runLocked(cb, function(cb) { |
|
|
|
self._getUtxosForCurrentWallet({ |
|
|
|
coin: opts.coin, |
|
|
|
addresses: opts.addresses |
|
|
|
}, function(err, utxos) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
// Compute balance by address
|
|
|
|
var byAddress = {}; |
|
|
|
_.each(_.indexBy(_.sortBy(utxos, 'address'), 'address'), function(value, key) { |
|
|
|
byAddress[key] = { |
|
|
|
address: key, |
|
|
|
path: value.path, |
|
|
|
amount: 0, |
|
|
|
}; |
|
|
|
}); |
|
|
|
var balance = self._totalizeUtxos(utxos); |
|
|
|
|
|
|
|
_.each(utxos, function(utxo) { |
|
|
|
byAddress[utxo.address].amount += utxo.satoshis; |
|
|
|
}); |
|
|
|
// Compute balance by address
|
|
|
|
var byAddress = {}; |
|
|
|
_.each(_.indexBy(_.sortBy(utxos, 'address'), 'address'), function(value, key) { |
|
|
|
byAddress[key] = { |
|
|
|
address: key, |
|
|
|
path: value.path, |
|
|
|
amount: 0, |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
balance.byAddress = _.values(byAddress); |
|
|
|
_.each(utxos, function(utxo) { |
|
|
|
byAddress[utxo.address].amount += utxo.satoshis; |
|
|
|
}); |
|
|
|
|
|
|
|
return cb(null, balance); |
|
|
|
balance.byAddress = _.values(byAddress); |
|
|
|
return cb(null, balance); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
WalletService.prototype._getBalanceOneStep = function(opts, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
self.storage.fetchAddresses(self.walletId, function(err, addresses) { |
|
|
|
if (err) return cb(err); |
|
|
|
self._getBalanceFromAddresses({ |
|
|
|
coin: opts.coin, |
|
|
|
addresses: addresses |
|
|
|
}, function(err, balance) { |
|
|
|
self.storage.fetchAddresses(self.walletId, function(err, addresses) { |
|
|
|
if (err) return cb(err); |
|
|
|
self._getBalanceFromAddresses({ |
|
|
|
coin: opts.coin, |
|
|
|
addresses: addresses |
|
|
|
}, function(err, balance) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
// Update cache
|
|
|
|
async.series([ |
|
|
|
|
|
|
|
function(next) { |
|
|
|
self.storage.cleanActiveAddresses(self.walletId, next); |
|
|
|
}, |
|
|
|
function(next) { |
|
|
|
var active = _.pluck(balance.byAddress, 'address') |
|
|
|
self.storage.storeActiveAddresses(self.walletId, active, next); |
|
|
|
}, |
|
|
|
], function(err) { |
|
|
|
if (err) { |
|
|
|
log.warn('Could not update wallet cache', err); |
|
|
|
} |
|
|
|
return cb(null, balance); |
|
|
|
// Update cache
|
|
|
|
var withBalance = _.map(balance.byAddress, 'address') |
|
|
|
self.storage.storeAddressesWithBalance(self.walletId, withBalance, function(err) { |
|
|
|
if (err) { |
|
|
|
log.warn('Could not update wallet cache', err); |
|
|
|
} |
|
|
|
return cb(null, balance); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
WalletService.prototype._getActiveAddresses = function(cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
self.storage.fetchActiveAddresses(self.walletId, function(err, active) { |
|
|
|
self.storage.fetchAddressesWithBalance(self.walletId, function(err, addressesWB) { |
|
|
|
if (err) { |
|
|
|
log.warn('Could not fetch active addresses from cache', err); |
|
|
|
return cb(); |
|
|
|
} |
|
|
|
if (!_.isArray(addressesWB)) |
|
|
|
addressesWB = []; |
|
|
|
|
|
|
|
if (!_.isArray(active)) return cb(); |
|
|
|
var now = Math.floor(Date.now() / 1000); |
|
|
|
var fromTs = now - Defaults.TWO_STEP_CREATION_HOURS * 3600; |
|
|
|
|
|
|
|
self.storage.fetchAddresses(self.walletId, function(err, allAddresses) { |
|
|
|
self.storage.fetchNewAddresses(self.walletId, fromTs, function(err, recent) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
var now = Math.floor(Date.now() / 1000); |
|
|
|
var recent = _.pluck(_.filter(allAddresses, function(address) { |
|
|
|
return address.createdOn > (now - 24 * 3600); |
|
|
|
}), 'address'); |
|
|
|
var result = _.uniq(_.union(addressesWB, recent), 'address'); |
|
|
|
return cb(null, result); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
var result = _.union(active, recent); |
|
|
|
WalletService.prototype._checkAndUpdateAddressCount = function(twoStepCache, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
var index = _.indexBy(allAddresses, 'address'); |
|
|
|
result = _.compact(_.map(result, function(r) { |
|
|
|
return index[r]; |
|
|
|
})); |
|
|
|
return cb(null, result); |
|
|
|
if (twoStepCache.addressCount > Defaults.TWO_STEP_BALANCE_THRESHOLD) { |
|
|
|
log.info('Not counting addresses for '+ self.walletId); |
|
|
|
return cb(null, true); |
|
|
|
} |
|
|
|
|
|
|
|
self.storage.countAddresses(self.walletId, function(err, addressCount) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
if (addressCount < Defaults.TWO_STEP_BALANCE_THRESHOLD) |
|
|
|
return cb(null, false); |
|
|
|
|
|
|
|
twoStepCache.addressCount = addressCount; |
|
|
|
|
|
|
|
// updates cache
|
|
|
|
self.storage.storeTwoStepCache(self.walletId, twoStepCache, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
return cb(null, true); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
@ -1344,7 +1355,8 @@ WalletService.prototype._getActiveAddresses = function(cb) { |
|
|
|
* @param {Boolean} opts.twoStep[=false] - Optional - Use 2 step balance computation for improved performance |
|
|
|
* @returns {Object} balance - Total amount & locked amount. |
|
|
|
*/ |
|
|
|
WalletService.prototype.getBalance = function(opts, cb) { |
|
|
|
|
|
|
|
WalletService.prototype.getBalance = function(opts, cb, i) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
opts = opts || {}; |
|
|
@ -1354,40 +1366,69 @@ WalletService.prototype.getBalance = function(opts, cb) { |
|
|
|
return cb(new ClientError('Invalid coin')); |
|
|
|
} |
|
|
|
|
|
|
|
if (!opts.twoStep) |
|
|
|
if (!opts.twoStep) { |
|
|
|
return self._getBalanceOneStep(opts, cb); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
self.storage.countAddresses(self.walletId, function(err, nbAddresses) { |
|
|
|
self.storage.getTwoStepCache(self.walletId, function(err, twoStepCache) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (nbAddresses < Defaults.TWO_STEP_BALANCE_THRESHOLD) { |
|
|
|
return self._getBalanceOneStep(opts, cb); |
|
|
|
} |
|
|
|
self._getActiveAddresses(function(err, activeAddresses) { |
|
|
|
twoStepCache = twoStepCache || {}; |
|
|
|
|
|
|
|
self._checkAndUpdateAddressCount(twoStepCache, function(err, needsTwoStep ) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!_.isArray(activeAddresses)) { |
|
|
|
|
|
|
|
if (!needsTwoStep) { |
|
|
|
return self._getBalanceOneStep(opts, cb); |
|
|
|
} else { |
|
|
|
log.debug('Requesting partial balance for ' + activeAddresses.length + ' out of ' + nbAddresses + ' addresses'); |
|
|
|
self._getBalanceFromAddresses({ |
|
|
|
coin: opts.coin, |
|
|
|
addresses: activeAddresses |
|
|
|
}, function(err, partialBalance) { |
|
|
|
if (err) return cb(err); |
|
|
|
cb(null, partialBalance); |
|
|
|
setTimeout(function() { |
|
|
|
self._getBalanceOneStep(opts, function(err, fullBalance) { |
|
|
|
if (err) return; |
|
|
|
if (!_.isEqual(partialBalance, fullBalance)) { |
|
|
|
log.info('Balance in active addresses differs from final balance'); |
|
|
|
self._notify('BalanceUpdated', fullBalance, { |
|
|
|
isGlobal: true |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
}, 1); |
|
|
|
return; |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
self._getActiveAddresses(function(err, activeAddresses) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (!_.isArray(activeAddresses)) { |
|
|
|
return self._getBalanceOneStep(opts, cb); |
|
|
|
} else { |
|
|
|
log.debug('Requesting partial balance for ' + activeAddresses.length + ' addresses'); |
|
|
|
|
|
|
|
self._getBalanceFromAddresses({ |
|
|
|
coin: opts.coin, |
|
|
|
addresses: activeAddresses |
|
|
|
}, function(err, partialBalance) { |
|
|
|
if (err) return cb(err); |
|
|
|
cb(null, partialBalance); |
|
|
|
|
|
|
|
var now = Math.floor(Date.now() / 1000); |
|
|
|
|
|
|
|
if (twoStepCache.lastEmpty > now - Defaults.TWO_STEP_INACTIVE_CLEAN_DURATION_MIN * 60 ) { |
|
|
|
log.debug('Not running the FULL balance query due to TWO_STEP_INACTIVE_CLEAN_DURATION_MIN '); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
setTimeout(function() { |
|
|
|
log.debug('Running full balance query'); |
|
|
|
self._getBalanceOneStep(opts, function(err, fullBalance) { |
|
|
|
if (err) return; |
|
|
|
if (!_.isEqual(partialBalance, fullBalance)) { |
|
|
|
log.info('Balance in active addresses differs from final balance'); |
|
|
|
self._notify('BalanceUpdated', fullBalance, { |
|
|
|
isGlobal: true |
|
|
|
}); |
|
|
|
} else { |
|
|
|
// updates cache
|
|
|
|
twoStepCache.lastEmpty = now; |
|
|
|
|
|
|
|
// updates cache
|
|
|
|
return self.storage.storeTwoStepCache(self.walletId, twoStepCache, function(err) { |
|
|
|
return; |
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
}); |
|
|
|
}, 1); |
|
|
|
return; |
|
|
|
}, i); |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
@ -1424,7 +1465,7 @@ WalletService.prototype.getSendMaxInfo = function(opts, cb) { |
|
|
|
if (!_.any(feeLevels, { |
|
|
|
name: opts.feeLevel |
|
|
|
})) |
|
|
|
return cb(new ClientError('Invalid fee level. Valid values are ' + _.pluck(feeLevels, 'name').join(', '))); |
|
|
|
return cb(new ClientError('Invalid fee level. Valid values are ' + _.map(feeLevels, 'name').join(', '))); |
|
|
|
} |
|
|
|
|
|
|
|
if (_.isNumber(opts.feePerKb)) { |
|
|
@ -1567,7 +1608,7 @@ WalletService.prototype.getFeeLevels = function(opts, cb) { |
|
|
|
var feeLevels = Defaults.FEE_LEVELS[opts.coin]; |
|
|
|
|
|
|
|
function samplePoints() { |
|
|
|
var definedPoints = _.uniq(_.pluck(feeLevels, 'nbBlocks')); |
|
|
|
var definedPoints = _.uniq(_.map(feeLevels, 'nbBlocks')); |
|
|
|
return _.uniq(_.flatten(_.map(definedPoints, function(p) { |
|
|
|
return _.range(p, p + Defaults.FEE_LEVELS_FALLBACK + 1); |
|
|
|
}))); |
|
|
@ -1986,7 +2027,7 @@ WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb) |
|
|
|
if (!_.any(feeLevels, { |
|
|
|
name: opts.feeLevel |
|
|
|
})) |
|
|
|
return next(new ClientError('Invalid fee level. Valid values are ' + _.pluck(feeLevels, 'name').join(', '))); |
|
|
|
return next(new ClientError('Invalid fee level. Valid values are ' + _.map(feeLevels, 'name').join(', '))); |
|
|
|
} |
|
|
|
|
|
|
|
if (_.isNumber(opts.feePerKb)) { |
|
|
@ -2642,7 +2683,7 @@ WalletService.prototype.rejectTx = function(opts, cb) { |
|
|
|
}, |
|
|
|
function(next) { |
|
|
|
if (txp.status == 'rejected') { |
|
|
|
var rejectedBy = _.pluck(_.filter(txp.actions, { |
|
|
|
var rejectedBy = _.map(_.filter(txp.actions, { |
|
|
|
type: 'reject' |
|
|
|
}), 'copayerId'); |
|
|
|
|
|
|
@ -3005,7 +3046,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) { |
|
|
|
function(next) { |
|
|
|
if (txs) return next(); |
|
|
|
|
|
|
|
var addressStrs = _.pluck(addresses, 'address'); |
|
|
|
var addressStrs = _.map(addresses, 'address'); |
|
|
|
var bc = self._getBlockchainExplorer(wallet.coin, wallet.network); |
|
|
|
if (!bc) return next(new Error('Could not get blockchain explorer instance')); |
|
|
|
bc.getTransactions(addressStrs, from, to, function(err, rawTxs, total) { |
|
|
@ -3169,6 +3210,7 @@ WalletService.prototype.scan = function(opts, cb) { |
|
|
|
var gap = Defaults.SCAN_ADDRESS_GAP; |
|
|
|
|
|
|
|
async.whilst(function() { |
|
|
|
log.debug('Scanning addr gap:'+ inactiveCounter); |
|
|
|
return inactiveCounter < gap; |
|
|
|
}, function(next) { |
|
|
|
var address = derivator.derive(); |
|
|
@ -3225,7 +3267,9 @@ WalletService.prototype.scan = function(opts, cb) { |
|
|
|
if (err) return cb(err); |
|
|
|
wallet.scanStatus = error ? 'error' : 'success'; |
|
|
|
self.storage.storeWallet(wallet, function() { |
|
|
|
return cb(error); |
|
|
|
self.storage.storeTwoStepCache(self.walletId, {}, function(err) { |
|
|
|
return cb(error); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}) |
|
|
|
}); |
|
|
|