14 Commits

8 changed files with 383 additions and 202 deletions

View File

@@ -1 +1 @@
0.2.6
0.2.8

29
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():
try:
f = ""
with open("VERSION", "r") as fi:
f = str(fi.readline())
return f
except Exception:
return "unknown"
_version=None
__version__ = "0.2.8" #AUTOMATICALLY GENERATED DO NOT EDIT
def version(self):
if not self._version:
try:
f = ""
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,
}
},
@@ -148,6 +149,8 @@ class BalPlugin(BasePlugin):
self.HIDE_REPLACED.set(self._hide_replaced)
def validate_will_settings(self, will_settings):
if not will_settings:
will_settings=[]
if int(will_settings.get("baltx_fees", 1)) < 1:
will_settings["baltx_fees"] = 1
if not will_settings.get("threshold"):

View File

@@ -30,9 +30,11 @@ from electrum.transaction import (
PartialTransaction,
PartialTxInput,
PartialTxOutput,
TxOutput,
TxOutpoint,
# TxOutput,
)
from electrum.payment_identifier import PaymentIdentifier
from electrum.util import (
bfh,
read_json_file,
@@ -44,7 +46,6 @@ from electrum.util import (
from .util import Util
from .willexecutors import Willexecutors
from electrum.util import BitcoinException
if TYPE_CHECKING:
from .simple_config import SimpleConfig
@@ -71,28 +72,22 @@ def reduce_outputs(in_amount, out_amount, fee, outputs):
output.value = math.floor((in_amount - fee) / out_amount * output.value)
"""
#TODO: put this method inside wallet.db to replace or complete get_locktime_for_new_transaction
def get_current_height(network:'Network'):
#if no network or not up to date, just set locktime to zero
if not network:
return 0
chain = network.blockchain()
if chain.is_tip_stale():
return 0
# figure out current block height
chain_height = chain.height() # learnt from all connected servers, SPV-checked
server_height = network.get_server_height() # height claimed by main server, unverified
# note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork)
# - if it's lagging too much, it is the network's job to switch away
if server_height < chain_height - 10:
# the diff is suspiciously large... give up and use something non-fingerprintable
return 0
# discourage "fee sniping"
height = min(chain_height, server_height)
return height
"""
def create_op_return_script(data_hex: str) -> bytes:
"""Crea scriptpubkey OP_RETURN in bytes"""
data = bytes.fromhex(data_hex)
if len(data) > 80:
raise ValueError("OP_RETURN data too big (max 80 bytes)")
# Costruzione manuale: OP_RETURN + push data
if len(data) <= 75:
# Formato più comune: OP_RETURN + 1-byte length + data
script = b'\x6a' + bytes([len(data)]) + data
else:
# Per dati più grandi (fino a 80) si usa OP_PUSHDATA1
script = b'\x6a\x4c' + bytes([len(data)]) + data
return script
def prepare_transactions(locktimes, available_utxos, fees, wallet):
available_utxos = sorted(
@@ -167,6 +162,13 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
outputs.append(change)
for i in range(0, 100):
random.shuffle(outputs)
#op_return_text = "Hello Bal!"
## Convert text to hex
#op_return_hex = op_return_text.encode('utf-8').hex()
#op_return_script = create_op_return_script(op_return_hex)
#outputs.append(PartialTxOutput(value=0, scriptpubkey=op_return_script))
tx = PartialTransaction.from_io(
used_utxos,
outputs,
@@ -424,6 +426,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 +465,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 +474,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 +538,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 +783,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.8",
"author":"Svatantrya",
"available_for": ["qt"],
"icon":"icons/bal32x32.png"

440
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,
@@ -402,9 +404,13 @@ class BalWindow(Logger):
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_tab = self.create_heirs_tab()
self.will_tab = self.create_will_tab()
self.wallet = self.window.wallet
self.heirs_tab.wallet = self.wallet
self.will_tab.wallet = self.wallet
@@ -486,17 +492,17 @@ class BalWindow(Logger):
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)
#if not self.will_settings:
# self.will_settings = self.wallet.db.get_dict("will_settings")
# Util.fix_will_settings_tx_fees(self.will_settings)
self.logger.info("will_settings: {}".format(self.will_settings))
if not self.will_settings:
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.logger.info("will_settings: {}".format(self.will_settings))
# if not self.will_settings:
# 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_widget.update_will_settings()
# self.heir_list_widget.update()
def init_wizard(self):
wizard_dialog = BalWizardDialog(self)
@@ -507,9 +513,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 +607,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 +614,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 +625,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):
@@ -740,6 +736,35 @@ class BalWindow(Logger):
def show_critical(self, text):
self.window.show_critical(text)
def update_locktime_widgets(self,locktime):
locktime = self.will_settings["locktime"] = (
locktime
if locktime
else "1y"
)
self.bal_plugin.WILL_SETTINGS.set(self.will_settings)
try:
self.heir_list_widget.heir_locktime.set_locktime(locktime)
except Exception as e:
pass
#self.preview_list.heirs_locktime.set_locktim(will_settings['thershold'])
def update_threshold_widgets(self,threshold):
threshold = self.will_settings["threshold"] = (
threshold
if threshold
else "1y"
)
self.bal_plugin.WILL_SETTINGS.set(self.will_settings)
try:
self.heir_list_widget.heir_threshold.set_locktime(threshold)
except Exception as e:
pass
try:
self.will_list.heir_threshold.set_locktime(threshold)
except Exception as e:
pass
def init_heirs_to_locktime(self, multiverse=False):
for heir in self.heirs:
h = self.heirs[heir]
@@ -748,8 +773,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 +781,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 +802,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 +814,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 +1065,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 +1119,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 +1174,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 +1237,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 +1262,20 @@ class BalWindow(Logger):
self.dw.show()
def update_all(self):
self.will_list.update_will(self.willitems)
self.heirs_tab.update()
self.will_tab.update()
self.will_list.update()
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_):
@@ -1293,8 +1351,8 @@ class HeirsLockTimeEdit(QWidget, _LockTimeEditor):
w.setEnabled(False)
prev_locktime = self.editor.get_locktime()
self.editor = self.option_index_to_editor_map[i]
if self.editor.is_acceptable_locktime(prev_locktime):
self.editor.set_locktime(prev_locktime, force=True)
#if self.editor.is_acceptable_locktime(prev_locktime):
# self.editor.set_locktime(prev_locktime, force=False)
self.editor.setVisible(True)
self.editor.setEnabled(True)
@@ -1305,8 +1363,16 @@ class HeirsLockTimeEdit(QWidget, _LockTimeEditor):
self.combo.setCurrentIndex(index)
self.on_current_index_changed(index)
def set_locktime(self, x: Any, force=True) -> None:
self.editor.set_locktime(x, force)
def set_locktime(self, x: Any, force=None) -> None:
if force is None:
force=True
try:
int(x)
self.set_index(1)
except:
if isinstance(x,str):
self.set_index(0)
self.editor.set_locktime(x, force=False)
class LockTimeRawEdit(QLineEdit, _LockTimeEditor):
@@ -1605,17 +1671,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 +1760,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 +1769,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 +1853,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
@@ -1832,19 +1900,14 @@ class BalWizardLocktimeAndFeeWidget(BalWizardWidget):
def get_content(self):
widget = QWidget()
self.heir_locktime = HeirsLockTimeEdit(widget, 0)
will_settings = self.bal_window.bal_plugin.WILL_SETTINGS.get()
#will_settings = self.bal_window.bal_plugin.WILL_SETTINGS.get()
will_settings = self.bal_window.will_settings
self.heir_locktime.set_locktime(will_settings["locktime"])
def on_heir_locktime():
if not self.heir_locktime.get_locktime():
self.heir_locktime.set_locktime("1y")
self.bal_window.will_settings["locktime"] = (
self.heir_locktime.get_locktime()
if self.heir_locktime.get_locktime()
else "1y"
)
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
self.bal_window.update_locktime_widgets(self.heir_locktime.get_locktime())
self.heir_locktime.valueEdited.connect(on_heir_locktime)
@@ -1854,11 +1917,7 @@ class BalWizardLocktimeAndFeeWidget(BalWizardWidget):
def on_heir_threshold():
if not self.heir_threshold.get_locktime():
self.heir_threshold.set_locktime("180d")
self.bal_window.will_settings["threshold"] = (
self.heir_threshold.get_locktime()
)
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
self.bal_window.update_threshold_widgets(self.heir_threshold.get_locktime())
self.heir_threshold.valueEdited.connect(on_heir_threshold)
@@ -2046,13 +2105,18 @@ class BalBuildWillDialog(BalDialog):
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
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 = []
self.check_row = None
self.inval_row = None
@@ -2079,13 +2143,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(
@@ -2098,15 +2175,12 @@ class BalBuildWillDialog(BalDialog):
_logger.debug("variables ok")
self.msg_set_status("checking variables:", varrow, "Ok", self.COLOR_OK)
except AmountException:
self.msg_set_status(
"checking variables",
varrow,
_(
self.msg_set_checking(
self.msg_warning(
"In the inheritance process, "
+ "the entire wallet will always be fully emptied. \n"
+ "Your settings require an adjustment of the amounts"
),
self.COLOR_WARNING,
)
)
self.msg_set_checking()
@@ -2121,9 +2195,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 +2222,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 +2262,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 +2307,7 @@ class BalBuildWillDialog(BalDialog):
self.msg_set_pushing(_("Broadcasting"))
retry = False
try:
willexecutors = Willexecutors.get_willexecutor_transactions(
self.bal_window.willitems
)
@@ -2257,7 +2337,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 +2351,6 @@ class BalBuildWillDialog(BalDialog):
)
except Exception as e:
_logger.error(f"loop push error:{e}")
raise e
if retry:
@@ -2306,12 +2388,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 +2440,6 @@ class BalBuildWillDialog(BalDialog):
self.close()
def closeEvent(self, event):
self.bal_window.update_all()
self._stopping = True
self.thread.stop()
@@ -2388,15 +2468,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 +2539,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 +2555,36 @@ class BalBuildWillDialog(BalDialog):
pass
self.updatemessage.emit()
def update(self):
self.vbox.removeWidget(self.qwidget)
self.qwidget = QWidget(self)
labelsbox = QVBoxLayout(self.qwidget)
for label in self.labels:
labelsbox.addWidget(QLabel(label))
self.vbox.addWidget(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):
# self.clear_layout(self.labelsbox)
# for label in self.labels:
# label=label.replace("\n","<br>")
# qlabel=QLabel(label)
# qlabel.setWordWrap(True)
# self.labelsbox.addWidget(qlabel)
# self.labelsbox.activate()
# self.qwidget.setMinimumSize(self.labelsbox.sizeHint())
# self.qwidget.adjustSize()
# from PyQt6.QtWidgets import QApplication
# QApplication.processEvents()
#
# self.adjustSize()
def msg_update(self):
full_text = "<br><br>".join(self.labels).replace("\n", "<br>")
self.message_label.setText(full_text)
self.message_label.adjustSize()
#self.setMinimumHeight(len(self.labels)*40)
self.resize(self.sizeHint())
def get_text(self):
return self.message_label.text()
@@ -2477,7 +2592,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 +2607,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 +2631,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 +2715,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
@@ -2654,32 +2771,28 @@ class HeirList(MyTreeView, MessageBoxMixin):
menu.addAction(_("Import"), self.bal_window.import_heirs)
menu.addAction(_("Export"), lambda: self.bal_window.export_heirs())
self.heir_locktime = HeirsLockTimeEdit(self, 0)
threshold = self.bal_window.will_settings.get('threshold',None) if self.bal_window.will_settings else self.bal_window.window.wallet.db.get_dict("will_settings")['threshold']
locktime = self.bal_window.will_settings.get('locktime',None) if self.bal_window.will_settings else self.bal_window.window.wallet.db.get_dict("will_settings")['locktime']
self.heir_locktime = HeirsLockTimeEdit(self, 0)
def on_heir_locktime():
if not self.heir_locktime.get_locktime():
self.heir_locktime.set_locktime("1y")
self.bal_window.will_settings["locktime"] = (
self.heir_locktime.get_locktime()
if self.heir_locktime.get_locktime()
else "1y"
)
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
self.bal_window.update_locktime_widgets(self.heir_locktime.get_locktime())
self.heir_locktime.valueEdited.connect(on_heir_locktime)
self.heir_locktime.set_locktime(locktime)
self.heir_threshold = HeirsLockTimeEdit(self, 0)
def on_heir_threshold():
if not self.heir_threshold.get_locktime():
self.heir_threshold.set_locktime("180d")
self.bal_window.will_settings["threshold"] = (
self.heir_threshold.get_locktime()
)
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
self.bal_window.update_threshold_widgets(self.heir_threshold.get_locktime())
self.heir_threshold.valueEdited.connect(on_heir_threshold)
self.heir_threshold.set_locktime(threshold)
self.heir_tx_fees = QSpinBox()
self.heir_tx_fees.setMinimum(1)
@@ -2747,6 +2860,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):
@@ -2963,18 +3077,59 @@ class PreviewList(MyTreeView):
menu.addAction(_("Check"), self.check)
menu.addAction(_("Invalidate"), self.invalidate_will)
def make_hlayout(label, twidget, help_text):
tw = QWidget()
hlayout = QHBoxLayout(tw)
hlayout.addWidget(QLabel(label))
hlayout.addWidget(twidget)
hlayout.addWidget(HelpButton(help_text))
hlayout.addStretch(1)
spacer_widget = QWidget()
spacer_widget.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
)
hlayout.addWidget(spacer_widget)
return tw
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(QLabel(_("Check Alive:")))
threshold = self.bal_window.will_settings.get('threshold',None) if self.bal_window.will_settings else self.bal_window.window.wallet.db.get_dict("will_settings")['threshold']
self.heir_threshold = HeirsLockTimeEdit(widget, 0)
self.heir_threshold.set_locktime(threshold)
def on_heir_threshold():
if not self.heir_threshold.get_locktime():
self.heir_threshold.set_locktime("180d")
self.bal_window.update_threshold_widgets(self.heir_threshold.get_locktime())
self.heir_threshold.valueEdited.connect(on_heir_threshold)
hlayout.addWidget(self.heir_threshold)
hlayout.addWidget(
HelpButton(
_(
"Check to ask for invalidation.\n"
+ "When less then this time is missing, ask to invalidate.\n"
+ "If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n"
+ "if you choose Raw, you can insert various options based on suffix:\n"
+ " - d: number of days after current day(ex: 1d means tomorrow).\n"
+ " - y: number of years after currrent day(ex: 1y means one year from today).\n\n"
)
)
)
hlayout.addWidget(wizard)
hlayout.addWidget(display)
hlayout.addWidget(refresh)
toolbar.insertWidget(2, widget)
self.menu = menu
@@ -3012,13 +3167,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 +3213,10 @@ class PreviewDialog(BalDialog, MessageBoxMixin):
self.size_label = QLabel()
self.transactions_list = PreviewList(self.bal_window, self.will)
self.bal_window.init_class_variables()
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 +3462,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 +3707,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 +3724,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 +3748,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 +3756,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 +3783,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 +3813,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 +3833,10 @@ 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):
return "Check alive expired please update it: {}".format(datetime.fromtimestamp(self.timestamp_to_check).isoformat())

