You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

156 lines
6.7 KiB

#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2023 The Electrum Developers
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from typing import TYPE_CHECKING
import copy
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QTextCharFormat, QFont
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QTextBrowser
from electrum.i18n import _
from .util import WindowModalDialog, ButtonsLineEdit, ShowQRLineEdit, ColorScheme, Buttons, CloseButton, MONOSPACE_FONT, WWLabel
from .history_list import HistoryList, HistoryModel
from .qrtextedit import ShowQRTextEdit
from .transaction_dialog import TxOutputColoring, QTextBrowserWithDefaultSize
if TYPE_CHECKING:
from electrum.transaction import PartialTxInput
from .main_window import ElectrumWindow
class UTXODialog(WindowModalDialog):
def __init__(self, window: 'ElectrumWindow', utxo: 'PartialTxInput'):
WindowModalDialog.__init__(self, window, _("Coin Privacy Analysis"))
self.main_window = window
self.config = window.config
self.wallet = window.wallet
self.utxo = utxo
self.parents_list = QTextBrowserWithDefaultSize(800, 400)
self.parents_list.setOpenLinks(False) # disable automatic link opening
self.parents_list.anchorClicked.connect(self.open_tx) # send links to our handler
self.parents_list.setFont(QFont(MONOSPACE_FONT))
self.parents_list.setReadOnly(True)
self.parents_list.setTextInteractionFlags(self.parents_list.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
self.txo_color_parent = TxOutputColoring(
legend=_("Direct parent"), color=ColorScheme.BLUE, tooltip=_("Direct parent"))
self.txo_color_uncle = TxOutputColoring(
legend=_("Address reuse"), color=ColorScheme.RED, tooltip=_("Address reuse"))
vbox = QVBoxLayout()
vbox.addWidget(QLabel(_("Output point") + ": " + str(self.utxo.short_id)))
vbox.addWidget(QLabel(_("Amount") + ": " + self.main_window.format_amount_and_units(self.utxo.value_sats())))
self.stats_label = WWLabel()
vbox.addWidget(self.stats_label)
vbox.addWidget(self.parents_list)
legend_hbox = QHBoxLayout()
legend_hbox.setContentsMargins(0, 0, 0, 0)
legend_hbox.addStretch(2)
legend_hbox.addWidget(self.txo_color_parent.legend_label)
legend_hbox.addWidget(self.txo_color_uncle.legend_label)
vbox.addLayout(legend_hbox)
vbox.addLayout(Buttons(CloseButton(self)))
self.setLayout(vbox)
self.update()
self.main_window.labels_changed_signal.connect(self.update)
def update(self):
txid = self.utxo.prevout.txid.hex()
parents = self.wallet.get_tx_parents(txid)
num_parents = len(parents)
parents_copy = copy.deepcopy(parents)
cursor = self.parents_list.textCursor()
ext = QTextCharFormat()
if num_parents < 200:
ASCII_EDGE = '└─'
ASCII_BRANCH = '├─'
ASCII_PIPE = ''
ASCII_SPACE = ' '
else:
ASCII_EDGE = ''
ASCII_BRANCH = ''
ASCII_PIPE = ''
ASCII_SPACE = ' '
self.parents_list.clear()
self.num_reuse = 0
def print_ascii_tree(_txid, prefix, is_last, is_uncle):
if _txid not in parents:
return
tx_mined_info = self.wallet.adb.get_tx_height(_txid)
tx_height = tx_mined_info.height
tx_pos = tx_mined_info.txpos
key = "%dx%d"%(tx_height, tx_pos) if tx_pos is not None else _txid[0:8]
label = self.wallet.get_label_for_txid(_txid) or ""
if _txid not in parents_copy:
label = '[duplicate]'
c = '' if _txid == txid else (ASCII_EDGE if is_last else ASCII_BRANCH)
cursor.insertText(prefix + c, ext)
if is_uncle:
self.num_reuse += 1
lnk = QTextCharFormat(self.txo_color_uncle.text_char_format)
else:
lnk = QTextCharFormat(self.txo_color_parent.text_char_format)
lnk.setToolTip(_('Click to open, right-click for menu'))
lnk.setAnchorHref(_txid)
#lnk.setAnchorNames([a_name])
lnk.setAnchor(True)
lnk.setUnderlineStyle(QTextCharFormat.SingleUnderline)
cursor.insertText(key, lnk)
cursor.insertText(" ", ext)
cursor.insertText(label, ext)
cursor.insertBlock()
next_prefix = '' if txid == _txid else prefix + (ASCII_SPACE if is_last else ASCII_PIPE)
parents_list, uncle_list = parents_copy.pop(_txid, ([],[]))
for i, p in enumerate(parents_list + uncle_list):
is_last = (i == len(parents_list) + len(uncle_list)- 1)
is_uncle = (i > len(parents_list) - 1)
print_ascii_tree(p, next_prefix, is_last, is_uncle)
# recursively build the tree
print_ascii_tree(txid, '', False, False)
msg = _("This UTXO has {} parent transactions in your wallet.").format(num_parents)
if self.num_reuse:
msg += '\n' + _('This does not include transactions that are downstream of address reuse.')
self.stats_label.setText(msg)
self.txo_color_parent.legend_label.setVisible(True)
self.txo_color_uncle.legend_label.setVisible(bool(self.num_reuse))
# set cursor to top
cursor.setPosition(0)
self.parents_list.setTextCursor(cursor)
def open_tx(self, txid):
if isinstance(txid, QUrl):
txid = txid.toString(QUrl.None_)
tx = self.wallet.adb.get_transaction(txid)
if not tx:
return
self.main_window.show_transaction(tx)