forked from bitcoinafterlife/bal-electrum-plugin
BAL - Bitcoin After Life Electrum plugin (v0.2.8)
Behavior-preserving refactor of the original BAL plugin with clean separation
of business logic from the PyQt GUI.
Layout:
bal/core/ GUI-free logic (util, plugin_base, heirs, will, willexecutors)
bal/gui/qt/ PyQt6 presentation (theme, common, widgets, calendar, dialogs,
lists, window, plugin)
bal/qt.py Qt entry-point shim (works as internal and external zip plugin)
bal/manifest.json standard-conforming metadata
Tooling:
build_zip.py deterministic, zipimport-friendly archive builder
tests/smoke_test.py imports + behavior regression test
tests/external_zip_test.py reproduces Electrum's external-zip loading
Targets Electrum 4.7.2 + PyQt6. Logic kept byte-identical where possible.
This commit is contained in:
952
bal/gui/qt/window.py
Normal file
952
bal/gui/qt/window.py
Normal file
@@ -0,0 +1,952 @@
|
||||
"""
|
||||
bal.gui.qt.window
|
||||
=================
|
||||
|
||||
The :class:`BalWindow` controller: one instance per Electrum wallet window.
|
||||
|
||||
This is the orchestration layer that ties together the heirs list, the will
|
||||
preview, the will-executors and the various dialogs. It owns the per-wallet
|
||||
state (``heirs``, ``will``, ``willitems``, ``will_settings``) and exposes the
|
||||
high-level actions (build / check / sign / broadcast / invalidate the will,
|
||||
import/export, etc.) that the menus, tabs and dialogs invoke.
|
||||
|
||||
The actual Bitcoin logic lives in :mod:`bal.core`; this class only coordinates
|
||||
it with the GUI.
|
||||
"""
|
||||
|
||||
from .common import *
|
||||
from .common import _, _logger # underscore names are not re-exported by "import *"
|
||||
from .widgets import LockTimeWidget, PercAmountEdit, WillSettingsWidget
|
||||
from .dialogs import (BalBlockingWaitingDialog, BalBuildWillDialog, BalDialog,
|
||||
BalWaitingDialog, BalWizardDialog, WillDetailDialog,
|
||||
WillExecutorDialog)
|
||||
from .lists import HeirListWidget, PreviewList, WillExecutorWidget
|
||||
|
||||
|
||||
class BalWindow:
|
||||
def __init__(self, bal_plugin: "BalPlugin", window: "ElectrumWindow"):
|
||||
self.bal_plugin = bal_plugin
|
||||
self.window = window
|
||||
self.heirs = {}
|
||||
self.will = {}
|
||||
self.willitems = {}
|
||||
self.willexecutors = {}
|
||||
self.will_settings = None
|
||||
self.ok = False
|
||||
self.disable_plugin = True
|
||||
self.bal_plugin.get_decimal_point = self.window.get_decimal_point
|
||||
|
||||
if self.window.wallet:
|
||||
self.wallet = self.window.wallet
|
||||
if not self.will_settings:
|
||||
self.will_settings = self.bal_plugin.WILL_SETTINGS.get()
|
||||
Util.fix_will_settings_tx_fees(self.will_settings)
|
||||
self.heirs = Heirs(self.wallet)
|
||||
|
||||
self.heirs_tab = self.create_heirs_tab()
|
||||
self.will_tab = self.create_will_tab()
|
||||
self.heirs_tab.wallet = self.wallet
|
||||
self.will_tab.wallet = self.wallet
|
||||
|
||||
def init_menubar_tools(self, tools_menu):
|
||||
self.tools_menu = tools_menu
|
||||
|
||||
def add_optional_tab(tabs, tab, icon, description):
|
||||
tab.tab_icon = icon
|
||||
tab.tab_description = description
|
||||
tab.tab_pos = len(tabs)
|
||||
if tab.is_shown_cv.get():
|
||||
tabs.addTab(tab, icon, description.replace("&", ""))
|
||||
|
||||
def add_toggle_action(tab):
|
||||
is_shown = tab.is_shown_cv.get()
|
||||
tab.menu_action = self.window.view_menu.addAction(
|
||||
tab.tab_description, lambda: self.window.toggle_tab(tab)
|
||||
)
|
||||
tab.menu_action.setCheckable(True)
|
||||
tab.menu_action.setChecked(is_shown)
|
||||
|
||||
add_optional_tab(
|
||||
self.window.tabs,
|
||||
self.heirs_tab,
|
||||
read_QIcon_from_bytes(self.bal_plugin.read_file("icons/heir.png")),
|
||||
_("&Heirs"),
|
||||
)
|
||||
add_optional_tab(
|
||||
self.window.tabs,
|
||||
self.will_tab,
|
||||
read_QIcon_from_bytes(self.bal_plugin.read_file("icons/will.png")),
|
||||
_("&Will"),
|
||||
)
|
||||
tools_menu.addSeparator()
|
||||
self.tools_menu.willexecutors_action = tools_menu.addAction(
|
||||
_("&Will-Executors"), self.show_willexecutor_dialog
|
||||
)
|
||||
self.window.view_menu.addSeparator()
|
||||
add_toggle_action(self.heirs_tab)
|
||||
add_toggle_action(self.will_tab)
|
||||
|
||||
def load_willitems(self):
|
||||
self.willitems = {}
|
||||
for wid, w in self.will.items():
|
||||
self.willitems[wid] = WillItem(w, wallet=self.wallet)
|
||||
if self.willitems:
|
||||
self.will_list_widget.will = self.willitems
|
||||
self.will_list_widget.update_will(self.willitems)
|
||||
self.will_tab.update()
|
||||
|
||||
def save_willitems(self):
|
||||
keys = list(self.will.keys())
|
||||
for k in keys:
|
||||
del self.will[k]
|
||||
for wid, w in self.willitems.items():
|
||||
self.will[wid] = w.to_dict()
|
||||
|
||||
def init_will(self):
|
||||
_logger.info("********************init_____will____________**********")
|
||||
if not self.willexecutors:
|
||||
self.willexecutors = Willexecutors.get_willexecutors(
|
||||
self.bal_plugin, update=False, bal_window=self
|
||||
)
|
||||
if not self.heirs:
|
||||
self.heirs = Heirs._validate(Heirs(self.wallet))
|
||||
self.heirs_tab.update()
|
||||
if not self.will:
|
||||
self.will = self.wallet.db.get_dict("will")
|
||||
Util.fix_will_tx_fees(self.will)
|
||||
if self.will:
|
||||
self.willitems = {}
|
||||
try:
|
||||
self.load_willitems()
|
||||
except Exception:
|
||||
self.disable_plugin = True
|
||||
self.show_warning(
|
||||
_("Please restart Electrum to activate the BAL plugin"),
|
||||
title=_("Success"),
|
||||
)
|
||||
self.close_wallet()
|
||||
return
|
||||
|
||||
# if not self.will_settings:
|
||||
# self.will_settings = self.wallet.db.get_dict("will_settings")
|
||||
# Util.fix_will_settings_tx_fees(self.will_settings)
|
||||
|
||||
# _logger.info("will_settings: {}".format(self.will_settings))
|
||||
# if not self.will_settings:
|
||||
# Util.copy(self.will_settings, self.bal_plugin.default_will_settings())
|
||||
# _logger.debug("not_will_settings {}".format(self.will_settings))
|
||||
# self.bal_plugin.validate_will_settings(self.will_settings)
|
||||
# self.heir_list_widget.update_will_settings()
|
||||
# self.heir_list_widget.update()
|
||||
|
||||
def init_wizard(self):
|
||||
wizard_dialog = BalWizardDialog(self)
|
||||
wizard_dialog.exec()
|
||||
|
||||
def show_willexecutor_dialog(self):
|
||||
self.willexecutor_dialog = WillExecutorDialog(self)
|
||||
self.willexecutor_dialog.show()
|
||||
|
||||
def create_heirs_tab(self):
|
||||
if not self.heirs:
|
||||
self.heirs = Heirs(self.wallet)
|
||||
self.heir_list_widget = HeirListWidget(self, self.window)
|
||||
tab = self.window.create_list_tab(self.heir_list_widget)
|
||||
tab.is_shown_cv = shown_cv(False)
|
||||
return tab
|
||||
|
||||
def create_will_tab(self):
|
||||
self.will_list_widget = PreviewList(self, self.window, None)
|
||||
tab = self.window.create_list_tab(self.will_list_widget)
|
||||
tab.is_shown_cv = shown_cv(True)
|
||||
return tab
|
||||
|
||||
def new_heir_dialog(self, heir_key=None):
|
||||
heir = self.heirs.get(heir_key)
|
||||
title = "New heir"
|
||||
if heir:
|
||||
title = f"Edit: {heir_key}"
|
||||
|
||||
d = BalDialog(
|
||||
self.window, self.bal_plugin, self.bal_plugin.get_window_title(_(title))
|
||||
)
|
||||
|
||||
vbox = QVBoxLayout(d)
|
||||
grid = QGridLayout()
|
||||
|
||||
heir_name = QLineEdit()
|
||||
heir_name.setFixedWidth(32 * char_width_in_lineedit())
|
||||
heir_address = QLineEdit()
|
||||
heir_address.setFixedWidth(32 * char_width_in_lineedit())
|
||||
heir_amount = PercAmountEdit(self.window.get_decimal_point)
|
||||
|
||||
if heir:
|
||||
heir_name.setText(str(heir_key))
|
||||
heir_address.setText(str(heir[0]))
|
||||
heir_amount.setText(
|
||||
str(Util.decode_amount(heir[1], self.window.get_decimal_point()))
|
||||
)
|
||||
self.heir_locktime = LockTimeWidget(self, self.window, heir[2])
|
||||
|
||||
# heir_is_xpub = QCheckBox()
|
||||
|
||||
new_heir_button = QPushButton(_("Add another heir"))
|
||||
self.add_another_heir = False
|
||||
|
||||
def new_heir():
|
||||
self.add_another_heir = True
|
||||
d.accept()
|
||||
|
||||
new_heir_button.clicked.connect(new_heir)
|
||||
new_heir_button.setDefault(True)
|
||||
|
||||
grid.addWidget(QLabel(_("Name")), 1, 0)
|
||||
grid.addWidget(heir_name, 1, 1)
|
||||
grid.addWidget(HelpButton(_("Unique name or description about heir")), 1, 2)
|
||||
|
||||
grid.addWidget(QLabel(_("Address")), 2, 0)
|
||||
grid.addWidget(heir_address, 2, 1)
|
||||
grid.addWidget(HelpButton(_("heir bitcoin address")), 2, 2)
|
||||
|
||||
grid.addWidget(QLabel(_("Amount")), 3, 0)
|
||||
grid.addWidget(heir_amount, 3, 1)
|
||||
grid.addWidget(HelpButton(_("Fixed or Percentage amount if end with %")), 3, 2)
|
||||
|
||||
locktime_label = QLabel(_("Locktime"))
|
||||
enable_multiverse = self.bal_plugin.ENABLE_MULTIVERSE.get()
|
||||
if enable_multiverse:
|
||||
grid.addWidget(locktime_label, 4, 0)
|
||||
grid.addWidget(self.heir_locktime, 4, 1)
|
||||
grid.addWidget(HelpButton(_("locktime")), 4, 2)
|
||||
|
||||
vbox.addLayout(grid)
|
||||
buttons = [CancelButton(d), OkButton(d)]
|
||||
if not heir:
|
||||
buttons.append(new_heir_button)
|
||||
vbox.addLayout(Buttons(*buttons))
|
||||
while d.exec():
|
||||
# TODO SAVE HEIR
|
||||
heir = [
|
||||
heir_name.text(),
|
||||
heir_address.text(),
|
||||
Util.encode_amount(heir_amount.text(), self.window.get_decimal_point()),
|
||||
str(self.will_settings["locktime"]),
|
||||
]
|
||||
try:
|
||||
self.set_heir(heir)
|
||||
if self.add_another_heir:
|
||||
self.new_heir_dialog()
|
||||
break
|
||||
except Exception as e:
|
||||
self.show_error(str(e))
|
||||
|
||||
def set_heir(self, heir):
|
||||
heir = list(heir)
|
||||
if not self.bal_plugin.ENABLE_MULTIVERSE.get():
|
||||
heir[3] = self.will_settings["locktime"]
|
||||
|
||||
h = Heirs.validate_heir(heir[0], heir[1:])
|
||||
self.heirs[heir[0]] = h
|
||||
self.heir_list_widget.update()
|
||||
return True
|
||||
|
||||
def delete_heirs(self, heirs):
|
||||
for heir in heirs:
|
||||
try:
|
||||
del self.heirs[heir]
|
||||
except Exception as e:
|
||||
_logger.debug(f"error deleting heir: {heir} {e}")
|
||||
pass
|
||||
self.heirs.save()
|
||||
self.heir_list_widget.update()
|
||||
return True
|
||||
|
||||
def import_heirs(self):
|
||||
import_meta_gui(
|
||||
self.window,
|
||||
_("heirs"),
|
||||
self.heirs.import_file,
|
||||
self.heir_list_widget.update,
|
||||
)
|
||||
|
||||
def export_heirs(self):
|
||||
export_meta_gui(self.window, "heirs.json", self.heirs.export_file)
|
||||
|
||||
def prepare_will(self, ignore_duplicate=False, keep_original=False):
|
||||
will = self.build_inheritance_transaction(
|
||||
ignore_duplicate=ignore_duplicate, keep_original=keep_original
|
||||
)
|
||||
return will
|
||||
|
||||
def delete_not_valid(self, txid, s_utxo):
|
||||
raise NotImplementedError()
|
||||
|
||||
def update_will(self, will):
|
||||
Will.update_will(self.willitems, will)
|
||||
self.willitems.update(will)
|
||||
Will.normalize_will(self.willitems, self.wallet)
|
||||
|
||||
def build_will(self, ignore_duplicate=True, keep_original=True):
|
||||
_logger.debug("building will...")
|
||||
will = {}
|
||||
# willtodelete = []
|
||||
# willtoappend = {}
|
||||
try:
|
||||
self.willexecutors = Willexecutors.get_willexecutors(
|
||||
self.bal_plugin, update=False, bal_window=self
|
||||
)
|
||||
if not self.no_willexecutor:
|
||||
|
||||
f = False
|
||||
for _u, w in self.willexecutors.items():
|
||||
if Willexecutors.is_selected(w):
|
||||
f = True
|
||||
if not f:
|
||||
_logger.error("No Will-Executor or backup transaction selected")
|
||||
raise NoWillExecutorNotPresent(
|
||||
"No Will-Executor or backup transaction selected"
|
||||
)
|
||||
txs = self.heirs.get_transactions(
|
||||
self.bal_plugin,
|
||||
self.window.wallet,
|
||||
self.will_settings["baltx_fees"],
|
||||
None,
|
||||
self.date_to_check,
|
||||
)
|
||||
|
||||
_logger.info(f"txs built: {txs}")
|
||||
creation_time = time.time()
|
||||
if txs:
|
||||
for txid in txs:
|
||||
# txtodelete = []
|
||||
_break = False
|
||||
tx = {}
|
||||
tx["tx"] = txs[txid]
|
||||
tx["my_locktime"] = txs[txid].my_locktime
|
||||
tx["heirsvalue"] = txs[txid].heirsvalue
|
||||
tx["description"] = txs[txid].description
|
||||
tx["willexecutor"] = copy.deepcopy(txs[txid].willexecutor)
|
||||
tx["status"] = _("New")
|
||||
tx["baltx_fees"] = txs[txid].tx_fees
|
||||
tx["time"] = creation_time
|
||||
tx["heirs"] = copy.deepcopy(txs[txid].heirs)
|
||||
tx["txchildren"] = []
|
||||
will[txid] = WillItem(tx, _id=txid, wallet=self.wallet)
|
||||
self.update_will(will)
|
||||
else:
|
||||
_logger.info("No transactions was built")
|
||||
_logger.info(f"will-settings: {self.will_settings}")
|
||||
_logger.info(f"date_to_check:{self.date_to_check}")
|
||||
_logger.info(f"heirs: {self.heirs}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
_logger.info(f"Exception build_will: {e}")
|
||||
raise e
|
||||
pass
|
||||
return self.willitems
|
||||
|
||||
def check_will(self):
|
||||
return Will.is_will_valid(
|
||||
self.willitems,
|
||||
self.block_to_check,
|
||||
self.date_to_check,
|
||||
self.will_settings["baltx_fees"],
|
||||
self.window.wallet.get_utxos(),
|
||||
heirs=self.heirs,
|
||||
willexecutors=self.willexecutors,
|
||||
self_willexecutor=self.no_willexecutor,
|
||||
wallet=self.wallet,
|
||||
callback_not_valid_tx=self.delete_not_valid,
|
||||
)
|
||||
|
||||
def show_message(self, text):
|
||||
self.window.show_message(text)
|
||||
|
||||
def show_warning(self, text, parent=None):
|
||||
self.window.show_warning(text, parent=None)
|
||||
|
||||
def show_error(self, text):
|
||||
self.window.show_error(text)
|
||||
|
||||
def show_critical(self, text):
|
||||
self.window.show_critical(text)
|
||||
|
||||
def update_combo_setting_widgets(
|
||||
self,
|
||||
new_value,
|
||||
field,
|
||||
update_all=False,
|
||||
update_will_dialog=False,
|
||||
update_heirs_dialog=False,
|
||||
):
|
||||
if (update_all or update_will_dialog) and hasattr(self,'will_list_widget'):
|
||||
self.update_widget_combo(self.will_list_widget,field,new_value)
|
||||
if update_all or update_heirs_dialog and hasattr(self,'heir_list_widget'):
|
||||
self.update_widget_combo(self.heir_list_widget,field,new_value)
|
||||
|
||||
|
||||
def update_widget_combo(self,widget,field,value):
|
||||
try:
|
||||
widget.will_settings_widget.widgets[field].set_index(value)
|
||||
except Exception as _e:
|
||||
pass
|
||||
def update_widget_value(self, widget, field, value):
|
||||
try:
|
||||
widget.will_settings_widget.widgets[field].set_value(value)
|
||||
except Exception as _e:
|
||||
pass
|
||||
|
||||
def update_setting_widgets(
|
||||
self,
|
||||
new_value,
|
||||
field,
|
||||
update_all=False,
|
||||
update_will_dialog=False,
|
||||
update_heirs_dialog=False,
|
||||
):
|
||||
if update_all or update_heirs_dialog:
|
||||
self.update_widget_value(self.heir_list_widget, field, new_value)
|
||||
if update_all or update_will_dialog:
|
||||
self.update_widget_value(self.will_list_widget, field, new_value)
|
||||
self.will_settings[field] = new_value
|
||||
self.bal_plugin.WILL_SETTINGS.set(self.will_settings)
|
||||
|
||||
def init_heirs_to_locktime(self, multiverse=False):
|
||||
#pass
|
||||
for heir in self.heirs:
|
||||
h = self.heirs[heir]
|
||||
if not multiverse:
|
||||
self.heirs[heir] = [h[0], h[1], self.will_settings["locktime"]]
|
||||
|
||||
def init_class_variables(self):
|
||||
if not self.heirs:
|
||||
raise NoHeirsException(_("Heirs are not defined"))
|
||||
try:
|
||||
self.date_to_check = BalTimestamp(self.will_settings['threshold']).to_timestamp()
|
||||
# found = False
|
||||
self.locktime_blocks = self.bal_plugin.LOCKTIME_BLOCKS.get()
|
||||
self.current_block = Util.get_current_height(self.wallet.network)
|
||||
self.block_to_check = 0
|
||||
self.no_willexecutor = self.bal_plugin.NO_WILLEXECUTOR.get()
|
||||
self.willexecutors = Willexecutors.get_willexecutors(
|
||||
self.bal_plugin, update=True, bal_window=self, task=False
|
||||
)
|
||||
if self.date_to_check < datetime.now().timestamp():
|
||||
raise CheckAliveError(self.date_to_check)
|
||||
|
||||
self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get())
|
||||
|
||||
except Exception as e:
|
||||
log_error(e )
|
||||
_logger.error(f"init_class_variables: {e}")
|
||||
|
||||
raise e
|
||||
|
||||
def build_inheritance_transaction(self, ignore_duplicate=True, keep_original=True):
|
||||
try:
|
||||
if self.disable_plugin:
|
||||
_logger.info("plugin is disabled")
|
||||
return
|
||||
if not self.heirs:
|
||||
_logger.warning("not heirs {}".format(self.heirs))
|
||||
return
|
||||
try:
|
||||
self.init_class_variables()
|
||||
Will.check_amounts(
|
||||
self.heirs,
|
||||
self.willexecutors,
|
||||
self.window.wallet.get_utxos(),
|
||||
self.date_to_check,
|
||||
self.window.wallet.dust_threshold(),
|
||||
)
|
||||
except AmountException as e:
|
||||
self.show_warning(
|
||||
_(
|
||||
f"In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.{e}"
|
||||
)
|
||||
)
|
||||
except CheckAliveError:
|
||||
self.show_error(
|
||||
_(
|
||||
"CheckAlive is in the past please update it to a date in the future but less than locktime"
|
||||
)
|
||||
)
|
||||
return
|
||||
locktime = Util.parse_locktime_string(self.will_settings["locktime"])
|
||||
if locktime < self.date_to_check:
|
||||
self.show_error(_("locktime is lower than threshold"))
|
||||
return
|
||||
if not self.no_willexecutor:
|
||||
f = False
|
||||
for _k, we in self.willexecutors.items():
|
||||
if Willexecutors.is_selected(we):
|
||||
f = True
|
||||
if not f:
|
||||
self.show_error(
|
||||
_(" no backup transaction or willexecutor selected")
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
self.check_will()
|
||||
except WillExpiredException:
|
||||
self.invalidate_will()
|
||||
return
|
||||
except NoHeirsException:
|
||||
return
|
||||
except NotCompleteWillException as e:
|
||||
_logger.info("{}:{}".format(type(e), e))
|
||||
message = False
|
||||
if isinstance(e, HeirChangeException):
|
||||
message = "Heirs changed:"
|
||||
elif isinstance(e, WillExecutorNotPresent):
|
||||
message = "Will-Executor not present:"
|
||||
elif isinstance(e, WillexecutorChangeException):
|
||||
message = "Will-Executor changed"
|
||||
elif isinstance(e, TxFeesChangedException):
|
||||
message = "Txfees are changed"
|
||||
elif isinstance(e, HeirNotFoundException):
|
||||
message = "Heir not found"
|
||||
|
||||
if message:
|
||||
self.show_message(
|
||||
f"{_(message)}:\n {e}\n{_('will have to be built')}"
|
||||
)
|
||||
|
||||
_logger.info("build will")
|
||||
self.build_will(ignore_duplicate, keep_original)
|
||||
|
||||
try:
|
||||
self.check_will()
|
||||
for wid, _w in self.willitems.items():
|
||||
self.wallet.set_label(wid, "BAL Transaction")
|
||||
except WillExpiredException as e:
|
||||
self.invalidate_will()
|
||||
except NotCompleteWillException as e:
|
||||
self.show_error(
|
||||
"Error:{}\n {}".format(
|
||||
str(e),
|
||||
_("Please, check your heirs, locktime and threshold!"),
|
||||
)
|
||||
)
|
||||
|
||||
self.window.history_list.update()
|
||||
self.window.utxo_list.update()
|
||||
self.update_all()
|
||||
return self.willitems
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def show_transaction_real(
|
||||
self,
|
||||
tx: Transaction,
|
||||
*,
|
||||
parent: "ElectrumWindow",
|
||||
prompt_if_unsaved: bool = False,
|
||||
external_keypairs: Mapping[bytes, bytes] = None,
|
||||
payment_identifier: "PaymentIdentifier" = None,
|
||||
):
|
||||
try:
|
||||
d = TxDialog(
|
||||
tx,
|
||||
parent=parent,
|
||||
prompt_if_unsaved=prompt_if_unsaved,
|
||||
external_keypairs=external_keypairs,
|
||||
# payment_identifier=payment_identifier,
|
||||
)
|
||||
d.setWindowIcon(
|
||||
read_QIcon_from_bytes(self.bal_plugin.read_file("icons/bal16x16.png"))
|
||||
)
|
||||
except SerializationError as e:
|
||||
_logger.error("unable to deserialize the transaction")
|
||||
parent.show_critical(
|
||||
_("Electrum was unable to deserialize the transaction:") + "\n" + str(e)
|
||||
)
|
||||
else:
|
||||
d.show()
|
||||
return d
|
||||
|
||||
def show_transaction(self, tx=None, txid=None, parent=None):
|
||||
if not parent:
|
||||
parent = self.window
|
||||
if txid is not None and txid in self.willitems:
|
||||
tx = self.willitems[txid].tx
|
||||
if not tx:
|
||||
raise Exception(_("no tx"))
|
||||
return self.show_transaction_real(tx, parent=parent)
|
||||
|
||||
def invalidate_will(self):
|
||||
def on_success(result):
|
||||
if result:
|
||||
self.show_message(
|
||||
_(
|
||||
"Please sign and broadcast this transaction to invalidate current will"
|
||||
)
|
||||
)
|
||||
self.wallet.set_label(result.txid(), "BAL Invalidate")
|
||||
self.show_transaction(result)
|
||||
else:
|
||||
self.show_message(_("No transactions to invalidate"))
|
||||
|
||||
def on_failure(exec_info):
|
||||
log_error(exec_info, self.bal_window)
|
||||
|
||||
fee_per_byte = self.will_settings.get("baltx_fees", 1)
|
||||
task = partial(Will.invalidate_will, self.willitems, self.wallet, fee_per_byte)
|
||||
msg = _("Calculating Transactions")
|
||||
self.waiting_dialog = BalWaitingDialog(
|
||||
self, msg, task, on_success, on_failure, exe=False
|
||||
)
|
||||
self.waiting_dialog.exe()
|
||||
|
||||
def sign_transactions(self, password):
|
||||
try:
|
||||
txs = {}
|
||||
signed = None
|
||||
tosign = None
|
||||
|
||||
def get_message():
|
||||
msg = ""
|
||||
if signed:
|
||||
msg = _(f"signed: {signed}\n")
|
||||
return msg + _(f"signing: {tosign}")
|
||||
|
||||
for txid in Will.only_valid(self.willitems):
|
||||
wi = self.willitems[txid]
|
||||
tx = copy.deepcopy(wi.tx)
|
||||
if wi.get_status("COMPLETE"):
|
||||
txs[txid] = tx
|
||||
continue
|
||||
tosign = txid
|
||||
try:
|
||||
self.waiting_dialog.update(get_message())
|
||||
except Exception:
|
||||
pass
|
||||
for txin in tx.inputs():
|
||||
prevout = txin.prevout.to_json()
|
||||
if prevout[0] in self.willitems:
|
||||
change = self.willitems[prevout[0]].tx.outputs()[prevout[1]]
|
||||
txin._trusted_value_sats = change.value
|
||||
try:
|
||||
txin.script_descriptor = change.script_descriptor
|
||||
except Exception:
|
||||
pass
|
||||
txin.is_mine = True
|
||||
txin._TxInput__address = change.address
|
||||
txin._TxInput__scriptpubkey = change.scriptpubkey
|
||||
txin._TxInput__value_sats = change.value
|
||||
|
||||
self.wallet.sign_transaction(tx, password, ignore_warnings=True)
|
||||
signed = tosign
|
||||
# is_complete = False
|
||||
if tx.is_complete():
|
||||
# is_complete = True
|
||||
wi.set_status("COMPLETE", True)
|
||||
txs[txid] = tx
|
||||
except Exception:
|
||||
return None
|
||||
return txs
|
||||
|
||||
def get_wallet_password(self, message=None, parent=None):
|
||||
parent = self.window if not parent else parent
|
||||
password = None
|
||||
if self.wallet.has_keystore_encryption():
|
||||
password = self.bal_plugin.password_dialog(parent=parent, msg=message)
|
||||
if password is None:
|
||||
return False
|
||||
try:
|
||||
self.wallet.check_password(password)
|
||||
except Exception as e:
|
||||
self.show_error(str(e))
|
||||
password = self.get_wallet_password(message)
|
||||
return password
|
||||
|
||||
def on_close(self):
|
||||
try:
|
||||
if not self.disable_plugin:
|
||||
close_window = BalBuildWillDialog(self)
|
||||
close_window.build_will_task()
|
||||
self.save_willitems()
|
||||
self.heirs_tab.close()
|
||||
self.will_tab.close()
|
||||
self.tools_menu.removeAction(self.tools_menu.willexecutors_action)
|
||||
self.window.toggle_tab(self.heirs_tab)
|
||||
self.window.toggle_tab(self.will_tab)
|
||||
self.window.tabs.update()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def ask_password_and_sign_transactions(self, callback=None):
|
||||
def on_success(txs):
|
||||
if txs:
|
||||
for txid, tx in txs.items():
|
||||
self.willitems[txid].tx = copy.deepcopy(tx)
|
||||
self.will[txid] = self.willitems[txid].to_dict()
|
||||
try:
|
||||
self.will_list_widget.update()
|
||||
except Exception:
|
||||
pass
|
||||
if callback:
|
||||
try:
|
||||
callback()
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def on_failure(exec_info):
|
||||
log_error(exec_info, self.bal_window)
|
||||
|
||||
password = self.get_wallet_password()
|
||||
task = partial(self.sign_transactions, password)
|
||||
msg = _("Signing transactions...")
|
||||
self.waiting_dialog = BalWaitingDialog(
|
||||
self, msg, task, on_success, on_failure, exe=False
|
||||
)
|
||||
self.waiting_dialog.exe()
|
||||
|
||||
def broadcast_transactions(self, force=False):
|
||||
def on_success(sulcess):
|
||||
self.will_list_widget.update()
|
||||
if sulcess:
|
||||
_logger.info("error, some transaction was not sent")
|
||||
self.show_warning(_("Some transaction was not broadcasted"))
|
||||
return
|
||||
_logger.debug("OK, sulcess transaction was sent")
|
||||
self.show_message(
|
||||
_("All transactions are broadcasted to respective Will-Executors")
|
||||
)
|
||||
|
||||
def on_failure(exec_info):
|
||||
log_error(exec_info, self.bal_window)
|
||||
# a,b,c = err
|
||||
# _logger.error(f"fail to broadcast transactions:{err}")
|
||||
# _logger.error(f"error: {b}")
|
||||
# _logger.error("traceback ")
|
||||
# tb = c
|
||||
# while tb is not None:
|
||||
# frame = tb.tb_frame
|
||||
# _logger.error("file:", frame.f_code.co_filename)
|
||||
# _logger.error("name:", frame.f_code.co_name)
|
||||
# _logger.error("line:", tb.tb_lineno)
|
||||
# _logger.error("lasti:", tb.tb_lasti)
|
||||
# tb = tb.tb_next
|
||||
|
||||
task = partial(self.push_transactions_to_willexecutors, force)
|
||||
msg = _("Selecting Will-Executors")
|
||||
self.waiting_dialog = BalWaitingDialog(
|
||||
self, msg, task, on_success, on_failure, exe=False
|
||||
)
|
||||
self.waiting_dialog.exe()
|
||||
|
||||
def push_transactions_to_willexecutors(self, force=False):
|
||||
willexecutors = Willexecutors.get_willexecutor_transactions(self.willitems)
|
||||
|
||||
def getMsg(willexecutors):
|
||||
msg = "Broadcasting Transactions to Will-Executors:\n"
|
||||
for url in willexecutors:
|
||||
msg += f"{url}:\t{willexecutors[url]['broadcast_status']}\n"
|
||||
return msg
|
||||
|
||||
error = False
|
||||
for url in willexecutors:
|
||||
if self.waiting_dialog._stopping:
|
||||
return
|
||||
willexecutor = willexecutors[url]
|
||||
self.waiting_dialog.update(getMsg(willexecutors))
|
||||
if "txs" in willexecutor:
|
||||
try:
|
||||
if Willexecutors.push_transactions_to_willexecutor(
|
||||
willexecutors[url]
|
||||
):
|
||||
for wid in willexecutors[url]["txsids"]:
|
||||
self.willitems[wid].set_status("PUSHED", True)
|
||||
willexecutors[url]["broadcast_status"] = _("Success")
|
||||
else:
|
||||
for wid in willexecutors[url]["txsids"]:
|
||||
self.willitems[wid].set_status("PUSH_FAIL", True)
|
||||
error = True
|
||||
willexecutors[url]["broadcast_status"] = _("Failed")
|
||||
del willexecutor["txs"]
|
||||
except Willexecutors.AlreadyPresentException:
|
||||
for wid in willexecutor["txsids"]:
|
||||
if self.waiting_dialog._stopping:
|
||||
return
|
||||
self.waiting_dialog.update(
|
||||
"checking {} - {} : {}".format(
|
||||
self.willitems[wid].we["url"], wid, "Waiting"
|
||||
)
|
||||
)
|
||||
w = self.willitems[wid]
|
||||
w.set_check_willexecutor(
|
||||
Willexecutors.check_transaction(wid, w.we["url"])
|
||||
)
|
||||
self.waiting_dialog.update(
|
||||
"checked {} - {} : {}".format(
|
||||
self.willitems[wid].we["url"],
|
||||
wid,
|
||||
self.willitems[wid].get_status("CHECKED"),
|
||||
)
|
||||
)
|
||||
|
||||
if error:
|
||||
return True
|
||||
|
||||
def export_json_file(self, path):
|
||||
for wid in self.willitems:
|
||||
self.willitems[wid].set_status("EXPORTED", True)
|
||||
self.will[wid] = self.willitems[wid].to_dict()
|
||||
write_json_file(path, self.will)
|
||||
|
||||
def export_will(self):
|
||||
try:
|
||||
export_meta_gui(self.window, "will.json", self.export_json_file)
|
||||
except Exception as e:
|
||||
self.show_error(str(e))
|
||||
raise e
|
||||
|
||||
def import_will(self):
|
||||
def sulcess():
|
||||
self.will_list_widget.update_will(self.willitems)
|
||||
|
||||
import_meta_gui(self.window, _("will"), self.import_json_file, sulcess)
|
||||
|
||||
def import_json_file(self, path):
|
||||
try:
|
||||
data = read_json_file(path)
|
||||
willitems = {}
|
||||
for k, v in data.items():
|
||||
data[k]["tx"] = tx_from_any(v["tx"])
|
||||
willitems[k] = WillItem(data[k], _id=k)
|
||||
self.update_will(willitems)
|
||||
except Exception as e:
|
||||
raise e
|
||||
# raise FileImportFailed(_("Invalid will file"))
|
||||
|
||||
def check_transactions_task(self, will):
|
||||
start = time.time()
|
||||
for wid, w in will.items():
|
||||
if self.waiting_dialog._stopping:
|
||||
return
|
||||
if w.we:
|
||||
self.waiting_dialog.update(
|
||||
"checking transaction: {}\n willexecutor: {}".format(wid, w.we["url"])
|
||||
)
|
||||
|
||||
w.set_check_willexecutor(Willexecutors.check_transaction(wid, w.we["url"]))
|
||||
|
||||
if time.time() - start < 3:
|
||||
time.sleep(3 - (time.time() - start))
|
||||
|
||||
def check_transactions(self, will):
|
||||
def on_success(result):
|
||||
if hasattr(self,"waiting_dialog"):
|
||||
del self.waiting_dialog
|
||||
self.update_all()
|
||||
pass
|
||||
|
||||
def on_failure(exec_info):
|
||||
log_error(exec_info, self)
|
||||
# _logger.error(f"error checking transactions {e}")
|
||||
# pass
|
||||
|
||||
task = partial(self.check_transactions_task, will)
|
||||
msg = _("Check Transaction")
|
||||
self.waiting_dialog = BalWaitingDialog(
|
||||
self, msg, task, on_success, on_failure, exe=False
|
||||
)
|
||||
self.waiting_dialog.exe()
|
||||
|
||||
def update_willexecutor_list_widget(self, parent, willexecutors):
|
||||
try:
|
||||
parent.willexecutors_list.update(willexecutors)
|
||||
parent.will_executor_list_widget.update()
|
||||
except Exception as e:
|
||||
_logger.error(f"impossible to update will_executor_list_widget {e}")
|
||||
self.will_executors.update()
|
||||
|
||||
def download_list(self, willexecutors, fn_on_success, fn_on_failure=None):
|
||||
|
||||
def on_success(result):
|
||||
self.willexecutors.update(result)
|
||||
fn_on_success(result)
|
||||
|
||||
def on_failure(exec_info):
|
||||
fn_on_failure(exec_info)
|
||||
|
||||
if fn_on_failure is None:
|
||||
fn_on_failure = log_error
|
||||
welist_server = self.bal_plugin.WELIST_SERVER.get()
|
||||
task = partial(Willexecutors.download_list, willexecutors, welist_server)
|
||||
msg = _(f"Downloading willexecutors list from {welist_server}")
|
||||
self.waiting_dialog = BalWaitingDialog(
|
||||
self, msg, task, on_success, on_failure, exe=False
|
||||
)
|
||||
self.waiting_dialog.exe()
|
||||
|
||||
def ping_willexecutors_task(self, wes):
|
||||
_logger.info("ping willexecutots task")
|
||||
pinged = []
|
||||
failed = []
|
||||
|
||||
def get_title():
|
||||
msg = _("Ping Will-Executors:")
|
||||
msg += "\n\n"
|
||||
for url in wes:
|
||||
urlstr = "{:<50}: ".format(url[:50])
|
||||
if url in pinged:
|
||||
urlstr += "Ok"
|
||||
elif url in failed:
|
||||
urlstr += "Ko"
|
||||
else:
|
||||
urlstr += "--"
|
||||
urlstr += "\n"
|
||||
msg += urlstr
|
||||
|
||||
return msg
|
||||
|
||||
for url, we in wes.items():
|
||||
try:
|
||||
self.waiting_dialog.update(get_title())
|
||||
except Exception:
|
||||
pass
|
||||
wes[url] = Willexecutors.get_info_task(url, we)
|
||||
if wes[url]["status"] == "KO":
|
||||
failed.append(url)
|
||||
else:
|
||||
pinged.append(url)
|
||||
|
||||
def ping_willexecutors(self, wes, fn_on_success, fn_on_failure=None):
|
||||
def on_success(result):
|
||||
fn_on_success(result)
|
||||
|
||||
def on_failure(exec_info):
|
||||
fn_on_failure(exec_info)
|
||||
|
||||
if not fn_on_failure:
|
||||
fn_on_failure = log_error
|
||||
_logger.info("ping willexecutors")
|
||||
task = partial(self.ping_willexecutors_task, wes)
|
||||
msg = _("Ping Will-Executors")
|
||||
self.waiting_dialog = BalWaitingDialog(
|
||||
self, msg, task, on_success, on_failure, exe=False
|
||||
)
|
||||
self.waiting_dialog.exe()
|
||||
|
||||
def preview_modal_dialog(self):
|
||||
self.dw = WillDetailDialog(self)
|
||||
self.dw.show()
|
||||
|
||||
def update_all(self):
|
||||
try:
|
||||
Will.add_willtree(self.willitems)
|
||||
all_utxos = self.wallet.get_utxos()
|
||||
utxos_list = Will.utxos_strs(all_utxos)
|
||||
Will.check_invalidated(self.willitems, utxos_list, self.wallet)
|
||||
|
||||
self.will_list_widget.update_will(self.willitems)
|
||||
self.heirs_tab.update()
|
||||
self.will_tab.update()
|
||||
self.will_list_widget.update()
|
||||
except Exception as e:
|
||||
_logger.error(f"error while updating window: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user