Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show the spending transaction of spent outputs #356

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions app/api/electrumAddressApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,58 @@ function lookupTxBlockHash(txid) {
});
}

// Lookup the spending transaction and height of a given transaction output. Only works with Electrum 1.5 protocol, ElectRS 0.9.0 does not implement subscriptions
// but can return the transaction spending the given outpoint
function lookupOutpointsTx(outpoints) {
if (electrumClients.length == 0) {
return Promise.reject({ error: "Not supported by Electrum 1.4", userText: noConnectionsErrorText });
}

if (outpoints.length == 0) {
return Promise.resolve(null);
}
return runOnAllServers(function(electrumClient) {
return new Promise((resolve, reject) => {
let contents = [];
let arguments_far_calls = {};
for (let outpoint of outpoints) {
const id = ++electrumClient.id;
const request = utils.makeRequest('blockchain.outpoint.subscribe', outpoint, id);
contents.push(request);
arguments_far_calls[id] = outpoint;
}
const content = '[' + contents.join(',') + ']';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use string manipulation instead of native Json encoding?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The honest answer is that this is how it was made fore requestBatch in electrum-client lib x')

The write method for net.socket must take a string https://nodejs.org/api/net.html#net_socket_write_data_encoding_callback

const promise = utils.createPromiseResultBatch(resolve, reject, arguments_far_calls);
electrumClient.callback_message_queue[electrumClient.id] = promise;
electrumClient.conn.write(content + '\n');
});
}).then(function(results) {
runOnAllServers(function(electrumClient) {
return new Promise((resolve, reject) => {
let contents = [];
let arguments_far_calls = {};
for (let outpoint of outpoints) {
const id = ++electrumClient.id;
const request = utils.makeRequest('blockchain.outpoint.unsubscribe', outpoint, id);
contents.push(request);
arguments_far_calls[id] = outpoint;
}
const content = '[' + contents.join(',') + ']';
const promise = utils.createPromiseResultBatch(resolve, reject, arguments_far_calls);
electrumClient.callback_message_queue[electrumClient.id] = promise;
electrumClient.conn.write(content + '\n');
});
});
const spend_infos = results[0].result;
if (results.slice(1).every(({ result }) => result == spend_infos)) {
return spend_infos;
} else {
return Promise.reject({conflictedResults:results});
}
});
}


