Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 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
52 changes: 52 additions & 0 deletions app/api/electrumAddressApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,57 @@ 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
Copy Markdown
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
Copy Markdown
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 +431,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,
};
24 changes: 24 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 @@ -1433,6 +1434,29 @@ 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) {
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
11 changes: 11 additions & 0 deletions views/includes/transaction-io-details.pug
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,17 @@ mixin outputValueDisplay(vout, voutIndex)
+darkBadge
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip") #{utils.outputTypeAbbreviation(vout.scriptPubKey.type)}

if (spendings)
- var spending_vout = spendings[voutIndex];
if (spending_vout)
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 if (spending_vout.spender_height == spending_vout.height)
span in the same block

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