8 Commits

7 changed files with 387 additions and 125 deletions

View File

@@ -1 +1 @@
0.2.6
0.2.8

23
bal.py
View File

@@ -47,18 +47,19 @@ class BalConfig:
class BalPlugin(BasePlugin):
LATEST_VERSION = "1"
KNOWN_VERSIONS = ("0", "1")
assert LATEST_VERSION in KNOWN_VERSIONS
def version():
_version=None
__version__ = "0.2.7" #AUTOMATICALLY GENERATED DO NOT EDIT
def version(self):
if not self._version:
try:
f = ""
with open("VERSION", "r") as fi:
f = str(fi.readline())
return f
except Exception:
return "unknown"
with open("{}/VERSION".format(self.plugin_dir), "r") as fi:
f = str(fi.read())
self._version = f.strip()
except Exception as e:
_logger.error(f"failed to get version: {e}")
self._version="unknown"
return self._version
SIZE = (159, 97)
@@ -108,7 +109,7 @@ class BalPlugin(BasePlugin):
"base_fee": 100000,
"status": "New",
"info": "Bitcoin After Life Will Executor",
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"address": "bc1qusymuetsz2psaqzqxv8qmzcy64d9meckj3lxxf",
"selected": True,
}
},

View File

@@ -424,6 +424,8 @@ class Heirs(dict, Logger):
def prepare_lists(
self, balance, total_fees, wallet, willexecutor=False, from_locktime=0
):
if balance<total_fees or balance < wallet.dust_threshold():
raise BalanceTooLowException(balance,wallet.dust_threshold(),total_fees)
willexecutors_amount = 0
willexecutors = {}
heir_list = {}
@@ -461,7 +463,6 @@ class Heirs(dict, Logger):
percent_amount,
fixed_amount_with_dust,
) = self.fixed_percent_lists_amount(from_locktime, wallet.dust_threshold())
if fixed_amount > newbalance:
fixed_amount = self.normalize_perc(
fixed_heirs, newbalance, fixed_amount, wallet
@@ -471,14 +472,12 @@ class Heirs(dict, Logger):
heir_list.update(fixed_heirs)
newbalance -= fixed_amount
if newbalance > 0:
perc_amount = self.normalize_perc(
percent_heirs, newbalance, percent_amount, wallet
)
newbalance -= perc_amount
heir_list.update(percent_heirs)
if newbalance > 0:
newbalance += fixed_amount
fixed_amount = self.normalize_perc(
@@ -537,7 +536,7 @@ class Heirs(dict, Logger):
break
elif 0 <= j:
url, willexecutor = willexecutorsitems[j]
if not Willexecutors.is_selected(willexecutor):
if not Willexecutors.is_selected(willexecutor) or willexecutor["base_fee"] < wallet.dust_threshold():
continue
else:
willexecutor["url"] = url
@@ -782,3 +781,10 @@ class WillExecutorFeeException(Exception):
return "WillExecutorFeeException: {} fee:{}".format(
self.willexecutor["url"], self.willexecutor["base_fee"]
)
class BalanceTooLowException(Exception):
def __init__(self,balance, dust_threshold, fees):
self.balance=balance
self.dust_threshold = dust_threshold
self.fees = fees
def __str__(self):
return f"Balance too low, balance: {self.balance}, dust threshold: {self.dust_threshold}, fees: {self.fees}"

View File

@@ -1,7 +1,7 @@
{
"name": "BAL",
"fullname": "Bitcoin After Life",
"description": "Provides free and decentralized inheritance support<br> Version: 0.2.5",
"description": "Provides free and decentralized inheritance support<br> Version: 0.2.7",
"author":"Svatantrya",
"available_for": ["qt"],
"icon":"icons/bal32x32.png"

430
qt.py
View File

@@ -15,13 +15,14 @@ from datetime import datetime
from decimal import Decimal
from functools import partial
from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Union
import traceback
try:
QT_VERSION = sys._GUI_QT_VERSION
except Exception:
QT_VERSION = 6
if QT_VERSION == 5:
from PyQt5.QtCore import QThread, QCoreApplication
from PyQt5.QtCore import (
QDateTime,
QModelIndex,
@@ -57,6 +58,7 @@ if QT_VERSION == 5:
QWidget,
)
else: # QT6
from PyQt6.QtCore import QThread, QCoreApplication
from PyQt6.QtCore import (
QDateTime,
QModelIndex,
@@ -495,8 +497,8 @@ class BalWindow(Logger):
Util.copy(self.will_settings, self.bal_plugin.default_will_settings())
self.logger.debug("not_will_settings {}".format(self.will_settings))
self.bal_plugin.validate_will_settings(self.will_settings)
self.heir_list.update_will_settings()
self.heir_list.update()
self.heir_list_widget.update_will_settings()
self.heir_list_widget.update()
def init_wizard(self):
wizard_dialog = BalWizardDialog(self)
@@ -507,9 +509,9 @@ class BalWindow(Logger):
self.willexecutor_dialog.show()
def create_heirs_tab(self):
self.heir_list = HeirList(self, self.window)
self.heir_list_widget = HeirListWidget(self, self.window)
tab = self.window.create_list_tab(self.heir_list)
tab = self.window.create_list_tab(self.heir_list_widget)
tab.is_shown_cv = shown_cv(False)
return tab
@@ -601,14 +603,6 @@ class BalWindow(Logger):
except Exception as e:
self.show_error(str(e))
# def export_inheritance_handler(self,path):
# txs = self.build_inheritance_transaction(ignore_duplicate=True, keep_original=False)
# with open(path,"w") as f:
# for tx in txs:
# tx['status']+="."+BalPlugin.STATUS_EXPORTED
# f.write(str(tx['tx']))
# f.write('\n')
def set_heir(self, heir):
heir = list(heir)
if not self.bal_plugin.ENABLE_MULTIVERSE.get():
@@ -616,7 +610,7 @@ class BalWindow(Logger):
h = Heirs.validate_heir(heir[0], heir[1:])
self.heirs[heir[0]] = h
self.heir_list.update()
self.heir_list_widget.update()
return True
def delete_heirs(self, heirs):
@@ -627,14 +621,12 @@ class BalWindow(Logger):
_logger.debug(f"error deleting heir: {heir} {e}")
pass
self.heirs.save()
self.heir_list.update()
self.heir_list_widget.update()
return True
def import_heirs(
self,
):
def import_heirs(self):
import_meta_gui(
self.window, _("heirs"), self.heirs.import_file, self.heir_list.update
self.window, _("heirs"), self.heirs.import_file, self.heir_list_widget.update
)
def export_heirs(self):
@@ -748,8 +740,7 @@ class BalWindow(Logger):
def init_class_variables(self):
if not self.heirs:
raise NoHeirsException()
return
raise NoHeirsException(_("Heirs are not defined"))
try:
self.date_to_check = Util.parse_locktime_string(
self.will_settings["threshold"]
@@ -757,11 +748,13 @@ class BalWindow(Logger):
# 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 = self.current_block + self.locktime_blocks
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 CheckAliveException(self.date_to_check)
self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get())
except Exception as e:
@@ -776,8 +769,8 @@ class BalWindow(Logger):
if not self.heirs:
self.logger.warning("not heirs {}".format(self.heirs))
return
self.init_class_variables()
try:
self.init_class_variables()
Will.check_amounts(
self.heirs,
self.willexecutors,
@@ -788,9 +781,12 @@ class BalWindow(Logger):
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.\n{e}"
f"In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.{e}"
)
)
except CheckAliveException:
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"))
@@ -1036,7 +1032,18 @@ class BalWindow(Logger):
)
def on_failure(err):
a,b,c = err
self.logger.error(f"fail to broadcast transactions:{err}")
self.logger.error(f"error: {b}")
self.logger.error(f"traceback ")
tb = c
while tb is not None:
frame = tb.tb_frame
self.logger.error("file:", frame.f_code.co_filename)
self.logger.error("name:", frame.f_code.co_name)
self.logger.error("line:", tb.tb_lineno)
self.logger.error("lasti:", tb.tb_lasti)
tb = tb.tb_next
task = partial(self.push_transactions_to_willexecutors, force)
msg = _("Selecting Will-Executors")
@@ -1079,7 +1086,13 @@ class BalWindow(Logger):
self.willitems[wid].we["url"], wid, "Waiting"
)
)
self.willitems[wid].check_willexecutor()
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"],
@@ -1128,7 +1141,9 @@ class BalWindow(Logger):
self.waiting_dialog.update(
"checking transaction: {}\n willexecutor: {}".format(wid, w.we["url"])
)
w.check_willexecutor()
w.set_check_willexecutor(Willexecutors.check_transaction(wid, w.we["url"]))
if time.time() - start < 3:
time.sleep(3 - (time.time() - start))
@@ -1189,7 +1204,7 @@ class BalWindow(Logger):
def on_success(result):
# del self.waiting_dialog
try:
parent.willexecutor_list.update()
parent.will_executor_list_widget.update()
except Exception as e:
pass
try:
@@ -1214,10 +1229,20 @@ class BalWindow(Logger):
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.update_will(self.willitems)
self.heirs_tab.update()
self.will_tab.update()
self.will_list.update()
except Exception as e:
_logger.error(f"error while updating window: {e}")
def add_widget(grid, label, widget, row, help_):
@@ -1246,6 +1271,30 @@ class _LockTimeEditor:
return cls.min_allowed_value <= x <= cls.max_allowed_value
<<<<<<< HEAD
=======
"""
HeirsLockTimeEdit - A custom QWidget for editing locktime values in the context of heirs distribution.
This widget combines raw locktime editing with date-based selection and provides
additional functionality for managing locktime values in a heir inheritance scenario.
Features:
- Supports both raw locktime values and human-readable date formats
- Emits valueEdited signal when the locktime value changes
- Provides threshold-based validation for locktime values
- Integrates with heir distribution workflows
Attributes:
valueEdited (pyqtSignal): Signal emitted when the locktime value is edited
locktime_threshold (int): Minimum threshold value for locktime (default: 50000000)
Args:
parent: Optional parent QWidget
default_index (int): Default index for the combo box (default: 1)
"""
>>>>>>> origin/doc
class HeirsLockTimeEdit(QWidget, _LockTimeEditor):
valueEdited = pyqtSignal()
locktime_threshold = 50000000
@@ -1605,17 +1654,19 @@ class BalWizardDialog(BalDialog):
)
def on_accept(self):
self.bal_window.update_all()
pass
def on_reject(self):
pass
def on_close(self):
self.bal_window.update_all()
pass
def closeEvent(self, event):
self.bal_window.update_all()
self.bal_window.heir_list.update_will_settings()
self.bal_window.heir_list_widget.update_will_settings()
pass
@@ -1692,7 +1743,7 @@ class BalWizardHeirsWidget(BalWizardWidget):
)
def get_content(self):
self.heirs_list = HeirList(self.bal_window, self)
self.heir_list_widget = HeirListWidget(self.bal_window, self)
button_add = QPushButton(_("Add"))
button_add.clicked.connect(self.add_heir)
button_import = QPushButton(_("Import"))
@@ -1701,20 +1752,20 @@ class BalWizardHeirsWidget(BalWizardWidget):
button_export.clicked.connect(self.export_to_file)
widget = QWidget()
vbox = QVBoxLayout(widget)
vbox.addWidget(self.heirs_list)
vbox.addWidget(self.heir_list_widget)
vbox.addLayout(Buttons(button_add, button_import, button_export))
return widget
def import_from_file(self):
self.bal_window.import_heirs()
self.heirs_list.update()
self.heir_list_widget.update()
def export_to_file(self):
self.bal_window.export_heirs()
def add_heir(self):
self.bal_window.new_heir_dialog()
self.heirs_list.update()
self.heir_list_widget.update()
def validate(self):
return True
@@ -1785,7 +1836,7 @@ class BalWizardWEDownloadWidget(BalWizardWidget):
_logger.debug(f"Failed to download willexecutors list {fail}")
pass
task = partial(Willexecutors.download_list, self.bal_window.bal_plugin,self.bal_window.willexecutors)
task = partial(Willexecutors.download_list,self.bal_window.willexecutors)
msg = _("Downloading Will-Executors list")
self.waiting_dialog = BalWaitingDialog(
self.bal_window, msg, task, on_success, on_failure, exe=False
@@ -2042,18 +2093,78 @@ class BalBuildWillDialog(BalDialog):
COLOR_OK = "#05ad05"
def __init__(self, bal_window, parent=None):
<<<<<<< HEAD
=======
"""Initialize the Build Will dialog.
Args:
bal_window (BalWindow): The main application window
parent (QWidget, optional): Parent widget. Defaults to None.
Initializes:
- Main UI components (message label, container widget)
- Message queue system with debounce timer
- Layout management
- Network connection
"""
>>>>>>> origin/doc
if not parent:
parent = bal_window.window
BalDialog.__init__(self, parent, bal_window.bal_plugin, _("Building Will"))
self.parent = parent
self.updatemessage.connect(self.update)
self.updatemessage.connect(self.msg_update)
self.bal_window = bal_window
self.bal_plugin = bal_window.bal_plugin
<<<<<<< HEAD
self.message_label = QLabel(_("Building Will:"))
self.vbox = QVBoxLayout(self)
self.vbox.addWidget(self.message_label)
self.qwidget = QWidget()
self.vbox.addWidget(self.qwidget)
self.vbox.addWidget(self.message_label,0)
self.qwidget = QWidget(self)
self.vbox.addWidget(self.qwidget,1)
self.labelsbox=QVBoxLayout(self.qwidget)
self.setMinimumWidth(600)
self.setMinimumHeight(100)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
self.labels = []
=======
# Main message label
self.message_label = QLabel(_("Building Will:"))
self.vbox = QVBoxLayout(self)
self.vbox.addWidget(self.message_label, 0)
# Container for dynamic messages
self.qwidget = QWidget(self)
self.vbox.addWidget(self.qwidget, 1)
# Layout for messages with reduced spacing
self.labelsbox = QVBoxLayout(self.qwidget)
self.labelsbox.setContentsMargins(0, 0, 0, 0)
self.labelsbox.setSpacing(4) # Reduced spacing between messages
# Set minimum dimensions
self.setMinimumWidth(600)
self.setMinimumHeight(100)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
# Message queue implementation for efficient updates
self._message_queue = [] # Thread-safe message queue
self._message_timer = QTimer(self)
self._message_timer.setSingleShot(True)
self._message_timer.setInterval(50) # Debounce interval: 50ms
self._message_timer.timeout.connect(self._process_message_queue)
# Other initialization
self.labels = [] # Immediate message storage
self.check_row = None
self.inval_row = None
self.build_row = None
self.sign_row = None
self.push_row = None
self.network = Network.get_instance()
self._stopping = False
>>>>>>> origin/doc
self.check_row = None
self.inval_row = None
self.build_row = None
@@ -2079,13 +2190,26 @@ class BalBuildWillDialog(BalDialog):
self.exec()
def task_phase1(self):
txs=None
_logger.debug("close plugin phase 1 started")
varrow = self.msg_set_status("checking variables")
try:
self.bal_window.init_class_variables()
except NoHeirsException:
_logger.error("no heirs exception")
return False, None
varrow = self.msg_set_status("checking variables")
except CheckAliveException as cae:
fee_per_byte = self.bal_window.will_settings.get("baltx_fees", 1)
tx = Will.invalidate_will(
self.bal_window.willitems, self.bal_window.wallet, fee_per_byte
)
if tx:
_logger.debug("during phase1 CAE: {}, Continue to invalidate".format(cae))
self.msg_set_checking(self.msg_warning("Check Alive Threshold Passed: you have to Invalidate your old Will"))
else:
raise cae
return None, tx
except Exception as e:
raise e
try:
_logger.debug("checking variables")
Will.check_amounts(
@@ -2121,9 +2245,10 @@ class BalBuildWillDialog(BalDialog):
return None, Will.invalidate_will(
self.bal_window.willitems, self.bal_window.wallet, fee_per_byte
)
except NoHeirsException:
except NoHeirsException as e:
_logger.debug("no heirs")
self.msg_set_checking("No Heirs")
raise e
except NotCompleteWillException as e:
_logger.debug(f"not complete {e} true")
message = False
@@ -2147,13 +2272,15 @@ class BalBuildWillDialog(BalDialog):
if have_to_build:
self.msg_set_building()
try:
if not self.bal_window.build_will():
txs = self.bal_window.build_will()
if not txs:
self.msg_set_status(
_("Balance is too low. No transaction was built"),
None,
_("Skipped"),
self.COLOR_ERROR,
)
return False,None
self.bal_window.check_will()
for wid in Will.only_valid(self.bal_window.willitems):
@@ -2185,12 +2312,14 @@ class BalBuildWillDialog(BalDialog):
if not self.bal_window.willitems[wid].get_status("COMPLETE"):
have_to_sign = True
break
return have_to_sign, None
return have_to_sign, txs
def on_accept(self):
self.bal_window.update_all()
pass
def on_accept_phase2(self):
self.bal_window.update_all()
pass
def on_error_push(self):
@@ -2228,6 +2357,7 @@ class BalBuildWillDialog(BalDialog):
self.msg_set_pushing(_("Broadcasting"))
retry = False
try:
willexecutors = Willexecutors.get_willexecutor_transactions(
self.bal_window.willitems
)
@@ -2257,7 +2387,10 @@ class BalBuildWillDialog(BalDialog):
self.bal_window.willitems[wid].we["url"], wid, "Waiting"
)
)
self.bal_window.willitems[wid].check_willexecutor()
self.bal_plugin = self.bal_window.bal_plugin
w = self.bal_window.willitems[wid]
w.set_check_willexecutor(Willexecutors.check_transaction(wid, w.we["url"]))
row = self.msg_edit_row(
"checked {} - {} : {}".format(
self.bal_window.willitems[wid].we["url"],
@@ -2268,7 +2401,6 @@ class BalBuildWillDialog(BalDialog):
)
except Exception as e:
_logger.error(f"loop push error:{e}")
raise e
if retry:
@@ -2306,12 +2438,11 @@ class BalBuildWillDialog(BalDialog):
on_error=self.on_error_phase1,
)
def on_error(self, error):
_logger.error(error)
pass
def on_success_phase1(self, result):
self.have_to_sign, tx = list(result)
#if not tx:
# self.msg_edit_row(self.msg_error("Error, no tx was built"))
# return
_logger.debug("have to sign {}".format(self.have_to_sign))
password = None
if self.have_to_sign is None:
@@ -2359,7 +2490,6 @@ class BalBuildWillDialog(BalDialog):
self.close()
def closeEvent(self, event):
self.bal_window.update_all()
self._stopping = True
self.thread.stop()
@@ -2388,15 +2518,26 @@ class BalBuildWillDialog(BalDialog):
self.msg_set_pushing(self.msg_ok())
except Exception as e:
td = traceback.format_exc()
self.msg_set_pushing(self.msg_error(e))
self.msg_edit_row(self.msg_ok())
self.wait(5)
def on_error(self, error):
_logger.error(error)
pass
def on_error_phase1(self, error):
_logger.error(f"error phase1: {error}")
self.bal_window.update_all()
a,b,c = error
self.msg_edit_row(self.msg_error(f"Error: {b}"))
_logger.error(f"error phase1: {b}")
def on_error_phase2(self, error):
_logger.error(f"error phase2: { error}")
self.bal_window.upade_all()
a,b,c = error
self.msg_edit_row(self.msg_error(f"Error: {b}"))
_logger.error(f"error phase2: {b}")
def msg_set_checking(self, status="Waiting", row=None):
row = self.check_row if row is None else row
@@ -2448,10 +2589,11 @@ class BalBuildWillDialog(BalDialog):
def msg_edit_row(self, line, row=None):
try:
self.labels[row] = line
except Exception:
except Exception as e:
self.labels.append(line)
row = len(self.labels) - 1
self.updatemessage.emit()
return row
@@ -2463,13 +2605,92 @@ class BalBuildWillDialog(BalDialog):
pass
self.updatemessage.emit()
def update(self):
self.vbox.removeWidget(self.qwidget)
self.qwidget = QWidget(self)
labelsbox = QVBoxLayout(self.qwidget)
def clear_layout(self,layout):
while layout.count():
item = layout.takeAt(0)
w = item.widget()
if w:
w.setParent(None)
w.deleteLater()
def msg_update(self):
<<<<<<< HEAD
self.clear_layout(self.labelsbox)
for label in self.labels:
labelsbox.addWidget(QLabel(label))
self.vbox.addWidget(self.qwidget)
label=label.replace("\n","<br>")
qlabel=QLabel(label)
self.labelsbox.addWidget(QLabel(label),1)
self.setMinimumHeight(30*(len(self.labels)+2))
=======
"""Updates the UI with new messages using a debounced queue system.
This method implements the following logic:
1. Adds all pending messages to the queue
2. Clears the immediate message storage
3. Starts the debounce timer if not already active
The actual UI update happens in _process_message_queue after the
debounce interval to prevent excessive UI updates.
Note:
Thread-safe operation - can be called from any thread
"""
self._message_queue.extend(self.labels)
self.labels = [] # Clear immediate labels after queuing
if not self._message_timer.isActive():
self._message_timer.start()
def _process_message_queue(self):
"""Processes queued messages with debounce for efficient UI updates.
This method:
1. Clears the existing layout
2. Processes all queued messages
3. Updates the UI once with all new messages
4. Resets the queue
5. Adjusts dialog height based on content
The debounce interval (50ms) ensures rapid message bursts are
processed in a single batch, reducing UI flicker.
Note:
Called automatically by QTimer after debounce interval
"""
if not self._message_queue:
return
# Clear existing layout
self.clear_layout(self.labelsbox)
# Process all queued messages
for text in self._message_queue:
try:
# Format text for rich display
formatted_text = text.replace("\n", "<br>")
# Create label with proper settings
label = QLabel(formatted_text)
label.setWordWrap(True)
label.setTextFormat(Qt.TextFormat.RichText)
label.setOpenExternalLinks(False) # Security
# Set size policy
label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
# Add to layout
self.labelsbox.addWidget(label)
except Exception as e:
# Log errors without interrupting processing
import logging
logging.error(f"Error creating label in BalBuildWillDialog: {e}")
# Reset queue and update dimensions
self._message_queue = []
self.setMinimumHeight(min(30 * (len(self.labels) + 2), 400)) # Max height limit
>>>>>>> origin/doc
def get_text(self):
return self.message_label.text()
@@ -2477,7 +2698,7 @@ class BalBuildWillDialog(BalDialog):
pass
class HeirList(MyTreeView, MessageBoxMixin):
class HeirListWidget(MyTreeView, MessageBoxMixin):
class Columns(MyTreeView.BaseColumnsEnum):
NAME = enum.auto()
ADDRESS = enum.auto()
@@ -2492,9 +2713,16 @@ class HeirList(MyTreeView, MessageBoxMixin):
ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1000
ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 1001
ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 4000
key_role = ROLE_HEIR_KEY
def createEditor(self, parent, option, index):
return QLineEdit(parent)
def setEditorData(self, editor, index):
editor.setText(index.data())
def setModelData(self, editor, model, index):
model.setData(index, editor.text())
def __init__(self, bal_window: "BalWindow", parent):
super().__init__(
parent=parent,
@@ -2509,6 +2737,7 @@ class HeirList(MyTreeView, MessageBoxMixin):
self.decimal_point = bal_window.window.get_decimal_point()
self.bal_window = bal_window
try:
self.setModel(QStandardItemModel(self))
self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder)
@@ -2592,12 +2821,6 @@ class HeirList(MyTreeView, MessageBoxMixin):
menu.addAction(_("Delete"), lambda: self.delete_heirs(selected_keys))
menu.exec(self.viewport().mapToGlobal(position))
# def get_selected_keys(self):
# selected_keys = []
# for s_idx in self.selected_in_column(self.Columns.NAME):
# sel_key = self.model().itemFromIndex(s_idx).data(0)
# selected_keys.append(sel_key)
# return selected_keys
def update(self):
current_key = self.get_role_data_for_current_item(
col=self.Columns.NAME, role=self.ROLE_HEIR_KEY
@@ -2747,6 +2970,7 @@ class HeirList(MyTreeView, MessageBoxMixin):
self.heir_tx_fees.setValue(int(self.bal_window.will_settings["baltx_fees"]))
except Exception as e:
pass
_logger.debug(f"Exception update_will_settings {e}")
def build_transactions(self):
@@ -2965,16 +3189,16 @@ class PreviewList(MyTreeView):
wizard = QPushButton(_("Setup Wizard"))
wizard.clicked.connect(self.bal_window.init_wizard)
display = QPushButton(_("Display"))
display.clicked.connect(self.bal_window.preview_modal_dialog)
#display = QPushButton(_("Display"))
#display.clicked.connect(self.bal_window.preview_modal_dialog)
display = QPushButton(_("Refresh"))
display.clicked.connect(self.check)
refresh = QPushButton(_("Refresh"))
refresh.clicked.connect(self.check)
widget = QWidget()
hlayout = QHBoxLayout(widget)
hlayout.addWidget(wizard)
hlayout.addWidget(display)
hlayout.addWidget(refresh)
toolbar.insertWidget(2, widget)
self.menu = menu
@@ -3012,13 +3236,6 @@ class PreviewList(MyTreeView):
self.update()
def check(self):
Will.add_willtree(self.bal_window.willitems)
all_utxos = self.bal_window.wallet.get_utxos()
utxos_list = Will.utxos_strs(all_utxos)
Will.check_invalidated(
self.bal_window.willitems, utxos_list, self.bal_window.wallet
)
close_window = BalBuildWillDialog(self.bal_window)
close_window.build_will_task()
@@ -3065,7 +3282,10 @@ class PreviewDialog(BalDialog, MessageBoxMixin):
self.size_label = QLabel()
self.transactions_list = PreviewList(self.bal_window, self.will)
try:
self.bal_window.init_class_variables()
except Exception as e:
_logger.error(f"PreviewDialog Exception: {e}")
self.check_will()
vbox = QVBoxLayout(self)
@@ -3311,7 +3531,7 @@ class WillWidget(QWidget):
hlayout.addWidget(WillWidget(w, parent=parent))
class WillExecutorList(MyTreeView):
class WillExecutorListWidget(MyTreeView):
class Columns(MyTreeView.BaseColumnsEnum):
SELECTED = enum.auto()
URL = enum.auto()
@@ -3556,7 +3776,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
self.willexecutors_list = Willexecutors.get_willexecutors(self.bal_plugin)
self.size_label = QLabel()
self.willexecutor_list = WillExecutorList(self)
self.will_executor_list_widget = WillExecutorListWidget(self)
vbox = QVBoxLayout(self)
vbox.addWidget(self.size_label)
@@ -3573,7 +3793,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
hbox.addWidget(spacer_widget)
vbox.addWidget(widget)
vbox.addWidget(self.willexecutor_list)
vbox.addWidget(self.will_executor_list_widget)
buttonbox = QHBoxLayout()
b = QPushButton(_("Add"))
@@ -3597,7 +3817,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
buttonbox.addWidget(b)
vbox.addLayout(buttonbox)
# self.willexecutor_list.update()
# self.will_executor_list_widget.update()
def add(self):
self.willexecutors_list["http://localhost:8080"] = {
@@ -3605,11 +3825,11 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
"base_fee": 0,
"status": "-1",
}
self.willexecutor_list.update()
self.will_executor_list_widget.update()
def download_list(self):
self.willexecutors_list.update(Willexecutors.download_list(self.bal_plugin,self.bal_window.willexecutors))
self.willexecutor_list.update()
self.willexecutors_list.update(Willexecutors.download_list(self.bal_window.willexecutors))
self.will_executor_list_widget.update()
def export_file(self, path):
Util.export_meta_gui(
@@ -3632,13 +3852,13 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
wes = self.willexecutors_list
self.bal_window.ping_willexecutors(wes, self.parent)
self.willexecutors_list.update(wes)
self.willexecutor_list.update()
self.will_executor_list_widget.update()
def import_json_file(self, path):
data = read_json_file(path)
data = self._validate(data)
self.willexecutors_list.update(data)
self.willexecutor_list.update()
self.will_executor_list_widget.update()
# TODO validate willexecutor json import file
def _validate(self, data):
@@ -3662,10 +3882,10 @@ class WillExecutorDialog(BalDialog, MessageBoxMixin):
self.setMinimumSize(1000, 200)
vbox = QVBoxLayout(self)
self.willexecutor_list = WillExecutorWidget(
self.will_executor_list_widget = WillExecutorWidget(
self, self.bal_window, self.willexecutors_list
)
vbox.addWidget(self.willexecutor_list)
vbox.addWidget(self.will_executor_list_widget)
def is_hidden(self):
return self.isMinimized() or self.isHidden()
@@ -3682,3 +3902,29 @@ class WillExecutorDialog(BalDialog, MessageBoxMixin):
def closeEvent(self, event):
event.accept()
class CheckAliveException(Exception):
def __init__(self,timestamp_to_check):
self.timestamp_to_check = timestamp_to_check
def __str__(self):
<<<<<<< HEAD
=======
def __del__(self):
"""Explicit cleanup to prevent memory leaks.
This destructor ensures proper cleanup of:
- Message queue timer
- All widgets in the layout
- Network connections
Called automatically when the dialog is destroyed.
"""
if hasattr(self, '_message_timer') and self._message_timer:
self._message_timer.stop()
self._message_timer.deleteLater()
self.clear_layout(self.labelsbox)
>>>>>>> origin/doc
return "Check alive expired please update it: {}".format(datetime.fromtimestamp(self.timestamp_to_check).isoformat())

21
will.py
View File

@@ -339,6 +339,7 @@ class Will:
utxos = wallet.get_utxos()
filtered_inputs = []
prevout_to_spend = []
current_height = Util.get_current_height(wallet.network)
for prevout_str, ws in inputs.items():
for w in ws:
if w[0] not in filtered_inputs:
@@ -348,6 +349,8 @@ class Will:
balance = 0
utxo_to_spend = []
for utxo in utxos:
if utxo.is_coinbase_output() and utxo.block_height < current_height+100:
continue
utxo_str = utxo.prevout.to_str()
if utxo_str in prevout_to_spend:
balance += inputs[utxo_str][0][2].value_sats()
@@ -356,7 +359,7 @@ class Will:
change_addresses = wallet.get_change_addresses_for_new_transaction()
out = PartialTxOutput.from_address_and_value(change_addresses[0], balance)
out.is_change = True
locktime = Util.get_current_height(wallet.network)
locktime = current_height
tx = PartialTransaction.from_io(
utxo_to_spend, [out], locktime=locktime, version=2
)
@@ -560,6 +563,9 @@ class Will:
raise WillExpiredException(
f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}"
)
else:
from datetime import datetime
_logger.debug(f"Will Not Expired {wid[0][0]}: {datetime.fromtimestamp(locktime).isoformat()} > {datetime.fromtimestamp(timestamp_to_check).isoformat()}")
# def check_all_input_spent_are_in_wallet():
# _logger.info("check all input spent are in wallet or valid txs")
@@ -793,9 +799,9 @@ class WillItem(Logger):
iw = inp[1]
self.set_anticipate(iw)
def check_willexecutor(self):
def set_check_willexecutor(self,resp):
try:
if resp := Willexecutors.check_transaction(self._id, self.we["url"]):
if resp :
if "tx" in resp and resp["tx"] == str(self.tx):
self.set_status("PUSHED")
self.set_status("CHECKED")
@@ -835,7 +841,12 @@ class WillItem(Logger):
class WillException(Exception):
pass
def __init__(self,msg="WillException"):
self.msg=msg
Exception.__init__(self)
def __str__(self):
return self.msg
class WillExpiredException(WillException):
@@ -872,8 +883,6 @@ class WillExecutorNotPresent(NotCompleteWillException):
class NoHeirsException(WillException):
pass
class AmountException(WillException):
pass

View File

@@ -132,7 +132,7 @@ class Willexecutors:
raise Exception("You are offline.")
_logger.debug(f"<-- {method} {url} {data}")
headers = {}
headers["user-agent"] = f"BalPlugin v:{BalPlugin.version()}"
headers["user-agent"] = f"BalPlugin v:{BalPlugin.__version__}"
headers["Content-Type"] = "text/plain"
if not handle_response:
handle_response = Willexecutors.handle_response
@@ -260,7 +260,7 @@ class Willexecutors:
def download_list(bal_plugin,old_willexecutors):
def download_list(old_willexecutors):
try:
willexecutors = Willexecutors.send_request(
"get",
@@ -280,7 +280,7 @@ class Willexecutors:
_logger.error(f"Failed to download willexecutors list: {e}")
return {}
def get_willexecutors_list_from_json(bal_plugin):
def get_willexecutors_list_from_json():
try:
with open("willexecutors.json") as f:
willexecutors = json.load(f)