Bugfix: Missing values in tx details (#1862)

* fix missing values in tx details
* cypress test
* remove spaces after Address
* better handling of non-wallet txids in inputs
* Update zblack.yml

Co-authored-by: moneymanolis <moneymanolis@protonmail.com>
Co-authored-by: k9ert <kneunert@gmail.com>
This commit is contained in:
relativisticelectron 2022-09-21 17:32:24 +02:00 committed by GitHub
parent e3d09f15c3
commit 9394d7993d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 168 additions and 78 deletions

View file

@ -8,6 +8,4 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: psf/black@22.3.0 # the default is equivalent to `black . --diff --check`.
with:
args: ". --diff --check"
- uses: psf/black@stable # https://black.readthedocs.io/en/stable/integrations/github_actions.html

View file

@ -30,7 +30,38 @@ describe('Test sending transactions', () => {
expect(n).to.be.equals(0)
})
})
// Skipped for now, will only work reliably once the the Cypress tests run without mining loop
it('Open up transaction details', () => {
cy.selectWallet("Test Hot Wallet 1")
cy.get('#btn_transactions').click()
// Click on the txid in the first row
cy.get('tbody.tx-tbody').find('tr').eq(0).find('#column-txid').find('.explorer-link').click()
cy.get('.tx-data-info').contains('Input #0')
cy.get('.tx-data-info').contains('Transaction id:')
cy.get('.tx-data-info').contains('Output index:') // Not sure whether it is always 1 - output ordering is random in Core ...
cy.get('.tx-data-info').contains('Address #0')
cy.get('.tx-data-info').contains('Value: 20 tBTC')
cy.get('.tx-data-info').contains('Output #0')
cy.get('.tx-data-info').contains('Burn address')
cy.get('.tx-data-info').contains('Value: 19.9999989 tBTC') // Fees should always be the same
cy.get('#page_overlay_popup_cancel_button').click()
// Change to sats and check amounts and units
cy.get('[href="/settings/"]').click()
cy.get('[name="unit"]').select('sats')
cy.contains('Save').click()
cy.selectWallet("Test Hot Wallet 1")
cy.get('#btn_transactions').click()
cy.get('tbody.tx-tbody').find('tr').eq(0).find('#column-txid').find('.explorer-link').click()
cy.get('.tx-data-info').contains('Value: 2,000,000,000 tsat')
cy.get('.tx-data-info').contains('Value: 1,999,999,890 tsat')
cy.get('#page_overlay_popup_cancel_button').click()
// Change back to btc
cy.get('[href="/settings/"]').click()
cy.get('[name="unit"]').select('BTC')
cy.contains('Save').click()
})
it('Adding and deleting recipients', () => {
// We need new sats but mine2wallet only works if a wallet is selected
cy.selectWallet("Test Hot Wallet 1")

View file

@ -256,7 +256,24 @@ def decoderawtx(wallet_alias):
wallet: Wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
txid = request.form.get("txid", "")
if txid:
tx = wallet.rpc.gettransaction(txid)
try:
tx = wallet.rpc.gettransaction(txid)
except RpcError as e:
if "Invalid or non-wallet transaction id" in str(e):
# Expected failure when looking up a txid that didn't originate from the
# user's Wallet.
logger.info(
"Looking up a txid that didn't originate from the user's wallet. Can't return any tx data."
)
return jsonify(
success=False,
nonWalletTxId=True,
)
else:
logger.warning(
"Failed to fetch transaction data. Exception: {}".format(e)
)
return jsonify(success=False)
# This is a fix for Bitcoin Core versions < v0.20
# These do not return the blockheight as part of the `gettransaction` command
# So here we check if this property is lacking and if so
@ -310,15 +327,6 @@ def decoderawtx(wallet_alias):
rawtx=rawtx,
walletName=wallet.name,
)
except RpcError as e:
if "Invalid or non-wallet transaction id" in str(e):
# Expected failure when looking up a txid that didn't originate from the
# user's Wallet.
pass
else:
app.logger.warning(
"Failed to fetch transaction data. Exception: {}".format(e)
)
except Exception as e:
app.logger.warning("Failed to fetch transaction data. Exception: {}".format(e))

View file

@ -40,6 +40,7 @@
}
</style>
<script type="text/javascript" src="{{ url_for('static', filename='helpers.js') }}"></script>
{% include "includes/helpers.jinja" %}
{% include "services/inject_in_basejinja_head.jinja" %}
{% block head %}
{% endblock %}

View file

