Improved block sync speed

-A number of functions have been rewritten to be more optimized and faster: calculate_total, is_unique, convert_to_satoshi, get_input_addresses, processVoutAddresses, prepare_vout, prepare_vin
-Txes are now written to database via bulk writes which helps improve the sync speed and also controls memory usage with batching to write data once a certain threshold is reached
-update_address function changed to update_addresses since it now bulk writes the addresses in batches to improve sync speed and also controls memory usage with batching to write data once a certain threshold is reached
-The syncLoop function has been completely removed from the project and replaced with async library loops or even normal "for" loops in some cases which greatly improves sync speeds over large batches of data
-Fixed an issue with the flattened count of txes that is saved to the coinstats collection which could save incorrectly when using more than 1 thread
-Fixed an issue with the block sync which caused an unwanted delay when syncing less blocks than the amount of threads used to sync the data
-Fixed an issue with vout data processing that could sometimes populate data out of order
-Added a new sync.batch_size setting used to determine how many records (txes, addresses, addresstxes) should be saved in a single database transaction
-Added a new wait_for_bulk_database_save setting used to increase the block sync speed at the cost of not returning any error msgs for data that failed to save
-get_input_addresses function no longer returns in the exports section of the explorer.js file since it is only referenced in that file
-Updated explorerspec tests to use the newest function changes for any tests that needed to be updated