13
util.py
View File

@@ -494,3 +494,16 @@ class Util:
del will[txid]["tx_fees"]
have_to_update = True
return have_to_update
def text_to_hex(text: str) -> str:
"""Convert text to hexadecimal string"""
hex_string = text.encode('utf-8').hex()
return hex_string
def hex_to_text(hex_string: str) -> str:
"""Convert hexadecimal string back to text (for verification)"""
try:
return bytes.fromhex(hex_string).decode('utf-8')
except Exception:
return "Error: Invalid hex string"

31
will.py
View File

@@ -148,16 +148,6 @@ class Will:
inp._TxInput__value_sats = change.value
return inp
"""
in questa situazione sono presenti due transazioni con id differente(quindi transazioni differenti)
per prima cosa controllo il locktime
se il locktime della nuova transazione e' maggiore del locktime della vecchia transazione, allora
confronto gli eredi, per locktime se corrispondono controllo i willexecutor
se hanno la stessa url ma le fee vecchie sono superiori alle fee nuove, allora anticipare.
"""
def check_anticipate(ow: "WillItem", nw: "WillItem"):
anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1)
if int(nw.tx.locktime) >= int(anticipate):
@@ -339,6 +329,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 +339,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 +349,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 +553,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 +789,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 +831,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 +873,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)