@ -5,7 +5,6 @@
}
.service-icon {
margin-top: -5px;
margin-right: 0.25em;
height:24px;
vertical-align: middle;
}
@ -65,9 +64,7 @@
}
</style>
<form class="address-label-form">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<img class="service-icon"/>
<a target="_blank" class="explorer-link"><span class="label" autocomplete="off" spellcheck="false">{{ _("Fetching address label...") }}</span></a>
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/><img class="service-icon"><a target="_blank" class="explorer-link"><span class="label" autocomplete="off" spellcheck="false">{{ _("Fetching address label...") }}</span></a>
<button type="button" class="btn edit" title="Edit label"><img src="{{ url_for('static', filename='img/edit.svg') }}" style="width: 22px; margin-bottom:-5px;" class="svg-white"/></button>
<button type="button" class="btn update hidden">{{ _("Update") }}</button>
<button type="button" class="btn cancel hidden">{{ _("Cancel") }}</button>

View file

@ -0,0 +1,75 @@
<!-- This file contains javascript helper functions that also can use jinja -->
<script type="text/javascript">
// Formats an unitLabel (e.g. from a tx output) to a useful unit label
function formatUnitLabel(unitLabel, convertToSat){
var newLabel = unitLabel;
if (newLabel == "" || !newLabel){
newLabel = {% if specter.is_liquid %}"LBTC"{%else%}"BTC"{%endif%}
}
if (convertToSat) {
if(newLabel == "LBTC"){
newLabel = "Lsat";
}
// Liquid JSON response on testnet is already tLBTC
else if (newLabel == "tLBTC") {
newLabel = "tLsat";
}
if(newLabel == "BTC"){
newLabel = "sat";
}
}
{% if specter.is_testnet %}
if (!newLabel.startsWith("t")){
newLabel = "t" + newLabel;
}
{%endif%}
return newLabel;
}
// Formats the valueInBTC (e.g. from a tx output) to "formattedValue formattedUnitLabel"
// e.g. 0.22569496 tBTC
function formatBtcAmount(valueInBTC, unitLabel, convertToSat){
var formattedUnitLabel = formatUnitLabel(unitLabel, convertToSat);
var formattedValue = valueInBTC;
if (valueInBTC) {
value = parseFloat(valueInBTC.toFixed(8));
// The second condition in the if clause if True if formattedUnitLabel is equal to any element of the array
if (convertToSat && (["Lsat", "sat", "tLsat", "tsat"].indexOf(formattedUnitLabel) > -1)) {
formattedValue = parseInt(value * 1e8)
}
formattedValue = `${numberWithCommas(formattedValue)}`;
}
else {
formattedValue = '{{ _("Confidential") }}';
formattedUnitLabel = '';
}
return `${formattedValue} ${formattedUnitLabel}`;
}
// Determines if an unitLabel (e.g. from a tx output) is "BTC", "LBTC", "tBTC", "tLBTC"
function unitLabelIsBitcoin(unitLabel){
if (!unitLabel){
return true
}
return ([null, "", "LBTC", "BTC", "tBTC", "tLBTC"].indexOf(unitLabel) > -1)
}
// Calculates and formats the price as a span class="note"
function formatPrice(valueInBTC, unitLabel, symbol, price){
var formattedPrice = "";
if (valueInBTC) {
if(unitLabelIsBitcoin(unitLabel)){
if (price && symbol) {
formattedPrice = `<span class="note">(${symbol}${numberWithCommas((parseFloat(price) * valueInBTC).toFixed(2))})</span>`;
}
}
}
return formattedPrice
}
</script>

View file

