Add support for bitcoin P2PK scripts/txes
-Adds a tx_type field to the tx model which is typically null for "normal" transaction types, but can also display 'p2pk' for bitcoin txes which require addtional encoding to reveal the P2PKH address as well as 'zksnarks' for transactions with hidden sender or receiver data -Additional fixes for how data is displayed when a valid wallet address cannot be found -Includes some small updates to how zksnarks transactions display hidden sender/receiver data
This commit is contained in:
+39
-16
@@ -10,6 +10,7 @@ var mongoose = require('mongoose'),
|
||||
Heavy = require('../models/heavy'),
|
||||
lib = require('./explorer'),
|
||||
settings = require('./settings'),
|
||||
locale = require('./locale'),
|
||||
fs = require('fs'),
|
||||
coindesk = require('./apis/coindesk'),
|
||||
async = require('async');
|
||||
@@ -115,8 +116,8 @@ function find_tx(txid, cb) {
|
||||
function save_tx(txid, blockheight, cb) {
|
||||
lib.get_rawtransaction(txid, function(tx) {
|
||||
if (tx && tx != 'There was an error. Check your console.') {
|
||||
lib.prepare_vin(tx, function(vin) {
|
||||
lib.prepare_vout(tx.vout, txid, vin, ((!settings.blockchain_specific.zksnarks.enabled || typeof tx.vjoinsplit === 'undefined' || tx.vjoinsplit == null) ? [] : tx.vjoinsplit), function(vout, nvin) {
|
||||
lib.prepare_vin(tx, function(vin, tx_type_vin) {
|
||||
lib.prepare_vout(tx.vout, txid, vin, ((!settings.blockchain_specific.zksnarks.enabled || typeof tx.vjoinsplit === 'undefined' || tx.vjoinsplit == null) ? [] : tx.vjoinsplit), function(vout, nvin, tx_type_vout) {
|
||||
lib.syncLoop(vin.length, function (loop) {
|
||||
var i = loop.iteration();
|
||||
|
||||
@@ -142,7 +143,8 @@ function save_tx(txid, blockheight, cb) {
|
||||
total: total.toFixed(8),
|
||||
timestamp: tx.time,
|
||||
blockhash: tx.blockhash,
|
||||
blockindex: blockheight
|
||||
blockindex: blockheight,
|
||||
tx_type: (tx_type_vout == null ? tx_type_vin : tx_type_vout)
|
||||
});
|
||||
|
||||
newTx.save(function(err) {
|
||||
@@ -172,6 +174,22 @@ function get_market_data(market, coin_symbol, pair_symbol, cb) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
function check_add_db_field(model_obj, field_name, default_value, cb) {
|
||||
// determine if a particular field exists in a db collection
|
||||
model_obj.findOne({[field_name]: {$exists: false}}, function(err, model_data) {
|
||||
// check if field exists
|
||||
if (model_data) {
|
||||
// add field to all documents in the collection
|
||||
model_obj.updateMany({}, {
|
||||
$set: { [field_name]: default_value }
|
||||
}, function() {
|
||||
return cb(true);
|
||||
});
|
||||
} else
|
||||
return cb(false);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// initialize DB
|
||||
connect: function(database, cb) {
|
||||
@@ -299,22 +317,27 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
check_txes: function(cb) {
|
||||
Tx.findOne({}, function(err, tx) {
|
||||
if (tx) {
|
||||
// collection has data
|
||||
// determine if tx_type field exists
|
||||
check_add_db_field(Tx, 'tx_type', null, function(exists) {
|
||||
return cb(true);
|
||||
});
|
||||
} else
|
||||
return cb(false);
|
||||
});
|
||||
},
|
||||
|
||||
check_stats: function(coin, cb) {
|
||||
Stats.findOne({coin: coin}, function(err, stats) {
|
||||
if (stats) {
|
||||
// collection exists, now check if it is missing the last_usd_price column
|
||||
Stats.findOne({last_usd_price: {$exists: false}}, function(err, stats) {
|
||||
if (stats) {
|
||||
// the last_usd_price needs to be added to the collection
|
||||
Stats.updateOne({coin: coin}, {
|
||||
last_usd_price: 0
|
||||
}, function() {
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
// collection has data
|
||||
// determine if last_usd_price field exists
|
||||
check_add_db_field(Stats, 'last_usd_price', 0, function(exists) {
|
||||
return cb(true);
|
||||
});
|
||||
|
||||
return cb(true);
|
||||
} else
|
||||
return cb(false);
|
||||
});
|
||||
@@ -366,7 +389,7 @@ module.exports = {
|
||||
var burn_addresses = settings.richlist_page.burned_coins.addresses;
|
||||
|
||||
// always omit the private address from the richlist
|
||||
burn_addresses.push("private_tx");
|
||||
burn_addresses.push('hidden_address');
|
||||
|
||||
if (list == 'received') {
|
||||
// update 'received' richlist data
|
||||
|
||||
+217
-29
@@ -1,5 +1,6 @@
|
||||
var request = require('postman-request'),
|
||||
settings = require('./settings'),
|
||||
locale = require('./locale'),
|
||||
Address = require('../models/address');
|
||||
|
||||
var base_server = 'http://127.0.0.1:' + settings.webserver.port + "/";
|
||||
@@ -68,6 +69,56 @@ function convertHashUnits(hashes) {
|
||||
}
|
||||
}
|
||||
|
||||
function processVoutAddresses(address_list, vout_value, arr_vout, cb) {
|
||||
// check if there are any addresses to process
|
||||
if (address_list != null && address_list.length > 0) {
|
||||
// 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], 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);
|
||||
}
|
||||
}
|
||||
|
||||
function encodeP2PKaddress(p2pk_descriptor, cb) {
|
||||
// find the descriptor value
|
||||
module.exports.get_descriptorinfo(p2pk_descriptor, function(descriptor_info) {
|
||||
// check for errors
|
||||
if (descriptor_info != null) {
|
||||
// encode the address using the output descriptor
|
||||
module.exports.get_deriveaddresses(descriptor_info.descriptor, function(p2pkh_address) {
|
||||
// check for errors
|
||||
if (p2pkh_address != null) {
|
||||
// return P2PKH address
|
||||
return cb(p2pkh_address);
|
||||
} else {
|
||||
// address could not be encoded
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// address could not be encoded
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
convert_to_satoshi: function(amount, cb) {
|
||||
// fix to 8dp & convert to string
|
||||
@@ -629,6 +680,67 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
get_descriptorinfo: function(descriptor, cb) {
|
||||
// format the descriptor correctly for use in the getdescriptorinfo cmd
|
||||
descriptor = 'pkh(' + descriptor.replace(' OP_CHECKSIG', '') + ')';
|
||||
|
||||
var cmd = prepareRpcCommand(settings.blockchain_specific.bitcoin.api_cmds.getdescriptorinfo, (descriptor ? [descriptor] : []));
|
||||
|
||||
if (!(cmd.method == '' && cmd.parameters.length == 0)) {
|
||||
if (settings.api_cmds.use_rpc) {
|
||||
rpcCommand([{method:cmd.method, parameters: cmd.parameters}], function(response) {
|
||||
// check if an error msg was received from the rpc server
|
||||
if (response == 'There was an error. Check your console.')
|
||||
return cb(null);
|
||||
else
|
||||
return cb(response);
|
||||
});
|
||||
} else {
|
||||
var uri = base_url + 'getdescriptorinfo?descriptor=' + encodeURIComponent(descriptor);
|
||||
|
||||
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
|
||||
// check if an error msg was received from the web api server
|
||||
if (body == 'There was an error. Check your console.')
|
||||
return cb(null);
|
||||
else
|
||||
return cb(body);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// cmd not in use. return null.
|
||||
return cb(null);
|
||||
}
|
||||
},
|
||||
|
||||
get_deriveaddresses: function(descriptor, cb) {
|
||||
var cmd = prepareRpcCommand(settings.blockchain_specific.bitcoin.api_cmds.deriveaddresses, (descriptor ? [descriptor] : []));
|
||||
|
||||
if (!(cmd.method == '' && cmd.parameters.length == 0)) {
|
||||
if (settings.api_cmds.use_rpc) {
|
||||
rpcCommand([{method:cmd.method, parameters: cmd.parameters}], function(response) {
|
||||
// check if an error msg was received from the rpc server
|
||||
if (response == 'There was an error. Check your console.')
|
||||
return cb(null);
|
||||
else
|
||||
return cb(response);
|
||||
});
|
||||
} else {
|
||||
var uri = base_url + 'deriveaddresses?descriptor=' + encodeURIComponent(descriptor);
|
||||
|
||||
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
|
||||
// check if an error msg was received from the web api server
|
||||
if (body == 'There was an error. Check your console.')
|
||||
return cb(null);
|
||||
else
|
||||
return cb(body);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// cmd not in use. return null.
|
||||
return cb(null);
|
||||
}
|
||||
},
|
||||
|
||||
// synchonous loop used to interate through an array,
|
||||
// avoid use unless absolutely neccessary
|
||||
syncLoop: function(iterations, process, exit) {
|
||||
@@ -903,32 +1015,63 @@ module.exports = {
|
||||
prepare_vout: function(vout, txid, vin, vhidden, cb) {
|
||||
var arr_vout = [];
|
||||
var arr_vin = vin;
|
||||
var tx_type = null;
|
||||
|
||||
module.exports.syncLoop(vout.length, function (loop) {
|
||||
var i = loop.iteration();
|
||||
// make sure vout has an address
|
||||
if (vout[i].scriptPubKey.type != 'nonstandard' && vout[i].scriptPubKey.type != 'nulldata') {
|
||||
// check if tx is public or private (private = if no out address)
|
||||
if (vout[i].scriptPubKey.type != 'zerocoinmint' && typeof vout[i].scriptPubKey.addresses != 'undefined') {
|
||||
// check if vout address is unique, if so add it array, if not add its amount to existing index
|
||||
module.exports.is_unique(arr_vout, vout[i].scriptPubKey.addresses[0], function(unique, index) {
|
||||
if (unique == true) {
|
||||
// unique vout
|
||||
module.exports.convert_to_satoshi(parseFloat(vout[i].value), function(amount_sat) {
|
||||
arr_vout.push({addresses: vout[i].scriptPubKey.addresses[0], amount: amount_sat});
|
||||
loop.next();
|
||||
// check if this is a zerocoin tx
|
||||
if (vout[i].scriptPubKey.type != 'zerocoinmint') {
|
||||
var address_list = vout[i].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 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) {
|
||||
// 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();
|
||||
});
|
||||
} else {
|
||||
// could not decipher the address, save as unknown and move to next vout
|
||||
module.exports.convert_to_satoshi(parseFloat(vout[i].value), function(amount_sat) {
|
||||
console.log('Failed to find vout address from tx ' + txid);
|
||||
arr_vout.push({addresses: 'unknown_address', amount: amount_sat});
|
||||
loop.next();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// already exists
|
||||
// could not decipher the address, save as unknown and move to next vout
|
||||
module.exports.convert_to_satoshi(parseFloat(vout[i].value), function(amount_sat) {
|
||||
arr_vout[index].amount = arr_vout[index].amount + amount_sat;
|
||||
console.log('Failed to find vout address from tx ' + txid);
|
||||
arr_vout.push({addresses: 'unknown_address', amount: amount_sat});
|
||||
loop.next();
|
||||
});
|
||||
}
|
||||
});
|
||||
} 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();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// private tx
|
||||
// TODO: save this data to be able to show an anon tx
|
||||
// TODO: add support for zerocoin transactions
|
||||
console.log('Zerocoin tx found. skipping for now as it is unsupported');
|
||||
tx_type = "zerocoin";
|
||||
loop.next();
|
||||
}
|
||||
} else {
|
||||
@@ -942,20 +1085,22 @@ module.exports = {
|
||||
vhidden.forEach(function(vanon, i) {
|
||||
if (vanon.vpub_old > 0) {
|
||||
module.exports.convert_to_satoshi(parseFloat(vanon.vpub_old), function(amount_sat) {
|
||||
arr_vout.push({addresses: "private_tx", amount: amount_sat});
|
||||
arr_vout.push({addresses: 'hidden_address', amount: amount_sat});
|
||||
});
|
||||
} else {
|
||||
module.exports.convert_to_satoshi(parseFloat(vanon.vpub_new), function(amount_sat) {
|
||||
if (vhidden.length > 0 && (!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.
|
||||
arr_vout.push({addresses: "private_tx", amount: 0});
|
||||
arr_vout.push({addresses: 'hidden_address', amount: 0});
|
||||
}
|
||||
|
||||
// add a private send address with the known amount sent
|
||||
arr_vin.push({addresses: "private_tx", amount: amount_sat});
|
||||
arr_vin.push({addresses: 'hidden_address', amount: amount_sat});
|
||||
});
|
||||
}
|
||||
|
||||
tx_type = "zksnarks";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -966,13 +1111,13 @@ module.exports = {
|
||||
arr_vout[0].amount = arr_vout[0].amount - arr_vin[0].amount;
|
||||
arr_vin.shift();
|
||||
|
||||
return cb(arr_vout, arr_vin);
|
||||
return cb(arr_vout, arr_vin, tx_type);
|
||||
} else
|
||||
return cb(arr_vout, arr_vin);
|
||||
return cb(arr_vout, arr_vin, tx_type);
|
||||
} else
|
||||
return cb(arr_vout, arr_vin);
|
||||
return cb(arr_vout, arr_vin, tx_type);
|
||||
} else
|
||||
return cb(arr_vout, arr_vin);
|
||||
return cb(arr_vout, arr_vin, tx_type);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -989,24 +1134,56 @@ module.exports = {
|
||||
loop.next();
|
||||
}, function() {
|
||||
addresses.push({hash: 'coinbase', amount: amount});
|
||||
return cb(addresses);
|
||||
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)
|
||||
if (tx.vout[i].scriptPubKey.addresses) {
|
||||
addresses.push({hash: tx.vout[i].scriptPubKey.addresses[0], amount:tx.vout[i].value});
|
||||
|
||||
loop.break(true);
|
||||
loop.next();
|
||||
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';
|
||||
// save the P2PKH address
|
||||
addresses.push({hash: p2pkh_address, amount: tx.vout[i].value});
|
||||
} else {
|
||||
// could not decipher the address, save as unknown and move to next vin
|
||||
addresses.push({hash: 'unknown_address', amount: tx.vout[i].value});
|
||||
console.log('Failed to find vin address from tx ' + input.txid);
|
||||
}
|
||||
|
||||
loop.break(true);
|
||||
loop.next();
|
||||
});
|
||||
} else {
|
||||
// could not decipher the address, save as unknown and move to next vin
|
||||
addresses.push({hash: 'unknown_address', amount: tx.vout[i].value});
|
||||
console.log('Failed to find vin address from tx ' + input.txid);
|
||||
|
||||
loop.break(true);
|
||||
loop.next();
|
||||
}
|
||||
}
|
||||
} else
|
||||
loop.next();
|
||||
}, function() {
|
||||
return cb(addresses);
|
||||
return cb(addresses, tx_type);
|
||||
});
|
||||
} else
|
||||
return cb();
|
||||
@@ -1016,11 +1193,18 @@ module.exports = {
|
||||
|
||||
prepare_vin: function(tx, cb) {
|
||||
var arr_vin = [];
|
||||
var 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) {
|
||||
module.exports.get_input_addresses(tx.vin[i], 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
|
||||
tx_type = tx_type_vin;
|
||||
}
|
||||
|
||||
if (addresses && addresses.length) {
|
||||
module.exports.is_unique(arr_vin, addresses[0].hash, function(unique, index) {
|
||||
if (unique == true) {
|
||||
@@ -1035,11 +1219,15 @@ module.exports = {
|
||||
});
|
||||
}
|
||||
});
|
||||
} else
|
||||
} 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);
|
||||
arr_vin.push({addresses: 'unknown_address', amount: 0});
|
||||
loop.next();
|
||||
}
|
||||
});
|
||||
}, function() {
|
||||
return cb(arr_vin);
|
||||
return cb(arr_vin, tx_type);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -61,8 +61,12 @@ exports.bits = "Bits",
|
||||
exports.nonce = "Nonce",
|
||||
exports.new_coins = "New Coins",
|
||||
exports.proof_of_stake = "PoS",
|
||||
exports.hidden_address = "Hidden Address",
|
||||
exports.hidden_sender = "Hidden Sender",
|
||||
exports.hidden_recipient = "Hidden Recipient",
|
||||
exports.unknown_address = "Unknown Address",
|
||||
exports.unknown_sender = "Unknown Sender",
|
||||
exports.unknown_recipient = "Unknown Recipient",
|
||||
exports.initial_index_alert = "Blockchain data is currently being synchronized. You may browse the site during this time, but keep in mind that data may not yet be fully accurate and some functionality may not work until synchronization is complete.",
|
||||
|
||||
exports.a_menu_showing = "Showing",
|
||||
|
||||
@@ -838,6 +838,22 @@ exports.api_cmds = {
|
||||
|
||||
// blockchain_specific: A collection of settings that pertain to non-standard blockchain features that can extend the functionality of the default explorer
|
||||
exports.blockchain_specific = {
|
||||
// bitcoin: A collection of settings that pertain to Bitcoin-specific scripts (P2PK support)
|
||||
"bitcoin": {
|
||||
// enabled: Enable/disable the use of bitcoin scripts in the explorer (true/false)
|
||||
// If set to false, all P2PK transactions will be saved without addresses as they require special encoding to reveal the more familiar P2PKH address
|
||||
// NOTE: Enabling this feature will require a full reindex of the blockchain data to fix any P2PK transactions that were previously not displaying addresses
|
||||
"enabled": false,
|
||||
//api_cmds: A collection of settings that pertain to the list of customizable bitcoin rpc api commands
|
||||
// Not all blockchains utilize the same rpc cmds for accessing the internal daemon api. Use these settings to set alternate names for similar api cmds.
|
||||
// Leaving a cmd value blank ( "" ) will completely disable use of that cmd.
|
||||
"api_cmds": {
|
||||
// getdescriptorinfo: Accepts a descriptor as input and returns an object with more detailed information, including its computed checksum
|
||||
"getdescriptorinfo": "getdescriptorinfo",
|
||||
// deriveaddresses: Accepts an output descriptor as input and returns an array containing one or more P2PKH addresses
|
||||
"deriveaddresses": "deriveaddresses"
|
||||
}
|
||||
},
|
||||
// heavycoin: A collection of settings that pertain to the democratic voting and reward capabilities of the heavycoin blockchain
|
||||
"heavycoin": {
|
||||
// enabled: Enable/disable the use of heavycoin features in the explorer (true/false)
|
||||
|
||||
Reference in New Issue
Block a user