Compare commits

...

89 Commits

Author SHA1 Message Date
sairajzero
8121a2ba96 Bug fix
- Fixed: mempool (unconfirmed tx) getting repeated in chain query when using before option
2023-04-27 04:15:43 +05:30
sairajzero
31344d770e Adding 'before' option to address APIs
- Similar to 'after' option but inverse of it. ie, if before txid is passed, then query list/values before the given txid
- 'before' and 'after' option can be use in combination or individually
- cache system for address summary api wont be used with 'after' and/or 'before' option is used
2023-04-27 03:48:09 +05:30
sairajzero
96677310c2 Bug fixes 2023-04-27 01:41:22 +05:30
sairajzero
e180672583 Adding reverse option to address history query
- Using option `reverse` as true will query the latest 1000 (max val) tx instead of the 1st 1000 tx.
2023-04-23 02:55:49 +05:30
sairajzero
158d5aefc2 Decrease MAX_TX_QUERY_LIMIT_SUMMARY to 500
- Summary cache is triggered for addresses with more than 500 tx
2023-04-22 23:55:11 +05:30
sairajzero
e7248320a6 bug fix: txid & blockhash not cached correctly 2023-04-22 22:19:23 +05:30
sairajzero
60289a644b Fix: Incorrect values in cache during reorg
- store the blockhash of lastTx in cache value
- If last cached tx-block was removed (during reorg), then delete the cache and recalculate the values.
2023-04-21 22:51:45 +05:30
sairajzero
d42e569008 update address-cache key prefix 2023-04-19 03:03:21 +05:30
sairajzero
6beb2ecd06 Adding function to delete cache for a given addr
Adding _deleteCache: deletes the cache for given address (useful for dev purposes or to recalculate caches)
2023-04-19 02:31:04 +05:30
sairajzero
4d023760ad Adding del (delete) function in db service 2023-04-19 02:28:44 +05:30
sairajzero
10d9459f26 Bug fix: unconfirmed-tx values corrupting cache 2023-04-19 01:24:37 +05:30
sairajzero
8b07a1e4a5 separate MAX_TX_QUERY_LIMIT for each query type 2023-04-19 01:03:45 +05:30
sairajzero
4afb0dfaaa Bug fix: cache summary not working properly 2023-04-19 00:45:44 +05:30
sairajzero
f8260541ef Fix: Cache value encoding
- Using BigInt (uInt64) for cache value (balance, sent, received) as they are large for uInt32
- Reflect the changes for BigInt (uInt64) in decode cache value
- Removed default values in cache encoding, as if a value is invalid then it must throw error and not write db
2023-04-19 00:42:47 +05:30
sairajzero
6b794aa9a3 Fixes: cache storing unconfirmed values
- removed unconfirmed values from cache store and get
-  do not update lastItem value for unconfirmed tx
2023-04-18 04:37:01 +05:30
sairajzero
adb81616aa Run cache summary
run cache storage for address summary in background when queried result is incomplete
2023-04-18 04:23:41 +05:30
sairajzero
3982807b32 Cache address-summary
Store cache of address summary in db when address has more than MAX_TX_QUERY_LIMIT
2023-04-17 20:13:29 +05:30
sairajzero
0a3a1b5ea6 changing typeof to equivalent lodash 2023-04-17 20:07:12 +05:30
sairajzero
df7710ded1 Adding option mempoolOnly in querying
- mempoolOnly option make the query in db from mempool only (ie, unconfirmed tx only
2023-04-13 03:52:02 +05:30
sairajzero
27f3993884 Fixed: inconsistency addrs API on multiple address
- Fixed: addrs API on multiple addresses when total tx is more than 1000 (MAX_TX_QUERY_LIMIT) giving inconsistent list of tx due to order messed up in parallel query
2023-04-13 02:26:33 +05:30
sairajzero
71bcafb243 Limited data responses identification
- address-query responses data has `incomplete` property set to `true` when addr has more than 1000 (MAX_TX_QUERY_LIMIT)
2023-04-11 04:04:19 +05:30
sairajzero
1b6352573f Limit all address query to MAX_TX_QUERY_LIMIT
- any address data API will now max out at 1000 (MAX_TX_QUERY_LIMIT)
- For addrs with more than 1000 tx, use chained API query to get the complete data (like balance, txid, etc)

- set parallel queue limit to 1: preserve consistency in API queries (and prevent incorrect data)
2023-04-11 03:49:21 +05:30
sairajzero
7f86e488e4 bug fix
Fixed bug: api on addr not giving response (timeoout) when it has only 1 tx
2023-04-06 22:25:03 +05:30
sairajzero
37e08b0801 Bug fix
- Fixed: Incorrect ordering of tx list with unconfirmed tx
2023-02-06 02:02:52 +05:30
Sai Raj
16bed1b811
Merge pull request #9 from ranchimall/api-improvements
API Improvements
2023-02-06 00:18:20 +05:30
sairajzero
bca4fe4f97 Bug fix
- Fixed: data inconsistency and continuity lost in chain querying of tx details
- Fixed: Not getting response when query has no tx. (ie, either address has no tx, or using the most recent tx as the key in `after` option)
2023-02-05 23:01:06 +05:30
sairajzero
7409dbb77d Bug fixes
- Fixed: incorrect data returned via `from` and `to` option
- Fixed: Missing data due to unordered items in getAddressHistory
- Fixed: callback invoked multiple items in _streamAddressSummary due to queue parallel limit
- Fixed: Queue drain being invoked before mempool txs are pushed into queue
2023-02-05 19:03:11 +05:30
sairajzero
774d830fff Improvements to API query options
- Stop request-stream process when stop-flag is on
- Changed: order of reading db to forward (so that continuity will be preserved with `after` option and ws api calls). And changes required for the same.
- Deprecating options (from and to) in calls that are not supported
- Added: Temporary support for from and to option in getAddressHistory
2023-02-05 03:12:04 +05:30
sairajzero
3a75002efc API Query options
- Fixed: option `start` to query from blockheight
- Added option `after`: pass this option in API to get list after the given txid
Note: If both `start` and `after` are given, then greater height will be used
Note: invalid or unconfirmed txid cannot be used in `after` option and will be ignored
2023-02-04 20:29:41 +05:30
sairajzero
d9579853ad Improve handling of duplicate tx query
- temporarily store txids to ignore duplication
- removed the queue pause() and resume() in _streamAddressSummary
2023-02-02 19:14:29 +05:30
sairajzero
3fbcbbe7bc Fixed: APIs giving incorrect data
- Fixed: addr API giving decimals in satoshi values
- Fixed: Incorrect balance, totalSent, totalReceived values returned in API calls (issue: duplication)
- Fixed: incorrect totalCount value in addr API and duplication of tx list
2023-01-28 21:55:58 +05:30
sairajzero
dbfe39991f Fixed: Address-summary request not responding 2023-01-28 02:04:40 +05:30
sairajzero
6c164993bf Update package.json 2023-01-27 22:33:30 +05:30
sairajzero
2145fdb056 pass 'express-ws' module to service setupRoutes
- flosight-api uses 'express-ws' module for ws api calls
2023-01-27 18:07:38 +05:30
sairajzero
4472ed8394 Changing fns to use _streamAddressSummary
Functions updated:
- getAddressHistory
- getAddressSummary

(old fns are kept as it is and renamed to __getAddressHistory and __getAddressSummary respectively)
2023-01-27 17:38:37 +05:30
sairajzero
e13bd5e3e6 Adding _streamAddressSummary
- Fn uses streamer to process data. Thus doesnt store the entire list of txid or tx details
- streamer fn can process the tx data as required
2023-01-27 17:36:39 +05:30
sairajzero
0283be05db Limit max response size 2023-01-27 17:31:02 +05:30
Sai Raj
25eb992cf1
Merge pull request #8 from ranchimall/block-subscribe-stuck-fix
Fix: Block subscribe getting stuck
2023-01-18 00:24:09 +05:30
sairajzero
69e9465b93 block service: set MAX_IGNORED_BLOCK to 16 2023-01-18 00:19:20 +05:30
sairajzero
700abe0500 hotfix: block subscription getting stuck
- Issue: Block subscription getting stuck when a (missing) block wasn't received by block service.
- Solution: Re-trigger the sync process when too many blocks ignored

- Others: fixed a typo in _reportInterval property
2023-01-17 21:54:48 +05:30
sairajzero
0572ee6b35 header: clear _syncCheckInterval upon synced
Header service: clear _syncCheckInterval to null when clearing interval after sync completed
2023-01-17 16:44:42 +05:30
Sai Raj
e6826c7dfc
Merge pull request #6 from ranchimall/startup-sync-fix
Startup sync fix
2023-01-15 12:33:50 +05:30
sairajzero
ece347c825 hotfix: best header not updating for prev fix
- updated header service best header from fcoin node directly.
- set interval check only if fcoin is started by flocore (else header best height ll not get updated and sync complete incorrectly)
2023-01-15 00:02:25 +05:30
sairajzero
b831cbce7e Update .gitignore 2023-01-14 23:56:18 +05:30
sairajzero
f9e2ed304b hotfix for unresponsive header sync 2023-01-11 03:38:38 +05:30
00515c5378
Update index.js
Added a missing return HeaderService.prototype._handleError
2023-01-09 05:12:39 +05:30
Sai Raj
e3f5de4df5
Merge pull request #3 from ranchimall/uncaught-no-shut
hotfix: Do not shutdown on unhandled exceptions
2023-01-08 17:36:33 +05:30
sairajzero
26b65d63a8 hotfix: Do not shutdown on unhandled exceptions 2023-01-08 17:13:30 +05:30
4113d9cfd0
Update index.js 2022-01-25 11:23:06 +05:30
7fa2f096df
Update reorg.js 2022-01-25 11:18:46 +05:30
fcecf08ac0
Update reorg.js 2022-01-25 11:18:13 +05:30
af6048de93
Update index.js 2022-01-25 11:13:27 +05:30
d47b6047e5
Update index.js 2022-01-25 11:00:27 +05:30
c525516a95
Update reorg.js 2022-01-25 10:57:40 +05:30
0eaa4b6fd2
Update index.js 2022-01-25 10:54:54 +05:30
2921e389df
Update block_handler.js 2022-01-25 00:31:31 +05:30
522d00bd52
Update index.js 2022-01-25 00:25:25 +05:30
821aae706d
Update index.js 2022-01-25 00:22:55 +05:30
18d1a16b0c
Update reorg.js 2022-01-25 00:07:57 +05:30
9c0ec67ccf
Update index.js 2022-01-25 00:03:52 +05:30
f0768027c9
Update index.js 2022-01-24 18:06:15 +05:30
80f22f731f
Update index.js 2022-01-24 17:57:48 +05:30
81a3d5f8ff
Update reorg.js 2022-01-24 17:56:37 +05:30
69fc6790ae
Update reorg.js 2022-01-24 17:55:39 +05:30
5db68b6bb7
Update reorg.js 2022-01-24 17:53:30 +05:30
091d7aa863
Update reorg.js 2022-01-24 17:52:14 +05:30
582bdd698a
Update reorg.js 2022-01-24 17:50:10 +05:30
726156843e
Update index.js 2022-01-24 17:45:10 +05:30
846f85e2f8
Update block_handler.js 2022-01-24 17:40:20 +05:30
ca47013c5a
Update block_handler.js 2022-01-24 15:17:50 +05:30
82357f2ecc
Update reorg.js 2022-01-24 15:17:02 +05:30
c3cc5f7465
Update index.js 2022-01-24 15:16:15 +05:30
1888b4a4ae
Throwing error removed
new Error was stopping the running instance of flosight. Substituted by log.info
2022-01-24 15:15:11 +05:30
1edc88f14b
Update node.js 2021-05-26 19:54:53 +05:30
0966ec124b
Stop the stoppage of service 2021-05-25 14:58:20 +05:30
05c6cd7739
Fixed header existence conditions 2021-05-25 11:07:56 +05:30
2c37e05ff7
Added test to check header.height 2021-05-25 11:01:31 +05:30
sairajzero
0cfb80a164 reverting log.info to log.debug 2021-05-24 17:56:13 +05:30
11b0a58351
Fixed syntax issue 2021-05-24 16:54:44 +05:30
869a7c21b4
Converted _sync log.debug to log.info 2021-05-24 16:29:15 +05:30
17dd83de10
db index.js asserts removed for resilience 2021-05-20 09:40:40 +05:30
bad1fb2552
Updated definition of index.log 2021-05-18 11:02:41 +05:30
5344d9cd5b
Fixed the conditional operator 2021-05-17 16:14:54 +05:30
89c43cd9a7
Fixing the conditional operator 2021-05-17 16:11:11 +05:30
0d4a7e3e42
Modification of condition operator 2021-05-17 16:06:06 +05:30
d689045002
Updated comparison operator 2021-05-17 16:00:27 +05:30
Vivek Teega
1efed08a39 1.0.9 Removing assertions in transction and p2p service 2021-05-17 14:02:31 +05:30
Vivek Teega
56e5cb1f25 1.0.8 Commented out assertion stops 2021-05-17 13:37:19 +05:30
Vivek Teega
453d11c64c 1.0.7 Commented out assertion stops 2021-05-17 12:59:36 +05:30
14 changed files with 963 additions and 104 deletions

2
.gitignore vendored
View File

@ -21,6 +21,8 @@ coverage/*
**/*.config **/*.config
**/*.creator **/*.creator
*.log *.log
*.tmp
*.tmp.*
.DS_Store .DS_Store
bin/florincoin* bin/florincoin*
bin/SHA256SUMS.asc bin/SHA256SUMS.asc

View File

@ -212,6 +212,7 @@ function exitHandler(options, _process, node, err) {
if(err.stack) { if(err.stack) {
log.error(err.stack); log.error(err.stack);
} }
if(options.exit)
node.stop(function(err) { node.stop(function(err) {
if(err) { if(err) {
log.error('Failed to stop services: ' + err); log.error('Failed to stop services: ' + err);
@ -229,7 +230,7 @@ function exitHandler(options, _process, node, err) {
} }
function registerExitHandlers(_process, node) { function registerExitHandlers(_process, node) {
_process.on('uncaughtException', exitHandler.bind(null, {exit:true}, _process, node)); _process.on('uncaughtException', exitHandler.bind(null, {exit:false}, _process, node));
_process.on('SIGINT', exitHandler.bind(null, {sigint:true}, _process, node)); _process.on('SIGINT', exitHandler.bind(null, {sigint:true}, _process, node));
} }

View File

@ -2,11 +2,13 @@
function Encoding(servicePrefix) { function Encoding(servicePrefix) {
this.servicePrefix = servicePrefix; this.servicePrefix = servicePrefix;
this.addressIndex = new Buffer('00', 'hex');
this.utxoIndex = new Buffer('01', 'hex');
this.addressCache = new Buffer('fe', 'hex');
} }
Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index, input, timestamp) { Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index, input, timestamp) {
var prefix = new Buffer('00', 'hex'); var buffers = [this.servicePrefix, this.addressIndex];
var buffers = [this.servicePrefix, prefix];
var addressSizeBuffer = new Buffer(1); var addressSizeBuffer = new Buffer(1);
addressSizeBuffer.writeUInt8(address.length); addressSizeBuffer.writeUInt8(address.length);
@ -58,8 +60,7 @@ Encoding.prototype.decodeAddressIndexKey = function(buffer) {
}; };
Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) { Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) {
var prefix = new Buffer('01', 'hex'); var buffers = [this.servicePrefix, this.utxoIndex];
var buffers = [this.servicePrefix, prefix];
var addressSizeBuffer = new Buffer(1); var addressSizeBuffer = new Buffer(1);
addressSizeBuffer.writeUInt8(address.length); addressSizeBuffer.writeUInt8(address.length);
@ -114,5 +115,53 @@ Encoding.prototype.decodeUtxoIndexValue = function(buffer) {
}; };
}; };
Encoding.prototype.encodeAddressCacheKey = function(address) {
return Buffer.concat([this.servicePrefix, this.addressCache, new Buffer(address, 'utf8')]);
}
Encoding.prototype.decodeAddressCacheKey = function(buffer) {
return buffer.slice(3).toString('utf8');
}
Encoding.prototype.encodeAddressCacheValue = function(lastTx, lastBlock, balance, received, sent, txApperances) {
var buffer = [];
var balanceBuffer = new Buffer(8);
balanceBuffer.writeBigUInt64BE(BigInt(balance));
buffer.push(balanceBuffer);
var receivedBuffer = new Buffer(8);
receivedBuffer.writeBigUInt64BE(BigInt(received));
buffer.push(receivedBuffer);
var sentBuffer = new Buffer(8);
sentBuffer.writeBigUInt64BE(BigInt(sent));
buffer.push(sentBuffer);
var txApperancesBuffer = new Buffer(4);
txApperancesBuffer.writeUInt32BE(txApperances);
buffer.push(txApperancesBuffer);
var txidBuffer = new Buffer(lastTx, 'hex');
buffer.push(txidBuffer);
var blkBuffer = new Buffer(lastBlock, 'hex');
buffer.push(blkBuffer);
return Buffer.concat(buffer);
}
Encoding.prototype.decodeAddressCacheValue = function(buffer) {
var balance = parseInt(buffer.readBigUInt64BE(0));
var received = parseInt(buffer.readBigUInt64BE(8));
var sent = parseInt(buffer.readBigUInt64BE(16));
var txApperances = buffer.readUInt32BE(24);
var lastTx = buffer.slice(28, 60).toString('hex'); //28 + 32 (tx hash buffer length) = 60
var lastBlock = buffer.slice(60).toString('hex');
return { lastTx, lastBlock, balance, received, sent, txApperances };
}
module.exports = Encoding; module.exports = Encoding;

View File

@ -16,7 +16,9 @@ var utils = require('../../utils');
var LRU = require('lru-cache'); var LRU = require('lru-cache');
var XXHash = require('xxhash'); var XXHash = require('xxhash');
const MAX_TX_QUERY_LIMIT_HISTORY = 1000;
const MAX_TX_QUERY_LIMIT_UTXO = 1000;
const MAX_TX_QUERY_LIMIT_SUMMARY = 500;
// See rationale about this cache at function getTxList(next) // See rationale about this cache at function getTxList(next)
const TXID_LIST_CACHE_ITEMS = 250; // nr of items (this translates to: consecutive const TXID_LIST_CACHE_ITEMS = 250; // nr of items (this translates to: consecutive
@ -67,8 +69,8 @@ AddressService.dependencies = [
// then I would pass back [tx1, tx2] in that order // then I would pass back [tx1, tx2] in that order
// //
// Instead of passing addresses, with from>0, options.cacheKey can be used to define the address set. // Instead of passing addresses, with from>0, options.cacheKey can be used to define the address set.
// //(old one: non-optimized for large data)
AddressService.prototype.getAddressHistory = function(addresses, options, callback) { AddressService.prototype.__getAddressHistory = function(addresses, options, callback) {
var self = this; var self = this;
var cacheUsed = false; var cacheUsed = false;
@ -173,8 +175,106 @@ AddressService.prototype.getAddressHistory = function(addresses, options, callba
}; };
AddressService.prototype.getAddressHistory = function(addresses, options, streamer, callback) {
var self = this;
options = options || {};
//options.from = options.from || 0; //Deprecated, use `after` and `before` option
//options.to = options.to || 0xffffffff; //Deprecated, use `after` and `before` option
if(!_.isFunction(callback)){ //if only 3 args, then streamer is callback
callback = streamer;
streamer = () => null; //NULL fn
}
if (_.isUndefined(options.queryMempool)) {
options.queryMempool = true;
}
if (_.isUndefined(options.mempoolOnly)) {
options.mempoolOnly = false;
}
if(_.isUndefined(options.reverse)) {
options.reverse = false;
}
var old_support = false;
//Quick support for `from` and `to` options (DEPRECATED! Not recommeded to use)
if(!_.isUndefined(options.from) || !_.isUndefined(options.to)) {
old_support = true;
options.from = options.from || 0;
options.to = options.to || 0xffffffff; //Max value of to will actually be MAX_TX_QUERY_LIMIT_HISTORY
}
if (_.isString(addresses)) {
addresses = [addresses];
}
var results = {
totalCount: 0,
items: [],
}
async.eachLimit(addresses, 4, function(address, next) {
var addr_options = Object.assign({}, options), addr_count = 0;
self._streamAddressSummary(address, addr_options, function(err, tx){
if(err)
return log.error(err);
addr_count++;
if(!results.items.some(x => x.txid() === tx.txid())) {//add only if tx not already in array
if(!options.reverse)
results.items.unshift(tx); //using unshift, so that recent tx (low) are at front
else
results.items.push(tx);
}
if(results.items.length > MAX_TX_QUERY_LIMIT_HISTORY) { //remove items from array when overflown
results.items.sort((a, b) => (b.__height || 0xffffffff) - (a.__height || 0xffffffff) || b.txid().localeCompare(a.txid()));
let del_count = results.items.length - MAX_TX_QUERY_LIMIT_HISTORY;
let start_index = (old_support || options.reverse) ? MAX_TX_QUERY_LIMIT_HISTORY : 0;
results.items.splice(start_index, del_count);
results.incomplete = true;
if(!old_support && addr_count >= MAX_TX_QUERY_LIMIT_HISTORY)
addr_options.flag_stop = true; //limit has reached, stop quering db for more tx
}
streamer(null, tx);
}, next);
}, function(err) {
if (err) {
return callback(err);
}
//sort items in desc block-height, then asc txid (if same height)
results.items.sort((a, b) => (b.__height || 0xffffffff) - (a.__height || 0xffffffff) || b.txid().localeCompare(a.txid()));
results.totalCount = results.items.length ;
//Quick support for `from` and `to` options (DEPRECATED! Not recommeded to use)
if(old_support) {
results.items = results.items.slice(options.from, options.to);
}
callback(null, results);
})
}
// this is basically the same as _getAddressHistory apart from the summary // this is basically the same as _getAddressHistory apart from the summary
AddressService.prototype.getAddressSummary = function(address, options, callback) { //(old one: non-optimized for large data)
AddressService.prototype.__getAddressSummary = function(address, options, callback) {
var self = this; var self = this;
@ -200,7 +300,7 @@ AddressService.prototype.getAddressSummary = function(address, options, callback
txApperances: 0, txApperances: 0,
}; };
self.getAddressHistory(address, options, function(err, results) { self.__getAddressHistory(address, options, function(err, results) { //old fn
if (err) { if (err) {
return callback(err); return callback(err);
@ -218,6 +318,229 @@ AddressService.prototype.getAddressSummary = function(address, options, callback
}; };
AddressService.prototype.getAddressSummary = function(address, options, streamer, callback) {
var self = this;
options = options || {};
//options.from = options.from || 0; //Deprecated, use `after` and `before` option
//options.to = options.to || 0xffffffff; //Deprecated, use `after` and `before` option
if (_.isUndefined(options.queryMempool)) {
options.queryMempool = true;
}
if(!_.isFunction(callback)){ //if only 3 args, then streamer is callback
callback = streamer;
streamer = () => null; //NULL fn
}
var count = 0;
var result = {
addrStr: address,
balance: 0,
balanceSat: 0,
totalReceived: 0,
totalReceivedSat: 0,
totalSent: 0,
totalSentSat: 0,
unconfirmedBalance: 0,
unconfirmedBalanceSat: 0,
unconfirmedTxApperances: 0,
txApperances: 0,
};
var useCache = _.isUndefined(options.after) && _.isUndefined(options.before);
var lastTx, lastBlock;
self._loadCache(address, result, useCache, function(err, lastCachedTx) {
if(err)
log.error(err);
if(!_.isUndefined(lastCachedTx))
options.after = lastCachedTx;
self._streamAddressSummary(address, options, function(err, tx) {
if(err)
return log.error(err);
if(tx) {
count++;
self._aggregateAddressSummaryResult(tx, address, result, options);
if(tx.confirmations) {
lastTx = tx.txid();
lastBlock = tx.blockhash;
}
}
if(count >= MAX_TX_QUERY_LIMIT_SUMMARY) {//stop quering db when limit reached
options.flag_stop = true;
result.incomplete = true;
}
streamer(null, tx);
}, function(err) {
if (err) {
return callback(err);
}
result.balanceSat = parseInt(result.balanceSat.toFixed());
result.totalReceivedSat = parseInt(result.totalReceivedSat.toFixed());
result.totalSentSat = parseInt(result.totalSentSat.toFixed());
result.txApperances = parseInt(result.txApperances.toFixed());
result.unconfirmedBalanceSat = parseInt(result.unconfirmedBalanceSat.toFixed());
result.unconfirmedTxApperances = parseInt(result.unconfirmedTxApperances.toFixed());
result.balance = Unit.fromSatoshis(result.balanceSat).toBTC();
result.totalReceived = Unit.fromSatoshis(result.totalReceivedSat).toBTC();
result.totalSent = Unit.fromSatoshis(result.totalSentSat).toBTC();
result.unconfirmedBalance = Unit.fromSatoshis(result.unconfirmedBalanceSat).toBTC();
result.lastItem = lastTx;
callback(null, result);
//store in cache if needed
if(useCache) {
if(result.incomplete) //full summary needs to be calculated in background
self._cacheSummaryInBackground(address, lastTx, lastBlock, result);
else if (!_.isUndefined(lastCachedTx) && !_.isUndefined(lastTx)
&& lastTx != lastCachedTx && !self._cacheInstance.has(address)) //update cache if needed
self._storeCache(address, lastTx, lastBlock, result);
}
});
})
}
AddressService.prototype._cacheInstance = new Set();
AddressService.prototype._cacheSummaryInBackground = function(address, lastTx, lastBlock, result){
const self = this;
if(self._cacheInstance.has(address))
return;
self._cacheInstance.add(address);
const cache = {
balanceSat: result.balanceSat,
totalReceivedSat: result.totalReceivedSat,
totalSentSat: result.totalSentSat,
txApperances: result.txApperances,
unconfirmedBalanceSat: 0, //unconfirmed (mempool) values should not be cached
unconfirmedTxApperances: 0
};
const options = { queryMempool: false, after: lastTx, noTxList: true };
self._streamAddressSummary(address, options, function(err, tx) {
if(err)
return log.error(err);
if(tx) {
self._aggregateAddressSummaryResult(tx, address, cache, options);
if(tx.confirmations){
lastTx = tx.txid();
lastBlock = tx.blockhash;
}
}
}, function(err) {
if (err)
return log.error(err);
cache.balanceSat = parseInt(cache.balanceSat.toFixed());
cache.totalReceivedSat = parseInt(cache.totalReceivedSat.toFixed());
cache.totalSentSat = parseInt(cache.totalSentSat.toFixed());
cache.txApperances = parseInt(cache.txApperances.toFixed());
if(!_.isUndefined(lastTx))
self._storeCache(address, lastTx, lastBlock, cache);
self._cacheInstance.delete(address); //remove from running instance
});
}
AddressService.prototype._storeCache = function(address, lastCacheTx, lastCacheBlock, result, callback) {
const self = this;
var key = self._encoding.encodeAddressCacheKey(address);
var value = self._encoding.encodeAddressCacheValue(lastCacheTx, lastCacheBlock, result.balanceSat, result.totalReceivedSat, result.totalSentSat, result.txApperances)
if(!_.isFunction(callback)) //if callback is not passed, call a empty function
callback = () => null;
self._db.put(key, value, callback);
}
AddressService.prototype._loadCache = function(address, result, useCache, callback) {
const self = this;
if(!useCache) //skip if useCache is false (cases like 'after' and/or 'before' parameter is used by client)
return callback();
var key = self._encoding.encodeAddressCacheKey(address);
self._db.get(key, function(err, value) {
if (err) {
return callback(err);
}
if (!value) {
return callback();
}
var addressCache = self._encoding.decodeAddressCacheValue(value);
var lastCacheTx = addressCache.lastTx, lastCacheBlock = addressCache.lastBlock
self._block.getBlock(lastCacheBlock, function(err, block) {
if(err) {
return callback(err);
}
if (!block) { //block not found, probably removed in reorg.
//delete the existing cache and recalc values freshly
self._deleteCache(address, function() {
callback();
});
} else {
//values are in satoshis
result.balanceSat = addressCache.balance;
result.totalReceivedSat = addressCache.received;
result.totalSentSat = addressCache.sent;
result.txApperances = addressCache.txApperances;
callback(null, lastCacheTx);
}
})
});
}
AddressService.prototype._deleteCache = function(address, callback) {
const self = this;
var key = self._encoding.encodeAddressCacheKey(address);
if(!_.isFunction(callback)) //if callback is not passed, call a empty function
callback = () => null;
self._db.del(key, callback);
}
AddressService.prototype._setOutputResults = function(tx, address, result) { AddressService.prototype._setOutputResults = function(tx, address, result) {
for(var j = 0; j < tx.outputs.length; j++) { for(var j = 0; j < tx.outputs.length; j++) {
@ -265,7 +588,6 @@ AddressService.prototype._getAddressSummaryResult = function(txs, address, resul
var self = this; var self = this;
for(var i = 0; i < txs.length; i++) { for(var i = 0; i < txs.length; i++) {
var tx = txs[i]; var tx = txs[i];
self._setOutputResults(tx, address, result); self._setOutputResults(tx, address, result);
@ -283,6 +605,110 @@ AddressService.prototype._getAddressSummaryResult = function(txs, address, resul
return result; return result;
}; };
AddressService.prototype._getOccurrenceCount = function(tx, address) {
let count = 0;
for(var i = 0; i < tx.inputs.length; i++) {
var input = tx.inputs[i];
if(utils.getAddress(input, this._network) === address)
count++;
}
for(var j = 0; j < tx.outputs.length; j++) {
var output = tx.outputs[j];
if(utils.getAddress(output, this._network) === address)
count++;
}
return count;
}
AddressService.prototype._getOutputResults = function(tx, address) {
let value = 0;
for(var j = 0; j < tx.outputs.length; j++) {
var output = tx.outputs[j];
if (utils.getAddress(output, this._network) === address)
value += output.value;
}
return value;
};
AddressService.prototype._getInputResults = function(tx, address) {
let value = 0;
for(var i = 0; i < tx.inputs.length; i++) {
var input = tx.inputs[i];
if (utils.getAddress(input, this._network) === address)
value += tx.__inputValues[i];
}
return value;
};
AddressService.prototype._aggregateAddressSummaryResult = function (tx, address, result, options) {
var self = this;
let output_val = self._getOutputResults(tx, address);
let input_val = self._getInputResults(tx, address);
//aggregate the result
if(tx.confirmations) {
result.txApperances++;
result.totalReceivedSat += output_val;
result.balanceSat += output_val;
result.totalSentSat += input_val;
result.balanceSat -= input_val;
} else {
result.unconfirmedTxApperances++;
result.unconfirmedBalanceSat += output_val;
result.unconfirmedBalanceSat -= input_val;
}
if (!options.noTxList) {
if (!result.transactions) {
result.transactions = [];
}
let txid = tx.txid();
if(!result.transactions.includes(txid)) { //push txid only if its not in the array
result.transactions.unshift(txid); //using unshift, so that recent tx (low confirmation) are at front
if(result.transactions.length > MAX_TX_QUERY_LIMIT_SUMMARY)
result.transactions.pop(); //pop the oldest tx in list (when list limit is maxed out)
}
}
}
AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) { AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) {
var self = this; var self = this;
@ -365,6 +791,11 @@ AddressService.prototype.getAddressUnspentOutputs = function(address, options, c
utxoStream.on('data', function(data) { utxoStream.on('data', function(data) {
if(results.length >= MAX_TX_QUERY_LIMIT_UTXO) { //Max array limit reached, end response
utxoStream.emit('end');
return;
}
var key = self._encoding.decodeUtxoIndexKey(data.key); var key = self._encoding.decodeUtxoIndexKey(data.key);
var value = self._encoding.decodeUtxoIndexValue(data.value); var value = self._encoding.decodeUtxoIndexValue(data.value);
@ -440,19 +871,21 @@ AddressService.prototype.stop = function(callback) {
AddressService.prototype._getTxidStream = function(address, options) { AddressService.prototype._getTxidStream = function(address, options) {
var start = this._encoding.encodeAddressIndexKey(address); var criteria = {};
var end = Buffer.concat([
start.slice(0, address.length + 4),
options.endHeightBuf,
new Buffer(new Array(83).join('f'), 'hex')
]);
var criteria = { if(options.after)
gte: start, criteria.gt = this._encoding.encodeAddressIndexKey(address, options.start, options.after, 0xffffffff, 1, 0xffffffff); //0xffffffff is for getting after the txid
lte: end, else
reverse: true // txids stream from low confirmations to high confirmations criteria.gte = this._encoding.encodeAddressIndexKey(address, options.start);
};
if(options.before)
criteria.lt = this._encoding.encodeAddressIndexKey(address, options.end, options.before); //get before the txid
else
criteria.lte = this._encoding.encodeAddressIndexKey(address, options.end, Array(65).join('f'), 0xffffffff, 1, 0xffffffff);
//reverse option can be used explictly when latest tx are required
if(options.reverse)
criteria.reverse = true;
// txid stream // txid stream
var txidStream = this._db.createKeyStream(criteria); var txidStream = this._db.createKeyStream(criteria);
@ -463,6 +896,7 @@ AddressService.prototype._getTxidStream = function(address, options) {
return txidStream; return txidStream;
}; };
//(used by old fn)
AddressService.prototype._getAddressTxHistory = function(options, callback) { AddressService.prototype._getAddressTxHistory = function(options, callback) {
var self = this; var self = this;
@ -491,6 +925,7 @@ AddressService.prototype._getAddressTxHistory = function(options, callback) {
}; };
//(used by old fn)
AddressService.prototype._getAddressTxidHistory = function(address, options, callback) { AddressService.prototype._getAddressTxidHistory = function(address, options, callback) {
var self = this; var self = this;
@ -500,9 +935,6 @@ AddressService.prototype._getAddressTxidHistory = function(address, options, cal
var results = []; var results = [];
options.endHeightBuf = new Buffer(4);
options.endHeightBuf.writeUInt32BE(options.end);
if (_.isUndefined(options.queryMempool)) { if (_.isUndefined(options.queryMempool)) {
options.queryMempool = true; options.queryMempool = true;
} }
@ -551,7 +983,15 @@ AddressService.prototype._getAddressTxidHistory = function(address, options, cal
txIdTransformStream._transform = function(chunk, enc, callback) { txIdTransformStream._transform = function(chunk, enc, callback) {
var txInfo = self._encoding.decodeAddressIndexKey(chunk); var txInfo = self._encoding.decodeAddressIndexKey(chunk);
if(results.length >= MAX_TX_QUERY_LIMIT_HISTORY) { //Max array limit reached, end response
txIdTransformStream.emit('end');
return;
}
if(!results.some(r => r.txid == txInfo.txid)) //add txid to array only if its not already there
results.push({ txid: txInfo.txid, height: txInfo.height }); results.push({ txid: txInfo.txid, height: txInfo.height });
callback(); callback();
}; };
@ -563,6 +1003,225 @@ AddressService.prototype._getAddressTxidHistory = function(address, options, cal
}; };
AddressService.prototype._streamAddressSummary = function(address, options, streamer, callback) {
var self = this;
options = options || {};
options.start = options.start || 0;
options.end = options.end || 0xffffffff;
//options.from = options.from || 0; //Deprecated, use `after` and `before` option
//options.to = options.to || 0xffffffff; //Deprecated, use `after` and `before` option
if (_.isUndefined(options.queryMempool)) {
options.queryMempool = true;
}
if (_.isUndefined(options.mempoolOnly)) {
options.mempoolOnly = false;
}
if (_.isUndefined(options.reverse)) {
options.reverse = false;
}
//declare the queue to process tx data
var tmpTxList = {}; //store processed txid temporarily to ignore duplication
var q = async.queue(function(id, cb) {
//duplication finding
if(id.txid in tmpTxList){
tmpTxList[id.txid][0]++;
if(tmpTxList[id.txid][1] !== null && tmpTxList[id.txid][0] >= tmpTxList[id.txid][1]) //all duplications are found for this txid
delete tmpTxList[id.txid];
return cb();
} else tmpTxList[id.txid] = [1, null];
if (id.height === 0xffffffff) {
return self._mempool.getMempoolTransaction(id.txid, function(err, tx) {
if (err || !tx) {
return cb(err || new Error('Address Service: could not find tx: ' + id.txid));
}
self._transaction.setTxMetaInfo(tx, options, cb);
});
}
self._transaction.getDetailedTransaction(id.txid, options, cb);
}, 1);
//q.pause(); //pause and wait until queue is set (not needed)
function chunkCallback(err, tx){
if(q.killed || (!err && !tx)) //no error or tx data (duplicate calls will have empty tx value)
return;
if(tx){
let txid = tx.txid();
tmpTxList[txid][1] = self._getOccurrenceCount(tx, address);
if(tmpTxList[txid][0] >= tmpTxList[txid][1]) //all duplications are found for this txid
delete tmpTxList[txid];
}
streamer(err, tx);
if((err || options.flag_stop) && !q.killed){
q.kill();
q.killed = true;
return callback();
}
}
const waterfall_array = [];
waterfall_array.push(
//Find start height if `after` option is passed
function parse_after_id(next){
if(_.isUndefined(options.after)) {
return next();
}
self._transaction.getTransaction(options.after, options, function(err, tx) {
if(tx && tx.confirmations && tx.height >= options.start) {
options.start = tx.height;
} else {
delete options.after;
}
next();
});
});
waterfall_array.push(
//Find end height if `before` option is passed
function parse_before_id(next){
if(_.isUndefined(options.before)) {
return next();
}
self._transaction.getTransaction(options.before, options, function(err, tx) {
if(tx && tx.confirmations && tx.height <= options.end) {
options.end = tx.height;
} else {
delete options.before;
}
next();
});
});
// stream the confirmed txids out of the address index
function query_confirmed_txids(next) {
if (options.mempoolOnly) { //Option to query from mempool only (ie, unconfirmed txs only)
return next();
}
var txIdTransformStream = new Transform({ objectMode: true });
txIdTransformStream._flush = function(cb) {
txIdTransformStream.emit('end');
cb();
};
txIdTransformStream.on('error', function(err) {
log.error('Address Service: txstream err: ' + err);
txIdTransformStream.unpipe();
});
txIdTransformStream.on('end', function() {
next();
});
txIdTransformStream._transform = function(chunk, enc, cb) {
if(options.flag_stop)//stop data query
return txIdTransformStream.unpipe();
var txInfo = self._encoding.decodeAddressIndexKey(chunk);
q.push({ txid: txInfo.txid, height: txInfo.height }, chunkCallback);
cb();
};
var txidStream = self._getTxidStream(address, options);
txidStream.pipe(txIdTransformStream);
}
// query the mempool for relevant txs for this address
function query_mempool_txids(next) {
if (!options.queryMempool || !_.isUndefined(options.before)) { //if queryMempool=false or options.before is given a valid value, then do not query mempool
return next();
}
self._mempool.getTxidsByAddress(address, 'both', function(err, mempoolTxids) {
if (mempoolTxids.length <= 0) {
return next();
}
mempoolTxids.map(id => q.push(id, chunkCallback));
next();
});
}
if(options.reverse){ //when queried txs in reverse key order, mempool first then confirmed
waterfall_array.push(query_mempool_txids);
waterfall_array.push(query_confirmed_txids);
} else { //when queried tx in key order, confirmed tx 1st, then mempool
waterfall_array.push(query_confirmed_txids);
waterfall_array.push(query_mempool_txids);
}
waterfall_array.push(
//wait for queue to complete
function end_fall(next) {
if(!q.started || q.idle()) //No tx in query (or) already finished querying
return next();
else
q.drain = () => next();
});
async.waterfall(waterfall_array, callback);
}
AddressService.prototype._removeBlock = function(block, callback) { AddressService.prototype._removeBlock = function(block, callback) {
var self = this; var self = this;

View File

@ -242,7 +242,7 @@ ProcessSerial.prototype._write = function(block, enc, callback) {
self.block.once('concurrentaddblock', function() { self.block.once('concurrentaddblock', function() {
if(!check()) { if(!check()) {
var err = new Error('Concurrent block ' + self.block.concurrentTip.__height + ' is less than ' + block.__height); var err = 'Concurrent block ' + self.block.concurrentTip.__height + ' is less than ' + block.__height;
return self.emit('error', err); return self.emit('error', err);
} }
self._process(block, callback); self._process(block, callback);

View File

@ -13,6 +13,8 @@ var bcoin = require('fcoin');
var _ = require('lodash'); var _ = require('lodash');
var LRU = require('lru-cache'); var LRU = require('lru-cache');
const MAX_IGNORED_BLOCK = 16; //Maximum ignored block allowed before trigging sync again
var BlockService = function(options) { var BlockService = function(options) {
BaseService.call(this, options); BaseService.call(this, options);
@ -231,7 +233,7 @@ BlockService.prototype._resetTip = function(callback) {
self._header.getAllHeaders(function(err, headers) { self._header.getAllHeaders(function(err, headers) {
if (err || !headers) { if (err || !headers) {
return callback(err || new Error('headers required')); log.error(err || 'headers required'); return callback(err);
} }
log.info('Block Service: retrieved all the headers for lookups.'); log.info('Block Service: retrieved all the headers for lookups.');
@ -258,7 +260,12 @@ BlockService.prototype._resetTip = function(callback) {
block = _block; block = _block;
header = headers.getIndex(--height); header = headers.getIndex(--height);
assert(header, 'Header not found for reset.');
// FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (header == false) {
log.error('Header not found for reset.');
}
// assert(header, 'Header not found for reset.');
if (!block) { if (!block) {
log.debug('Block Service: trying block: ' + header.hash); log.debug('Block Service: trying block: ' + header.hash);
@ -271,8 +278,8 @@ BlockService.prototype._resetTip = function(callback) {
}, function(err) { }, function(err) {
if (err || !block) { if (err || !block) {
return callback(err || log.error(err ||
new Error('Block Service: none of the blocks from the headers match what is already indexed in the block service.')); 'Block Service: none of the blocks from the headers match what is already indexed in the block service.'); return callback(err);
} }
self._setTip({ hash: block.rhash(), height: height + 1 }, callback); self._setTip({ hash: block.rhash(), height: height + 1 }, callback);
@ -370,8 +377,8 @@ BlockService.prototype._loadRecentBlockHashes = function(callback) {
self.getBlock(hash, function(err, block) { self.getBlock(hash, function(err, block) {
if (err || !block) { if (err || !block) {
return callback(err || new Error('Block Service: attempted to retrieve block: ' + hash + log.error(err || 'Block Service: attempted to retrieve block: ' + hash +
' but was not in the index.')); ' but was not in the index.'); return callback(err);
} }
var prevHash = bcoin.util.revHex(block.prevBlock); var prevHash = bcoin.util.revHex(block.prevBlock);
@ -387,7 +394,11 @@ BlockService.prototype._loadRecentBlockHashes = function(callback) {
return callback(err); return callback(err);
} }
assert(self._recentBlockHashes.length === times, 'Block Service: did not load enough recent block hashes from the index.'); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (self._recentBlockHashes.length != times) {
log.error('Block Service: did not load enough recent block hashes from the index.');
}
//assert(self._recentBlockHashes.length === times, 'Block Service: did not load enough recent block hashes from the index.');
log.info('Block Service: loaded: ' + self._recentBlockHashes.length + ' hashes from the index.'); log.info('Block Service: loaded: ' + self._recentBlockHashes.length + ' hashes from the index.');
callback(); callback();
@ -406,7 +417,7 @@ BlockService.prototype._getTimeSinceLastBlock = function(callback) {
self._header.getBlockHeader(Math.max(self._tip.height - 1, 0), function(err, header) { self._header.getBlockHeader(Math.max(self._tip.height - 1, 0), function(err, header) {
if(err || !header) { if(err || !header) {
return callback(err || new Error('Block Service: we should have a header in order to get time since last block.')); log.error(err || 'Block Service: we should have a header in order to get time since last block.'); return callback(err);
} }
async.map([ self._tip.hash, header.hash ], function(hash, next) { async.map([ self._tip.hash, header.hash ], function(hash, next) {
@ -628,6 +639,7 @@ BlockService.prototype._startBlockSubscription = function() {
} }
this._subscribedBlock = true; this._subscribedBlock = true;
this._ignoredBlockCount = 0; //SZ: reset the ignored count to 0 when subscription starts
log.info('Block Service: starting p2p block subscription.'); log.info('Block Service: starting p2p block subscription.');
this._bus.on('p2p/block', this._queueBlock.bind(this)); this._bus.on('p2p/block', this._queueBlock.bind(this));
@ -656,7 +668,7 @@ BlockService.prototype._findLatestValidBlockHeader = function(callback) {
if (self._reorgToBlock) { if (self._reorgToBlock) {
return self._header.getBlockHeader(self._reorgToBlock, function(err, header) { return self._header.getBlockHeader(self._reorgToBlock, function(err, header) {
if (err || !header) { if (err || !header) {
return callback(err || new Error('Block Service: header not found to reorg to.')); log.error(err || 'Block Service: header not found to reorg to.'); return callback(err);
} }
callback(null, header); callback(null, header);
}); });
@ -711,12 +723,19 @@ BlockService.prototype._findLatestValidBlockHeader = function(callback) {
// any of our recent block hashes in its indexes. // any of our recent block hashes in its indexes.
// if some joker mines a block using an orphan block as its prev block, then the effect of this will be // if some joker mines a block using an orphan block as its prev block, then the effect of this will be
// us detecting a reorg, but not actually reorging anything // us detecting a reorg, but not actually reorging anything
assert(header, 'Block Service: we could not locate any of our recent block hashes in the header service ' + // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
'index. Perhaps our header service sync\'ed to the wrong chain?'); if (!_.isUndefined(header)) {
if (header == false) {
assert(header.height <= self._tip.height, 'Block Service: we found a common ancestor header whose ' + log.error('Block Service: we could not locate any of our recent block hashes in the header service ' + 'index. Perhaps our header service sync\'ed to the wrong chain?');
'height was greater than our current tip. This should be impossible.'); }
}
// assert(header, 'Block Service: we could not locate any of our recent block hashes in the header service ' + 'index. Perhaps our header service sync\'ed to the wrong chain?');
if (!_.isUndefined(header.height)) {
if (header.height > self._tip.height) {
log.error('Block Service: we found a common ancestor header whose ' + 'height was greater than our current tip. This should be impossible.');
}
// assert(header.height <= self._tip.height, 'Block Service: we found a common ancestor header whose ' + 'height was greater than our current tip. This should be impossible.');
}
callback(null, header); callback(null, header);
}); });
@ -739,13 +758,13 @@ BlockService.prototype._findBlocksToRemove = function(commonHeader, callback) {
self._getBlock(hash, function(err, block) { self._getBlock(hash, function(err, block) {
if (err || !block) { if (err || !block) {
return next(err || new Error('Block Service: block not found in index.')); return next(err || 'Block Service: block not found in index.');
} }
self._timestamp.getTimestamp(block.rhash(), function(err, timestamp) { self._timestamp.getTimestamp(block.rhash(), function(err, timestamp) {
if (err || !timestamp) { if (err || !timestamp) {
return callback(err || new Error('timestamp missing from reorg.')); log.error(err || 'timestamp missing from reorg.'); return callback(err);
} }
block.__height = height; block.__height = height;
@ -815,8 +834,10 @@ BlockService.prototype._handleReorg = function(callback) {
blocksToRemove = _blocksToRemove; blocksToRemove = _blocksToRemove;
assert(blocksToRemove.length >= 1 && blocksToRemove.length <= self._recentBlockHashes.length, // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
'Block Service: the number of blocks to remove looks to be incorrect.'); if (blocksToRemove.length < 1 || blocksToRemove.length > self._recentBlockHashes.length) {
log.error('Block Service: the number of blocks to remove looks to be incorrect.'); }
// assert(blocksToRemove.length >= 1 && blocksToRemove.length <= self._recentBlockHashes.length, 'Block Service: the number of blocks to remove looks to be incorrect.');
log.warn('Block Service: chain reorganization detected, current height/hash: ' + self._tip.height + '/' + log.warn('Block Service: chain reorganization detected, current height/hash: ' + self._tip.height + '/' +
self._tip.hash + ' common ancestor hash: ' + commonAncestorHeader.hash + ' at height: ' + commonAncestorHeader.height + self._tip.hash + ' common ancestor hash: ' + commonAncestorHeader.hash + ' at height: ' + commonAncestorHeader.height +
@ -920,6 +941,15 @@ BlockService.prototype._processBlock = function(block, callback) {
return self._saveBlock(block, callback); return self._saveBlock(block, callback);
} }
//SZ: count the ignored blocks. if many blocks ignored, trigger sync process
if(self._ignoredBlockCount < MAX_IGNORED_BLOCK)
self._ignoredBlockCount++;
else {
self._ignoredBlockCount = 0;
self._removeAllSubscriptions();
self._startSync();
}
// reorg -- in this case, we will not handle the reorg right away // reorg -- in this case, we will not handle the reorg right away
// instead, we will skip the block and wait for the eventual call to // instead, we will skip the block and wait for the eventual call to
// "onHeaders" function. When the header service calls this function, // "onHeaders" function. When the header service calls this function,
@ -935,6 +965,7 @@ BlockService.prototype._saveBlock = function(block, callback) {
var self = this; var self = this;
block.__height = self._tip.height + 1; block.__height = self._tip.height + 1;
self._ignoredBlockCount = 0; //SZ: a block is saved, reset the ignored count
var services = self.node.services; var services = self.node.services;
@ -1079,7 +1110,7 @@ BlockService.prototype._startSync = function() {
this.on('next block', this._sync.bind(this)); this.on('next block', this._sync.bind(this));
this.on('synced', this._onSynced.bind(this)); this.on('synced', this._onSynced.bind(this));
clearInterval(this._reportInterval); clearInterval(this._reportInterval);
this._reportingInterval = setInterval(this._logProgress.bind(this), 5000); this._reportInterval = setInterval(this._logProgress.bind(this), 5000);
return this._sync(); return this._sync();
} }

View File

@ -315,7 +315,7 @@ Reorg.prototype.findCommonAncestorAndNewHashes = function(oldTipHash, newTipHash
} }
if(!mainPosition && !forkPosition) { if(!mainPosition && !forkPosition) {
return next(new Error('Unknown common ancestor')); return next('Unknown common ancestor');
} }
next(); next();

View File

@ -3,6 +3,7 @@
var util = require('util'); var util = require('util');
var fs = require('fs'); var fs = require('fs');
var async = require('async'); var async = require('async');
var _ = require('lodash');
var levelup = require('levelup'); var levelup = require('levelup');
var leveldown = require('leveldown'); var leveldown = require('leveldown');
var mkdirp = require('mkdirp'); var mkdirp = require('mkdirp');
@ -50,7 +51,6 @@ DB.prototype._onError = function(err) {
if (!this._stopping) { if (!this._stopping) {
log.error('Db Service: error: ' + err); log.error('Db Service: error: ' + err);
//FLO Crash Error Resolution by RanchiMall 10th May 2021 //FLO Crash Error Resolution by RanchiMall 10th May 2021
//return this.node.stop();
//this.node.stop(); //this.node.stop();
} }
}; };
@ -96,7 +96,7 @@ DB.prototype.get = function(key, options, callback) {
var cb = callback; var cb = callback;
var opts = options; var opts = options;
if (typeof callback !== 'function') { if (!_.isFunction(callback)) {
cb = options; cb = options;
opts = {}; opts = {};
} }
@ -119,7 +119,9 @@ DB.prototype.get = function(key, options, callback) {
} else { } else {
cb(new Error('Shutdown sequence underway, not able to complete the query')); // FLOSight Error Correction from RanchiMall 20th May 2021.
//cb(new Error('Shutdown sequence underway, not able to complete the query'));
log.error('Shutdown sequence underway, not able to complete the query');
} }
}; };
@ -130,17 +132,40 @@ DB.prototype.put = function(key, value, callback) {
callback(); callback();
} }
assert(Buffer.isBuffer(key), 'key NOT a buffer as expected.'); // FLOSight Error Correction from RanchiMall 20th May 2021. removed the unhandled assert and replaced by looging of error
if (Buffer.isBuffer(key) == false) {
log.error('key NOT a buffer as expected.');
}
// assert(Buffer.isBuffer(key), 'key NOT a buffer as expected.');
if (value) { if (value) {
assert(Buffer.isBuffer(value), 'value exists but NOT a buffer as expected.'); // FLOSight Error Correction from RanchiMall 20th May 2021. removed the unhandled assert and replaced by looging of error
if (Buffer.isBuffer(value) == false) {
log.error('value exists but NOT a buffer as expected.');
}
//assert(Buffer.isBuffer(value), 'value exists but NOT a buffer as expected.');
} }
this._store.put(key, value, callback); this._store.put(key, value, callback);
}; };
DB.prototype.del = function(key, callback) {
if (this._stopping) {
callback();
}
// FLOSight Error Correction from RanchiMall 20th May 2021. removed the unhandled assert and replaced by looging of error
if (Buffer.isBuffer(key) == false) {
log.error('key NOT a buffer as expected.');
}
// assert(Buffer.isBuffer(key), 'key NOT a buffer as expected.');
this._store.del(key, callback);
}
DB.prototype.batch = function(ops, callback) { DB.prototype.batch = function(ops, callback) {
if (this._stopping) { if (this._stopping) {
@ -149,11 +174,18 @@ DB.prototype.batch = function(ops, callback) {
for(var i = 0; i < ops.length; i++) { for(var i = 0; i < ops.length; i++) {
assert(Buffer.isBuffer(ops[i].key), 'key NOT a buffer as expected.'); // FLOSight Error Correction from RanchiMall 20th May 2021. removed the unhandled assert and replaced by looging of error
if (Buffer.isBuffer(ops[i].key) == false) {
log.error('key NOT a buffer as expected.');
}
//assert(Buffer.isBuffer(ops[i].key), 'key NOT a buffer as expected.');
if (ops[i].value) { if (ops[i].value) {
// FLOSight Error Correction from RanchiMall 20th May 2021. removed the unhandled assert and replaced by looging of error
assert(Buffer.isBuffer(ops[i].value), 'value exists but NOT a buffer as expected.'); if (Buffer.isBuffer(ops[i].value) == false) {
log.error('value exists but NOT a buffer as expected.');
}
//assert(Buffer.isBuffer(ops[i].value), 'value exists but NOT a buffer as expected.');
} }
} }

View File

@ -14,6 +14,8 @@ var assert = require('assert');
var constants = require('../../constants'); var constants = require('../../constants');
var bcoin = require('fcoin'); var bcoin = require('fcoin');
const SYNC_CHECK_INTERVAL = 1000 * 60 * 15; //15 mins
var HeaderService = function(options) { var HeaderService = function(options) {
BaseService.call(this, options); BaseService.call(this, options);
@ -145,7 +147,11 @@ HeaderService.prototype._adjustTipBackToCheckpoint = function() {
HeaderService.prototype._setGenesisBlock = function(callback) { HeaderService.prototype._setGenesisBlock = function(callback) {
assert(this._tip.hash === this.GENESIS_HASH, 'Expected tip hash to be genesis hash, but it was not.'); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (this._tip.hash != this.GENESIS_HASH) {
log.error('Expected tip hash to be genesis hash, but it was not.');
}
// assert(this._tip.hash === this.GENESIS_HASH, 'Expected tip hash to be genesis hash, but it was not.');
var genesisHeader = { var genesisHeader = {
hash: this.GENESIS_HASH, hash: this.GENESIS_HASH,
@ -412,7 +418,10 @@ HeaderService.prototype._getDBOpForLastHeader = function(nextHeader) {
this._lastHeader.nextHash = nextHeader.hash; this._lastHeader.nextHash = nextHeader.hash;
var keyHash = this._encoding.encodeHeaderHashKey(this._lastHeader.hash); var keyHash = this._encoding.encodeHeaderHashKey(this._lastHeader.hash);
assert(this._lastHeader.height >= 0, 'Trying to save a header with incorrect height.'); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (this._lastHeader.height < 0) { log.error('Trying to save a header with incorrect height.');
}
// assert(this._lastHeader.height >= 0, 'Trying to save a header with incorrect height.');
var keyHeight = this._encoding.encodeHeaderHeightKey(this._lastHeader.height); var keyHeight = this._encoding.encodeHeaderHeightKey(this._lastHeader.height);
var value = this._encoding.encodeHeaderValue(this._lastHeader); var value = this._encoding.encodeHeaderValue(this._lastHeader);
@ -467,8 +476,11 @@ HeaderService.prototype._onHeaders = function(headers) {
var header = transformedHeaders[i]; var header = transformedHeaders[i];
assert(self._lastHeader.hash === header.prevHash, 'headers not in order: ' + self._lastHeader.hash + // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
' -and- ' + header.prevHash + ' Last header at height: ' + self._lastHeader.height); if (self._lastHeader.hash != header.prevHash) {
log.error('headers not in order: ' + self._lastHeader.hash + ' -and- ' + header.prevHash + ' Last header at height: ' + self._lastHeader.height);
}
// assert(self._lastHeader.hash === header.prevHash, 'headers not in order: ' + self._lastHeader.hash + ' -and- ' + header.prevHash + ' Last header at height: ' + self._lastHeader.height);
var ops = self._onHeader(header); var ops = self._onHeader(header);
@ -485,9 +497,9 @@ HeaderService.prototype._onHeaders = function(headers) {
}; };
HeaderService.prototype._handleError = function(err) { HeaderService.prototype._handleError = function(err) {
log.error('Header Service: ' + err); log.error('Error in Header Service: ' + err);
// FLO Crash Error Resolution by RanchiMall 10th May 2021 return;
// this.node.stop(); //this.node.stop();
}; };
HeaderService.prototype._saveHeaders = function(dbOps, callback) { HeaderService.prototype._saveHeaders = function(dbOps, callback) {
@ -512,6 +524,7 @@ HeaderService.prototype._saveHeaders = function(dbOps, callback) {
HeaderService.prototype._onHeadersSave = function(callback) { HeaderService.prototype._onHeadersSave = function(callback) {
var self = this; var self = this;
self._syncUnresponsive = false; //SZ: got response from peer
self._logProgress(); self._logProgress();
if (!self._syncComplete()) { if (!self._syncComplete()) {
@ -519,6 +532,12 @@ HeaderService.prototype._onHeadersSave = function(callback) {
return callback(); return callback();
} }
//SZ: clear the interval check as sync is completed
if(self._syncCheckInterval){
clearInterval(self._syncCheckInterval);
self._syncCheckInterval = null;
}
self._endHeaderSubscription(); // we don't need headers any more self._endHeaderSubscription(); // we don't need headers any more
self._startBlockSubscription(); // we need new blocks coming tu us aynchronuously self._startBlockSubscription(); // we need new blocks coming tu us aynchronuously
@ -598,7 +617,7 @@ HeaderService.prototype._getHeader = function(height, hash, callback) {
/*jshint -W018 */ /*jshint -W018 */
if (!hash && !(height >= 0)) { if (!hash && !(height >= 0)) {
/*jshint +W018 */ /*jshint +W018 */
return callback(new Error('invalid arguments')); return callback('invalid arguments');
} }
if (height === self._lastHeader.height || hash === self._lastHeader.hash) { if (height === self._lastHeader.height || hash === self._lastHeader.hash) {
@ -697,6 +716,23 @@ HeaderService.prototype._startSync = function() {
// common case // common case
if (numNeeded > 0) { if (numNeeded > 0) {
log.info('Header Service: Gathering: ' + numNeeded + ' ' + 'header(s) from the peer-to-peer network.'); log.info('Header Service: Gathering: ' + numNeeded + ' ' + 'header(s) from the peer-to-peer network.');
//SZ: Adding interval check for sync with peer is responsive or not
//(only if fcoin is started by flocore)
if(self._p2p._bcoin){
self._syncUnresponsive = true;
self._syncCheckInterval = setInterval(() => {
//check the best height
if(self._bestHeight < self._p2p._bcoin._bcoin.pool.chain.height)
self._bestHeight = self._p2p._bcoin._bcoin.pool.chain.height;
//call sync again if unresponsive
if(self._syncUnresponsive)
self._sync();
else //reset unresponsive as true
self._syncUnresponsive = true;
}, SYNC_CHECK_INTERVAL);
}
return self._sync(); return self._sync();
} }
@ -778,9 +814,11 @@ HeaderService.prototype._findReorgConditionInNewPeer = function(callback) {
// nothing matched... // nothing matched...
// at this point, we should wonder if we are connected to the wrong network // at this point, we should wonder if we are connected to the wrong network
assert(true, 'We tried to find a common header between current set of headers ' + // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
'and the new peer\'s set of headers, but there were none. This should be impossible ' + if (false) {
' if the new peer is using the same genesis block.'); log.error('We tried to find a common header between current set of headers ' + 'and the new peer\'s set of headers, but there were none. This should be impossible ' + ' if the new peer is using the same genesis block.');
}
// assert(true, 'We tried to find a common header between current set of headers ' +'and the new peer\'s set of headers, but there were none. This should be impossible ' +' if the new peer is using the same genesis block.');
}); });
self._getP2PHeaders(self.GENESIS_HASH); self._getP2PHeaders(self.GENESIS_HASH);
@ -881,7 +919,11 @@ HeaderService.prototype._sync = function() {
HeaderService.prototype.getEndHash = function(tip, blockCount, callback) { HeaderService.prototype.getEndHash = function(tip, blockCount, callback) {
assert(blockCount >= 1, 'Header Service: block count to getEndHash must be at least 1.'); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (blockCount < 1) {
log.error('Header Service: block count to getEndHash must be at least 1.');
}
// assert(blockCount >= 1, 'Header Service: block count to getEndHash must be at least 1.');
var self = this; var self = this;
@ -892,7 +934,7 @@ HeaderService.prototype.getEndHash = function(tip, blockCount, callback) {
} }
if (numResultsNeeded <= 0) { if (numResultsNeeded <= 0) {
return callback(new Error('Header Service: block service is mis-aligned ')); return callback('Header Service: block service is mis-aligned ');
} }
var startingHeight = tip.height + 1; var startingHeight = tip.height + 1;
@ -922,8 +964,11 @@ HeaderService.prototype.getEndHash = function(tip, blockCount, callback) {
if (streamErr) { if (streamErr) {
return streamErr; return streamErr;
} }
// FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
assert(results.length === numResultsNeeded, 'getEndHash returned incorrect number of results.'); if (results.length != numResultsNeeded) {
log.error('getEndHash returned incorrect number of results.');
}
// assert(results.length === numResultsNeeded, 'getEndHash returned incorrect number of results.');
var index = numResultsNeeded - 1; var index = numResultsNeeded - 1;
var endHash = index <= 0 || !results[index] ? 0 : results[index]; var endHash = index <= 0 || !results[index] ? 0 : results[index];
@ -941,7 +986,11 @@ HeaderService.prototype.getEndHash = function(tip, blockCount, callback) {
}; };
HeaderService.prototype.getLastHeader = function() { HeaderService.prototype.getLastHeader = function() {
assert(this._lastHeader, 'Last header should be populated.'); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (this._lastHeader == false) {
log.error('Last header should be populated.'); return;
}
// assert(this._lastHeader, 'Last header should be populated.');
return this._lastHeader; return this._lastHeader;
}; };
@ -999,7 +1048,13 @@ HeaderService.prototype._adjustHeadersForCheckPointTip = function(callback) {
return streamErr; return streamErr;
} }
assert(self._lastHeader, 'The last synced header was not in the database.');
// FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (self._lastHeader == false) {
log.error('The last synced header was not in the database.');
}
//assert(self._lastHeader, 'The last synced header was not in the database.');
self._tip.hash = self._lastHeader.hash; self._tip.hash = self._lastHeader.hash;
self._tip.height = self._lastHeader.height; self._tip.height = self._lastHeader.height;
self._db.batch(removalOps, callback); self._db.batch(removalOps, callback);

View File

@ -425,7 +425,12 @@ P2P.prototype._setListeners = function() {
P2P.prototype._setResourceFilter = function(filter) { P2P.prototype._setResourceFilter = function(filter) {
assert(filter && filter.startHash, 'A "startHash" field is required to retrieve headers or blocks'); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (filter == false || filter.startHash == false) {
log.error('A "startHash" field is required to retrieve headers or blocks');
}
// assert(filter && filter.startHash, 'A "startHash" field is required to retrieve headers or blocks');
if (!filter.endHash) { if (!filter.endHash) {
filter.endHash = 0; filter.endHash = 0;
} }

View File

@ -7,6 +7,7 @@ var _ = require('lodash');
var async = require('async'); var async = require('async');
var assert = require('assert'); var assert = require('assert');
var LRU = require('lru-cache'); var LRU = require('lru-cache');
var log = require('../../index').log;
function TransactionService(options) { function TransactionService(options) {
BaseService.call(this, options); BaseService.call(this, options);
@ -113,7 +114,7 @@ TransactionService.prototype.getTransaction = function(txid, options, callback)
var self = this; var self = this;
if (typeof callback !== 'function') { if (!_.isFunction(callback)) {
callback = options; callback = options;
} }
@ -184,8 +185,11 @@ TransactionService.prototype.setTxMetaInfo = function(tx, options, callback) {
var inputSatoshis = 0; var inputSatoshis = 0;
assert(tx.__inputValues.length === tx.inputs.length, // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
'Transaction Service: input values length is not the same as the number of inputs.'); if (tx.__inputValues.length != tx.inputs.length) {
log.error('Transaction Service: input values length is not the same as the number of inputs.');
}
// assert(tx.__inputValues.length === tx.inputs.length, 'Transaction Service: input values length is not the same as the number of inputs.');
tx.__inputValues.forEach(function(val) { tx.__inputValues.forEach(function(val) {
@ -308,14 +312,18 @@ TransactionService.prototype._getInputValues = function(tx, options, callback) {
// if not in mempool or tx index, we just don't have it, yet? // if not in mempool or tx index, we just don't have it, yet?
function(txid, tx, next) { function(txid, tx, next) {
if (!tx) { if (!tx) {
return next(new Error('Transaction Service: prev transacion: (' + input.prevout.txid() + ') for tx: ' + return next(log.error('Transaction Service: prev transacion: (' + input.prevout.txid() + ') for tx: ' +
_tx.txid() + ' at input index: ' + outputIndex + ' is missing from the index or not in the memory pool. It could be' + _tx.txid() + ' at input index: ' + outputIndex + ' is missing from the index or not in the memory pool. It could be' +
' that the parent tx has not yet been relayed to us, but will be relayed in the near future.')); ' that the parent tx has not yet been relayed to us, but will be relayed in the near future.'));
} }
var output = tx.outputs[outputIndex]; var output = tx.outputs[outputIndex];
assert(output, 'Expected an output, but did not get one for tx: ' + tx.txid() + ' outputIndex: ' + outputIndex); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (output == false) {
log.error('Expected an output, but did not get one for tx: ' + tx.txid() + ' outputIndex: ' + outputIndex);
}
// assert(output, 'Expected an output, but did not get one for tx: ' + tx.txid() + ' outputIndex: ' + outputIndex);
next(null, output.value); next(null, output.value);
} }
@ -375,7 +383,11 @@ TransactionService.prototype.onBlock = function(block, callback) {
return callback(err); return callback(err);
} }
assert(block.txs.length === operations.length, 'It seems we are not indexing the correct number of transactions.'); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (block.txs.length != operations.length) {
log.error('It seems we are not indexing the correct number of transactions.');
}
// assert(block.txs.length === operations.length, 'It seems we are not indexing the correct number of transactions.');
callback(null, _.flattenDeep(operations)); callback(null, _.flattenDeep(operations));
}); });
@ -477,19 +489,30 @@ TransactionService.prototype._processTransaction = function(tx, opts, callback)
return callback(err); return callback(err);
} }
assert(inputValues && inputValues.length === tx.inputs.length, // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
'Input values missing from tx.'); if (inputValues == false || inputValues.length != tx.inputs.length) {
log.error('Input values missing from tx.');
}
// assert(inputValues && inputValues.length === tx.inputs.length, 'Input values missing from tx.');
// inputValues // inputValues
tx.__inputValues = inputValues; tx.__inputValues = inputValues;
// timestamp // timestamp
tx.__timestamp = self._getBlockTimestamp(opts.block.rhash()); tx.__timestamp = self._getBlockTimestamp(opts.block.rhash());
assert(tx.__timestamp, 'Timestamp is required when saving a transaction.'); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (tx.__timestamp == false) {
log.error('Timestamp is required when saving a transaction.');
}
// assert(tx.__timestamp, 'Timestamp is required when saving a transaction.');
// height // height
tx.__height = opts.block.__height; tx.__height = opts.block.__height;
assert(tx.__height, 'Block height is required when saving a trasnaction.'); // FLOSight Error Correction from RanchiMall 17th May 2021. removed the unhandled assert and replaced by looging of error
if (tx.__height == false) {
log.error('Block height is required when saving a trasnaction.');
}
//assert(tx.__height, 'Block height is required when saving a trasnaction.');
// block hash // block hash
tx.__blockhash = opts.block.rhash(); tx.__blockhash = opts.block.rhash();

View File

@ -4,6 +4,7 @@ var fs = require('fs');
var http = require('http'); var http = require('http');
var https = require('https'); var https = require('https');
var express = require('express'); var express = require('express');
var express_ws = require('express-ws');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var socketio = require('socket.io'); var socketio = require('socket.io');
var inherits = require('util').inherits; var inherits = require('util').inherits;
@ -105,7 +106,7 @@ WebService.prototype.setupAllRoutes = function() {
if(service.getRoutePrefix && service.setupRoutes) { if(service.getRoutePrefix && service.setupRoutes) {
this.app.use('/' + this.node.services[key].getRoutePrefix(), subApp); this.app.use('/' + this.node.services[key].getRoutePrefix(), subApp);
this.node.services[key].setupRoutes(subApp, express); this.node.services[key].setupRoutes(subApp, express, express_ws);
} else { } else {
log.debug('No routes defined for: ' + key); log.debug('No routes defined for: ' + key);
} }

View File

@ -5,12 +5,12 @@
"node": ">=8.0.0" "node": ">=8.0.0"
}, },
"author": "BitPay <dev@bitpay.com>", "author": "BitPay <dev@bitpay.com>",
"version": "5.0.8", "version": "5.0.9-beta-rm",
"main": "./index.js", "main": "./index.js",
"repository": "git://github.com/oipwg/flocore-node.git", "repository": "git://github.com/ranchimall/flocore-node.git",
"homepage": "https://github.com/oipwg/flocore-node", "homepage": "https://github.com/ranchimall/flocore-node",
"bugs": { "bugs": {
"url": "https://github.com/oipwg/flocore-node/issues" "url": "https://github.com/ranchimall/flocore-node/issues"
}, },
"bin": { "bin": {
"flocore-node": "./bin/flocore-node" "flocore-node": "./bin/flocore-node"
@ -36,13 +36,14 @@
"commander": "^2.8.1", "commander": "^2.8.1",
"errno": "^0.1.4", "errno": "^0.1.4",
"express": "^4.13.3", "express": "^4.13.3",
"express-ws": "^5.0.2",
"fcoin": "^1.1.4", "fcoin": "^1.1.4",
"flocore-lib": "^0.15.2", "flocore-lib": "^0.15.2",
"flocore-message": "^1.0.7", "flocore-message": "^1.0.7",
"flocore-p2p": "^5.0.0-beta.8", "flocore-p2p": "^5.0.0-beta.8",
"florincoind-rpc": "0.7.1", "florincoind-rpc": "0.7.1",
"flosight-api": "^5.0.0-beta.75", "flosight-api": "github:ranchimall/flosight-api",
"flosight-ui": "^5.0.0-beta.72", "flosight-ui": "github:ranchimall/flosight-ui",
"leveldown": "^2.0.0", "leveldown": "^2.0.0",
"levelup": "^2.0.0", "levelup": "^2.0.0",
"liftoff": "^2.2.0", "liftoff": "^2.2.0",