function logStats(cmd, dt, success) {
if (!global.electrumStats.rpc[cmd]) {
global.electrumStats.rpc[cmd] = {count:0, time:0, successes:0, failures:0};
Expand Down Expand Up @@ -380,5 +432,6 @@ module.exports = {
connectToServers: connectToServers,
getAddressDetails: getAddressDetails,
lookupTxBlockHash: lookupTxBlockHash,
lookupOutpointsTx: lookupOutpointsTx,
};

26 changes: 25 additions & 1 deletion app/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,28 @@ function bip32Addresses(extPubkey, addressType, account, limit=10, offset=0) {
return addresses;
}

function createPromiseResultBatch(resolve, reject, argz) {
return (err, result) => {
if (result && result[0] && result[0].id) {
// this is a batch request response
for (let r of result) {
r.param = argz[r.id];
}
}
if (err) reject(err);
else resolve(result);
};
};

function makeRequest(method, params, id) {
return JSON.stringify({
jsonrpc: '2.0',
method: method,
params: params,
id: id,
});
};

module.exports = {
reflectPromise: reflectPromise,
redirectToConnectPageIfNeeded: redirectToConnectPageIfNeeded,
Expand Down Expand Up @@ -1220,5 +1242,7 @@ module.exports = {
getVoutAddress: getVoutAddress,
getVoutAddresses: getVoutAddresses,
xpubChangeVersionBytes: xpubChangeVersionBytes,
bip32Addresses: bip32Addresses
bip32Addresses: bip32Addresses,
createPromiseResultBatch: createPromiseResultBatch,
makeRequest: makeRequest,
};
Binary file added btc-rpc-explorer-node-details.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions routes/baseRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const coins = require("./../app/coins.js");
const config = require("./../app/config.js");
const coreApi = require("./../app/api/coreApi.js");
const addressApi = require("./../app/api/addressApi.js");
const electrumAddressApi = require("./../app/api/electrumAddressApi.js");
const rpcApi = require("./../app/api/rpcApi.js");
const btcQuotes = require("./../app/coins/btcQuotes.js");

Expand Down Expand Up @@ -1362,6 +1363,61 @@ router.get("/tx/:transactionId@:blockHeight", asyncHandler(async (req, res, next
next();
}));

router.get("/outpoint/:transactionId-:vout", asyncHandler(async (req, res, next) => {
if ((config.addressApi == "electrum" || config.addressApi == "electrumx") && config.electrumTxIndex) {
try {
var txid = utils.asHash(req.params.transactionId);
var vout = parseInt(req.params.vout);
var outpoint = [txid, vout];

const spent_outpoints = await electrumAddressApi.lookupOutpointsTx([outpoint]);
const spending_tx = spent_outpoints[0].result;

if (spending_tx.spender_txhash == undefined) {
res.locals.userMessageMarkdown = `Outpoint **${txid}:${vout}** unspent`;
req.url = "/tx/" + req.params.transactionId + `#output-${vout}`;
next();
} else {
var diff_height = "in the mempool";
if (spending_tx.spender_height > 0) {
diff_height = `after ${spending_tx.spender_height - spending_tx.height} block`
if (spending_tx.spender_height - spending_tx.height > 1) {
diff_height += `s`
}
}
res.locals.userMessageMarkdown = `Outpoint **${txid}:${vout}** spent by this transaction ` + diff_height;
req.url = "/tx/" + spending_tx.spender_txhash;
next();
}
} catch (err) {
if (global.prunedBlockchain && res.locals.blockHeight && res.locals.blockHeight < global.pruneHeight) {
// Failure to load tx here is expected and a full description of the situation is given to the user
// in the UI. No need to also show an error userMessage here.

} else if (!global.txindexAvailable) {
res.locals.noTxIndexMsg = noTxIndexMsg;

// As above, failure to load the tx is expected here and good user feedback is given in the UI.
// No need for error userMessage.

} else {
res.locals.userMessageMarkdown = `Outpoint not found: txid=**${txid}**, vout=**${vout}**`;
}



utils.logError("1237y4ewssgt", err);

res.render("transaction");

next();
}
} else {
req.url = "/tx/" + req.params.transactionId;

next();
}
}));

router.get("/tx/:transactionId", asyncHandler(async (req, res, next) => {
try {
Expand Down Expand Up @@ -1433,6 +1489,32 @@ router.get("/tx/:transactionId", asyncHandler(async (req, res, next) => {

await Promise.all(promises);

// Electrs 0.9.0 support spending transaction lookup for an outpoint
if ((config.addressApi == "electrum" || config.addressApi == "electrumx") && config.electrumTxIndex) {
if (res.locals.utxos.length > 200) {
res.locals.spendings = null;
} else {
let outpoints = [];
for (const vout in tx.vout) {
if (res.locals.utxos[vout] == null) {
outpoints.push([txid, parseInt(vout)]);
}
}
const spent_outpoints = await electrumAddressApi.lookupOutpointsTx(outpoints);
let spendings_status = [];
let spent_idx = 0;
for (const vout in tx.vout) {
if (res.locals.utxos[vout] == null) {
spendings_status.push(spent_outpoints[spent_idx].result);
++spent_idx;
} else {
spendings_status.push(false)
}
}
res.locals.spendings = spendings_status
}
}

if (global.specialTransactions && global.specialTransactions[txid]) {
let funInfo = global.specialTransactions[txid];

Expand Down
23 changes: 21 additions & 2 deletions views/includes/transaction-io-details.pug
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ mixin outputValueDisplay(vout, voutIndex)
- hiddenRow = true;
- hiddenRowCount++;

- var spent = false;
if (false && config.electrumServers && config.electrumServers.length > 0)
- spent = electrumAddressApi.lookupOutpointTx(vout, voutIndex)

div(data-txid=tx.txid, class=(hiddenRow ? "d-none" : ""))
.clearfix.mb-0.mb-sm-2.mb-md-0
a.xs-hidden.d-none.d-sm-inline.badge.card-highlight.border.text-decoration-none.fw-normal.me-2(data-bs-toggle="tooltip", title=`Output #${voutIndex.toLocaleString()}`, style="white-space: nowrap;")
Expand Down Expand Up @@ -275,7 +279,23 @@ mixin outputValueDisplay(vout, voutIndex)
span.d-none.d-sm-inline
+darkBadge
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip") #{utils.outputTypeAbbreviation(vout.scriptPubKey.type)}

if (spendings === undefined)
span.mt-1
a(href=`./outpoint/${tx.txid}-${voutIndex}`) Outpoint Status
else if (spendings)
- var spending_vout = spendings[voutIndex];
if (spending_vout && spending_vout.spender_txhash)
span.mt-1 spent by
a(href=`./tx/${spending_vout.spender_txhash}`) #{utils.ellipsizeMiddle(spending_vout.spender_txhash,9)}
if (spending_vout.spender_height > 0 && spending_vout.spender_height > spending_vout.height)
span #{spending_vout.spender_height - spending_vout.height} blocks later
else if (spending_vout.spender_height <= 0)
span in the mempool
else
span in the same block
else if (spendings === null && utxos[voutIndex] == null)
span.mt-1 spent by
a(href=`./outpoint/${txid}-${voutIndex}`) this transaction


if (voutAddresses.length == 0)
Expand All @@ -294,7 +314,6 @@ mixin outputValueDisplay(vout, voutIndex)
+darkBadge
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip") #{utils.outputTypeAbbreviation(vout.scriptPubKey.type)}


span.ms-2.float-end
+outputValueDisplay(vout, voutIndex)

Expand Down