@ -153,29 +153,6 @@
if (isMine) {
bgColor = '#925d07';
}
let value = "Confidential";
let price = '';
let assetlabel = "";
if("assetlabel" in spentOutput){
assetlabel = spentOutput.assetlabel;
}
if (spentOutput.value) {
value = parseFloat(spentOutput.value.toFixed(8));
if(assetlabel == "LBTC" || assetlabel == ""){
if (this.price && this.symbol) {
price = `<span class="note">(${this.symbol}${numberWithCommas((parseFloat(this.price) * value).toFixed(2))})</span>`;
}
if (this.btcUnit == 'sat') {
assetlabel = "sat";
value = parseInt(value * 1e8);
}
}
value = `${numberWithCommas(value.toString())}`;
}
let labelAttr = "";
if ("label" in spentOutput) {
labelAttr = `data-label="${spentOutput.label}"`;
@ -184,16 +161,21 @@
if ("service_id" in spentOutput) {
serviceIdAttr = `data-service-id="${spentOutput.service_id}"`;
}
// assetlabel only exists for Liquid, for BTC it is just null
var amountAndUnit = formatBtcAmount(spentOutput.value, spentOutput.assetlabel, this.btcUnit == 'sat');
var price = formatPrice(spentOutput.value, spentOutput.assetlabel, this.symbol, this.price);
addressAndValue = `<br>
{{ _("Address:") }} <address-label
addressAndValue = `
{{ _("Address:") }}
<address-label
data-copy-hidden="true"
data-address="${address}"
data-wallet="${this.wallet}"
${labelAttr}
${serviceIdAttr}
/><br>
{{ _("Value:") }} ${value} ${assetlabel} ${price}`;
${serviceIdAttr}>
</address-label><br>
{{ _("Value:") }} ${amountAndUnit} ${price}
`;
}
if ('coinbase' in rawtx.vin[i]) {
@ -206,14 +188,28 @@
`;
continue;
}
rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
<b>{{ _("Input #") }}${i}</b><br><br>
{{ _("Transaction id") }}: <explorer-link style="word-break: break-all;" data-type="tx" data-value="${rawtx.vin[i].txid}"></explorer-link><br>
{{ _("Output #") }}" ${rawtx.vin[i].vout}
${addressAndValue}
</p>
`;
if (jsonResponse.nonWalletTxId) {
let nonWalletTxId = jsonResponse.nonWalletTxId ? `<span>Not known, this is a non-wallet txid.</span>` : null
rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
<b>{{ _("Input #") }}${i}</b><br><br>
{{ _("Transaction id") }}: <explorer-link style="word-break: break-all;" data-type="tx" data-value="${rawtx.vin[i].txid}"></explorer-link><br>
{{ _("Output index: ") }}${rawtx.vin[i].vout}<br>
{{ _("Address & value:") }} ${nonWalletTxId}
</p>
`;
}
else {
rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
<b>{{ _("Input #") }}${i}</b><br><br>
{{ _("Transaction id") }}: <explorer-link style="word-break: break-all;" data-type="tx" data-value="${rawtx.vin[i].txid}"></explorer-link><br>
{{ _("Output index: ") }}${rawtx.vin[i].vout}<br>
${addressAndValue}
</p>
`;
}
}
rawtxHTML += `<h3>{{ _("Outputs") }}</h3>`;
@ -229,26 +225,6 @@
if (address == 'Unknown') {
address = rawtx.vout[i].address ? rawtx.vout[i].address : 'Unknown';
}
let value = "Confidential";
let price = '';
let assetlabel = "";
if("assetlabel" in rawtx.vout[i]){
assetlabel = rawtx.vout[i].assetlabel;
}
if (rawtx.vout[i].value) {
value = parseFloat(rawtx.vout[i].value.toFixed(8));
if(assetlabel == "LBTC" || assetlabel == ""){
if (this.price && this.symbol) {
price = `<span class="note">(${this.symbol}${numberWithCommas((parseFloat(this.price) * value).toFixed(2))})</span>`;
}
if (this.btcUnit == 'sat') {
assetlabel = "sat";
value = parseInt(value * 1e8)
}
}
value = `${numberWithCommas(value.toString())}`;
}
let bgColor = '#131a24';
if ("index" in rawtx.vout[i]) {
@ -266,18 +242,22 @@
if ("service_id" in rawtx.vout[i]) {
serviceIdAttr = `data-service-id="${rawtx.vout[i].service_id}"`;
}
// assetlabel only exists for Liquid, for BTC it is just null
var amountAndUnit = formatBtcAmount(rawtx.vout[i].value, rawtx.vout[i].assetlabel, this.btcUnit == 'sat');
var price = formatPrice(rawtx.vout[i].value, rawtx.vout[i].assetlabel, this.symbol, this.price);
rawtxHTML += `
<p class="tx_info" style="text-align: left; background-color: ${bgColor};">
<b>{{ _("Output #${i}") }}</b><br><br>
{{ _("Address:") }} <address-label
{{ _("Address:") }}
<address-label
data-copy-hidden="true"
data-address="${address}"
data-wallet="${this.wallet}"
${labelAttr}
${serviceIdAttr}
/><br/>
{{ _("Value:") }} ${value} ${assetlabel} ${price}
${serviceIdAttr}>
</address-label><br>
{{ _("Value:") }} ${amountAndUnit} ${price}
</p>
`;
}