Special thanks to Karzo from Pepecoin for help with the bulkwrite code changes!
This commit is contained in:
Joe Uhren
2025-02-02 19:10:17 -07:00
parent 0b0ef817f1
commit 3a2f679201
10 changed files with 966 additions and 867 deletions
+284 -397
View File
@@ -1,10 +1,9 @@
var request = require('postman-request'),
async = require('async'),
settings = require('./settings'),
Address = require('../models/address');
var base_server = 'http://127.0.0.1:' + settings.webserver.port + "/";
var base_url = base_server + 'api/';
const request = require('postman-request');
const async = require('async');
const settings = require('./settings');
const Address = require('../models/address');
const base_server = 'http://127.0.0.1:' + settings.webserver.port + '/';
const base_url = base_server + 'api/';
const onode = require('./node');
const client = new onode.Client(settings.wallet);
@@ -76,7 +75,7 @@ function convertHashUnits(hashes) {
}
}
function processVoutAddresses(address_list, vout_value, arr_vout, cb) {
function processVoutAddresses(address_list, vout_value, arr_vout) {
// check if there are any addresses to process
if (address_list != null && address_list.length > 0) {
// check if vout address is inside an array
@@ -85,28 +84,21 @@ function processVoutAddresses(address_list, vout_value, arr_vout, cb) {
address_list[0] = address_list[0][0];
}
const amount_sat = module.exports.convert_to_satoshi(parseFloat(vout_value));
const index = module.exports.is_unique(arr_vout, address_list[0], 'addresses');
// check if vout address is unique, if so add to array, if not add its amount to existing index
module.exports.is_unique(arr_vout, address_list[0], 'addresses', function(unique, index) {
if (unique == true) {
// unique vout
module.exports.convert_to_satoshi(parseFloat(vout_value), function(amount_sat) {
arr_vout.push({addresses: address_list[0], amount: amount_sat});
return cb(arr_vout);
});
} else {
// already exists
module.exports.convert_to_satoshi(parseFloat(vout_value), function(amount_sat) {
arr_vout[index].amount = arr_vout[index].amount + amount_sat;
return cb(arr_vout);
});
}
});
} else {
// no address, move to next vout
return cb(arr_vout);
if (index == -1) {
// unique vout
arr_vout.push({addresses: address_list[0], amount: amount_sat});
} else {
// already exists
arr_vout[index].amount += amount_sat;
}
}
// return the list of addresses
return arr_vout;
}
function encodeP2PKaddress(p2pk_descriptor, cb) {
@@ -182,12 +174,146 @@ function normalizeMasternodeCount(raw_count) {
}
}
function get_input_addresses(input, vout, cb) {
let addresses = [];
if (input.coinbase) {
let amount = 0;
for (let i = 0; i < vout.length; i++)
amount += parseFloat(vout[i].value);
addresses.push({ hash: 'coinbase', amount: amount });
return cb(addresses, null);
} else {
module.exports.get_rawtransaction(input.txid, function(tx) {
if (tx) {
let tx_type = null;
async.eachSeries(tx.vout, function(current_vout, loop) {
if (current_vout.n == input.vout) {
if (current_vout.scriptPubKey.addresses || current_vout.scriptPubKey.address) {
let new_address = current_vout.scriptPubKey.address || current_vout.scriptPubKey.addresses[0];
// check if address is inside an array
if (Array.isArray(new_address)) {
// extract the address
new_address = new_address[0];
}
const index = module.exports.is_unique(addresses, new_address, 'hash');
if (index == -1)
addresses.push({hash: new_address, amount: current_vout.value});
else
addresses[index].amount += current_vout.value;
loop();
} else {
// no addresses defined
// check if bitcoin features are enabled
if (settings.blockchain_specific.bitcoin.enabled == true) {
// assume the asm value is a P2PK (Pay To Pubkey) public key that should be encoded as a P2PKH (Pay To Pubkey Hash) address
encodeP2PKaddress(current_vout.scriptPubKey.asm, function(p2pkh_address) {
// check if the address was encoded properly
if (p2pkh_address != null) {
// mark this tx as p2pk
tx_type = 'p2pk';
// check if address is inside an array
if (Array.isArray(p2pkh_address)) {
// extract the address
p2pkh_address = p2pkh_address[0];
}
// save the P2PKH address
const index = module.exports.is_unique(addresses, p2pkh_address, 'hash');
if (index == -1)
addresses.push({hash: p2pkh_address, amount: current_vout.value});
else
addresses[index].amount += current_vout.value;
loop();
} else {
// could not decipher the address, save as unknown and move to next vin
console.log('Failed to find vin address from tx ' + input.txid);
const index = module.exports.is_unique(addresses, 'unknown_address', 'hash');
if (index == -1)
addresses.push({hash: 'unknown_address', amount: current_vout.value});
else
addresses[index].amount += current_vout.value;
loop();
}
});
} else {
// could not decipher the address, save as unknown and move to next vin
console.log('Failed to find vin address from tx ' + input.txid);
const index = module.exports.is_unique(addresses, 'unknown_address', 'hash');
if (index == -1)
addresses.push({hash: 'unknown_address', amount: current_vout.value});
else
addresses[index].amount += current_vout.value;
loop();
}
}
} else
loop();
}, function() {
return cb(addresses, tx_type);
});
} else
return cb();
});
}
}
function finalize_vout(first_vout, arr_vout, arr_vin, tx_type, txid, cb) {
if (typeof first_vout !== 'undefined' && first_vout.scriptPubKey.type == 'nonstandard') {
if (arr_vin.length > 0 && arr_vout.length > 0) {
if (arr_vin[0].addresses == arr_vout[0].addresses) {
// pos
arr_vout[0].amount -= arr_vin[0].amount;
arr_vin.shift();
// check if any vin remains
if (arr_vin == null || arr_vin.length == 0) {
// empty vin should be linked to coinbase
arr_vin = [{coinbase: 'coinbase'}];
let new_vout = [];
// loop through the arr_vout to create a copy of the data with coin amounts only for use with prepare_vin()
for (i = 0; i < arr_vout.length; i++)
new_vout.push({ value: arr_vout[i].amount / 100000000 });
// call the prepare_vin again to populate the vin data correctly
module.exports.prepare_vin({ txid: txid, vin: arr_vin, vout: new_vout}, function(return_vin, return_tx_type_vin) {
return cb(arr_vout, return_vin, return_tx_type_vin);
});
} else
return cb(arr_vout, arr_vin, tx_type);
} else
return cb(arr_vout, arr_vin, tx_type);
} else
return cb(arr_vout, arr_vin, tx_type);
} else
return cb(arr_vout, arr_vin, tx_type);
}
module.exports = {
convert_to_satoshi: function(amount, cb) {
// fix to 8dp & convert to string
var fixed = amount.toFixed(8).toString();
convert_to_satoshi: function(amount) {
// fix to 8 decimals and convert to string
const fixed = amount.toFixed(8).toString();
// remove decimal (.) and return integer
return cb(parseInt(fixed.replace('.', '')));
return parseInt(fixed.replace('.', ''));
},
get_hashrate: function(cb) {
@@ -810,77 +936,13 @@ module.exports = {
}
},
// synchonous loop used to interate through an array,
// avoid use unless absolutely neccessary
syncLoop: function(iterations, process, exit) {
var index = 0,
done = false,
shouldExit = false;
var loop = {
next: function() {
if (done) {
if (shouldExit && exit) {
// exit if we're done
exit();
}
// stop the loop if we're done
return;
}
// if we're not finished
if (index < iterations) {
// increment our index
index++;
if (index % 100 === 0) {
// clear stack
setTimeout(function() {
// run our process, pass in the loop
process(loop);
}, 1);
} else {
// run our process, pass in the loop
process(loop);
}
} else {
// otherwise we're done
// make sure we say we're done
done = true;
if (exit) {
// call the callback on exit
exit();
}
}
},
iteration: function() {
// return the loop number we're on
return index - 1;
},
break: function(end) {
// end the loop
done = true;
// passing end as true means we still call the exit callback
shouldExit = end;
}
};
loop.next();
return loop;
},
balance_supply: function(cb) {
Address.find({}, 'balance').where('balance').gt(0).exec().then((docs) => {
var count = 0;
let count = 0;
module.exports.syncLoop(docs.length, function (loop) {
var i = loop.iteration();
count = count + docs[i].balance;
loop.next();
async.eachSeries(docs, function(current_doc, loop) {
count += current_doc.balance;
loop();
}, function() {
return cb(count);
});
@@ -1080,222 +1142,160 @@ module.exports = {
});
},
is_unique: function(array, object, key_name, cb) {
var unique = true;
var index = null;
is_unique: function(array, object, key_name) {
for (let i = 0; i < array.length; i++) {
if (array[i][key_name] === object)
return i;
}
module.exports.syncLoop(array.length, function (loop) {
var i = loop.iteration();
if (array[i][key_name] == object) {
unique = false;
index = i;
loop.break(true);
loop.next();
} else
loop.next();
}, function() {
return cb(unique, index);
});
return -1;
},
calculate_total: function(vout, cb) {
var total = 0;
calculate_total: function(vout) {
let total = 0;
module.exports.syncLoop(vout.length, function (loop) {
var i = loop.iteration();
for (let i = 0; i < vout.length; i++)
total += vout[i].amount;
total = total + vout[i].amount;
loop.next();
}, function() {
return cb(total);
});
return total;
},
prepare_vout: function(vout, txid, vin, vhidden, cb) {
var arr_vout = [];
var arr_vin = vin;
var tx_type = null;
let arr_vout = [];
let arr_vin = vin;
let tx_type = null;
try {
module.exports.syncLoop(vout.length, function (loop) {
var i = loop.iteration();
async.eachSeries(vout, function(current_vout, loop) {
// make sure vout has an address
if (vout[i].scriptPubKey.type != 'nonstandard' && vout[i].scriptPubKey.type != 'nulldata') {
if (current_vout.scriptPubKey.type != 'nonstandard' && current_vout.scriptPubKey.type != 'nulldata') {
// check if this is a zerocoin tx
if (vout[i].scriptPubKey.type != 'zerocoinmint') {
var address_list = vout[i].scriptPubKey.addresses;
if (current_vout.scriptPubKey.type != 'zerocoinmint') {
const address_list = current_vout.scriptPubKey.addresses;
// check if there are one or more addresses in the vout
if (address_list == null || address_list.length == 0) {
// no addresses defined
// check if there is a single address defined
if (vout[i].scriptPubKey.address == null) {
if (current_vout.scriptPubKey.address == null) {
// no single address defined
// check if bitcoin features are enabled
if (settings.blockchain_specific.bitcoin.enabled == true) {
// assume the asm value is a P2PK (Pay To Pubkey) public key that should be encoded as a P2PKH (Pay To Pubkey Hash) address
encodeP2PKaddress(vout[i].scriptPubKey.asm, function(p2pkh_address) {
encodeP2PKaddress(current_vout.scriptPubKey.asm, function(p2pkh_address) {
// check if the address was encoded properly
if (p2pkh_address != null) {
// mark this tx as p2pk
tx_type = 'p2pk';
// process vout addresses
processVoutAddresses(p2pkh_address, vout[i].value, arr_vout, function(vout_array) {
// save updated array
arr_vout = vout_array;
// move to next vout
loop.next();
});
// process vout addresses and save the updated array
arr_vout = processVoutAddresses(p2pkh_address, current_vout.value, arr_vout);
} else {
// could not decipher the address, save as unknown and move to next vout
// could not decipher the address, save as unknown
console.log('Failed to find vout address from tx ' + txid);
// process vout addresses
processVoutAddresses(['unknown_address'], vout[i].value, arr_vout, function(vout_array) {
// save updated array
arr_vout = vout_array;
// move to next vout
loop.next();
});
// process vout addresses and save the updated array
arr_vout = processVoutAddresses(['unknown_address'], current_vout.value, arr_vout);
}
// move to next vout
loop();
});
} else {
// could not decipher the address, save as unknown and move to next vout
console.log('Failed to find vout address from tx ' + txid);
// process vout addresses
processVoutAddresses(['unknown_address'], vout[i].value, arr_vout, function(vout_array) {
// save updated array
arr_vout = vout_array;
// move to next vout
loop.next();
});
// process vout addresses and save the updated array
arr_vout = processVoutAddresses(['unknown_address'], current_vout.value, arr_vout);
// move to next vout
loop();
}
} else {
// process vout address
processVoutAddresses([vout[i].scriptPubKey.address], vout[i].value, arr_vout, function(vout_array) {
// save updated array
arr_vout = vout_array;
// move to next vout
loop.next();
});
// process vout addresses and save the updated array
arr_vout = processVoutAddresses([current_vout.scriptPubKey.address], current_vout.value, arr_vout);
// move to next vout
loop();
}
} else {
// process vout addresses
processVoutAddresses(address_list, vout[i].value, arr_vout, function(vout_array) {
// save updated array
arr_vout = vout_array;
// move to next vout
loop.next();
});
// process vout addresses and save the updated array
arr_vout = processVoutAddresses(address_list, current_vout.value, arr_vout);
// move to next vout
loop();
}
} else {
// TODO: add support for zerocoin transactions
console.log('Zerocoin tx found. skipping for now as it is unsupported');
tx_type = "zerocoin";
loop.next();
loop();
}
} else {
// no address, move to next vout
loop.next();
loop();
}
}, function() {
// check if zksnarks is enabled
if (settings.blockchain_specific.zksnarks.enabled == true) {
// check for hidden/anonymous outputs
if (vhidden != null && vhidden.length > 0) {
tx_type = "zksnarks";
// loop through all hidden/anonymous outputs
module.exports.syncLoop(vhidden.length, function (loop) {
var i = loop.iteration();
// check if zksnarks is enabled and there are any hidden/anonymous outputs
if (settings.blockchain_specific.zksnarks.enabled == true && vhidden != null && vhidden.length > 0) {
const hidden_address = 'hidden_address';
tx_type = 'zksnarks';
if (vhidden[i].vpub_old > 0) {
// process vout addresses
processVoutAddresses(['hidden_address'], parseFloat(vhidden[i].vpub_old), arr_vout, function(vout_array) {
// save updated array
arr_vout = vout_array;
// move to next vout
loop.next();
});
} else {
if ((!vout || vout.length == 0) && (!vin || vin.length == 0)) {
// hidden sender is sending to hidden recipient
// the sent and received values are not known in this case. only the fee paid is known and subtracted from the sender.
// process vout addresses
processVoutAddresses(['hidden_address'], 0, arr_vout, function(vout_array) {
// save updated array
arr_vout = vout_array;
// add a private send address with the known amount sent
module.exports.is_unique(arr_vin, 'hidden_address', 'addresses', function(unique, index) {
if (unique == true) {
module.exports.convert_to_satoshi(parseFloat(vhidden[i].vpub_new), function(amount_sat) {
arr_vin.push({addresses: 'hidden_address', amount: amount_sat});
// move to next vout
loop.next();
});
} else {
module.exports.convert_to_satoshi(parseFloat(vhidden[i].vpub_new), function(amount_sat) {
arr_vin[index].amount = arr_vin[index].amount + amount_sat;
// move to next vout
loop.next();
});
}
});
});
// loop through all hidden/anonymous outputs
async.eachSeries(vhidden, function(current_vhidden, loop) {
if (current_vhidden.vpub_old > 0) {
// process vout addresses and save the updated array
arr_vout = processVoutAddresses([hidden_address], parseFloat(current_vhidden.vpub_old), arr_vout);
// move to next vout
loop();
} else {
const amount_sat = module.exports.convert_to_satoshi(parseFloat(current_vhidden.vpub_new));
const index = module.exports.is_unique(arr_vin, hidden_address, 'addresses');
if ((!vout || vout.length == 0) && (!vin || vin.length == 0)) {
// hidden sender is sending to hidden recipient
// the sent and received values are not known in this case. only the fee paid is known and subtracted from the sender.
// process vout addresses and save the updated array
arr_vout = processVoutAddresses([hidden_address], 0, arr_vout);
// add a private send address with the known amount sent
if (index == -1) {
// add hidden address to array
arr_vin.push({addresses: hidden_address, amount: amount_sat});
} else {
module.exports.is_unique(arr_vin, 'hidden_address', 'addresses', function(unique, index) {
if (unique == true) {
module.exports.convert_to_satoshi(parseFloat(vhidden[i].vpub_new), function(amount_sat) {
arr_vin.push({addresses: 'hidden_address', amount: amount_sat});
// move to next vout
loop.next();
});
} else {
module.exports.convert_to_satoshi(parseFloat(vhidden[i].vpub_new), function(amount_sat) {
arr_vin[index].amount = arr_vin[index].amount + amount_sat;
// move to next vout
loop.next();
});
}
});
}
}
});
}
}
if (typeof vout[0] !== 'undefined' && vout[0].scriptPubKey.type == 'nonstandard') {
if (arr_vin.length > 0 && arr_vout.length > 0) {
if (arr_vin[0].addresses == arr_vout[0].addresses) {
//PoS
arr_vout[0].amount = arr_vout[0].amount - arr_vin[0].amount;
arr_vin.shift();
// check if any vin remains
if (arr_vin == null || arr_vin.length == 0) {
// empty vin should be linked to coinbase
arr_vin = [{coinbase: "coinbase"}];
var new_vout = [];
// loop through the arr_vout to create a copy of the data with coin amounts only for use with prepare_vin()
for (i = 0; i < arr_vout.length; i++) {
new_vout.push({
value: arr_vout[i].amount / 100000000
});
// update hidden address amount in array
arr_vin[index].amount = arr_vin[index].amount + amount_sat;
}
// call the prepare_vin again to populate the vin data correctly
module.exports.prepare_vin({txid: txid, vin: arr_vin, vout: new_vout}, function(return_vin, return_tx_type_vin) {
return cb(arr_vout, return_vin, return_tx_type_vin);
});
// move to next vout
loop();
} else {
return cb(arr_vout, arr_vin, tx_type);
// add a private send address with the known amount sent
if (index == -1) {
// add hidden address to array
arr_vin.push({addresses: hidden_address, amount: amount_sat});
} else {
// update hidden address amount in array
arr_vin[index].amount += amount_sat;
}
// move to next vout
loop();
}
} else
return cb(arr_vout, arr_vin, tx_type);
} else
return cb(arr_vout, arr_vin, tx_type);
} else
return cb(arr_vout, arr_vin, tx_type);
}
}, function() {
// finished updating hidden vout data
finalize_vout(vout[0], arr_vout, arr_vin, tx_type, txid, function(final_arr_vout, final_arr_vin, final_tx_type) {
return cb(final_arr_vout, final_arr_vin, final_tx_type);
});
});
} else {
// finalize vout data
finalize_vout(vout[0], arr_vout, arr_vin, tx_type, txid, function(final_arr_vout, final_arr_vin, final_tx_type) {
return cb(final_arr_vout, final_arr_vin, final_tx_type);
});
}
});
} catch(err) {
// check if a "Maximum call stack size exceeded" error occurred
@@ -1309,122 +1309,12 @@ module.exports = {
}
},
get_input_addresses: function(input, vout, cb) {
var addresses = [];
if (input.coinbase) {
var amount = 0;
module.exports.syncLoop(vout.length, function (loop) {
var i = loop.iteration();
amount = amount + parseFloat(vout[i].value);
loop.next();
}, function() {
addresses.push({hash: 'coinbase', amount: amount});
return cb(addresses, null);
});
} else {
module.exports.get_rawtransaction(input.txid, function(tx) {
if (tx) {
var tx_type = null;
module.exports.syncLoop(tx.vout.length, function (loop) {
var i = loop.iteration();
if (tx.vout[i].n == input.vout) {
if (tx.vout[i].scriptPubKey.addresses || tx.vout[i].scriptPubKey.address) {
var new_address = tx.vout[i].scriptPubKey.address || tx.vout[i].scriptPubKey.addresses[0];
// check if address is inside an array
if (Array.isArray(new_address)) {
// extract the address
new_address = new_address[0];
}
module.exports.is_unique(addresses, new_address, 'hash', function(unique, index) {
if (unique == true)
addresses.push({hash: new_address, amount: tx.vout[i].value});
else
addresses[index].amount = addresses[index].amount + tx.vout[i].value;
loop.break(true);
loop.next();
});
} else {
// no addresses defined
// check if bitcoin features are enabled
if (settings.blockchain_specific.bitcoin.enabled == true) {
// assume the asm value is a P2PK (Pay To Pubkey) public key that should be encoded as a P2PKH (Pay To Pubkey Hash) address
encodeP2PKaddress(tx.vout[i].scriptPubKey.asm, function(p2pkh_address) {
// check if the address was encoded properly
if (p2pkh_address != null) {
// mark this tx as p2pk
tx_type = 'p2pk';
// check if address is inside an array
if (Array.isArray(p2pkh_address)) {
// extract the address
p2pkh_address = p2pkh_address[0];
}
// save the P2PKH address
module.exports.is_unique(addresses, p2pkh_address, 'hash', function(unique, index) {
if (unique == true)
addresses.push({hash: p2pkh_address, amount: tx.vout[i].value});
else
addresses[index].amount = addresses[index].amount + tx.vout[i].value;
loop.break(true);
loop.next();
});
} else {
// could not decipher the address, save as unknown and move to next vin
console.log('Failed to find vin address from tx ' + input.txid);
module.exports.is_unique(addresses, 'unknown_address', 'hash', function(unique, index) {
if (unique == true)
addresses.push({hash: 'unknown_address', amount: tx.vout[i].value});
else
addresses[index].amount = addresses[index].amount + tx.vout[i].value;
loop.break(true);
loop.next();
});
}
});
} else {
// could not decipher the address, save as unknown and move to next vin
console.log('Failed to find vin address from tx ' + input.txid);
module.exports.is_unique(addresses, 'unknown_address', 'hash', function(unique, index) {
if (unique == true)
addresses.push({hash: 'unknown_address', amount: tx.vout[i].value});
else
addresses[index].amount = addresses[index].amount + tx.vout[i].value;
loop.break(true);
loop.next();
});
}
}
} else
loop.next();
}, function() {
return cb(addresses, tx_type);
});
} else
return cb();
});
}
},
prepare_vin: function(tx, cb) {
var arr_vin = [];
var tx_type = null;
let arr_vin = [];
let tx_type = null;
module.exports.syncLoop(tx.vin.length, function (loop) {
var i = loop.iteration();
module.exports.get_input_addresses(tx.vin[i], tx.vout, function(addresses, tx_type_vin) {
async.eachSeries(tx.vin, function(vin, loop) {
get_input_addresses(vin, tx.vout, function(addresses, tx_type_vin) {
// check if the tx type is set
if (tx_type_vin != null) {
// set the tx type return value
@@ -1432,28 +1322,25 @@ module.exports = {
}
if (addresses && addresses.length) {
module.exports.is_unique(arr_vin, addresses[0].hash, 'addresses', function(unique, index) {
if (unique == true) {
module.exports.convert_to_satoshi(parseFloat(addresses[0].amount), function(amount_sat) {
arr_vin.push({addresses: addresses[0].hash, amount: amount_sat});
loop.next();
});
} else {
module.exports.convert_to_satoshi(parseFloat(addresses[0].amount), function(amount_sat) {
arr_vin[index].amount = arr_vin[index].amount + amount_sat;
loop.next();
});
}
});
const amount_sat = module.exports.convert_to_satoshi(parseFloat(addresses[0].amount));
const index = module.exports.is_unique(arr_vin, addresses[0].hash, 'addresses');
if (index == -1)
arr_vin.push({addresses: addresses[0].hash, amount: amount_sat});
else
arr_vin[index].amount += amount_sat;
loop();
} else {
// could not decipher the address, save as unknown and move to next vin
console.log('Failed to find vin address from tx ' + tx.txid);
module.exports.is_unique(arr_vin, 'unknown_address', 'addresses', function(unique, index) {
if (unique == true)
arr_vin.push({addresses: 'unknown_address', amount: 0});
loop.next();
});
const index = module.exports.is_unique(arr_vin, 'unknown_address', 'addresses');
if (index == -1)
arr_vin.push({addresses: 'unknown_address', amount: 0});
loop();
}
});
}, function() {