2 Commits

Author SHA1 Message Date
609db44c1a blacked 2026-02-09 12:01:06 -04:00
0df6786f50 dust bug fix 2026-02-09 11:58:07 -04:00
10 changed files with 333 additions and 487 deletions

View File

@@ -1 +1 @@
0.2.6 0.2.4

13
bal.py
View File

@@ -57,7 +57,7 @@ class BalPlugin(BasePlugin):
with open("VERSION", "r") as fi: with open("VERSION", "r") as fi:
f = str(fi.readline()) f = str(fi.readline())
return f return f
except Exception: except:
return "unknown" return "unknown"
SIZE = (159, 97) SIZE = (159, 97)
@@ -111,16 +111,7 @@ class BalPlugin(BasePlugin):
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected": True, "selected": True,
} }
}, }
"testnet": {
"https://we.bitcoin-after.life": {
"base_fee": 100000,
"status": "New",
"info": "Bitcoin After Life Will Executor",
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected": True,
}
},
}, },
) )
self.WILL_SETTINGS = BalConfig( self.WILL_SETTINGS = BalConfig(

214
heirs.py
View File

@@ -1,37 +1,23 @@
# import datetime import datetime
# import json import json
import math import math
import random import random
import re import re
import threading import threading
import urllib.parse
# import urllib.parse import urllib.request
# import urllib.request from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple
from typing import (
TYPE_CHECKING,
Any,
Dict,
# List,
Optional,
# Sequence,
Tuple,
)
import dns import dns
from dns.exception import DNSException from dns.exception import DNSException
from electrum import ( from electrum import bitcoin, constants, descriptor, dnssec
bitcoin,
constants,
# descriptor,
dnssec,
)
from electrum.logging import Logger, get_logger from electrum.logging import Logger, get_logger
from electrum.transaction import ( from electrum.transaction import (
PartialTransaction, PartialTransaction,
PartialTxInput, PartialTxInput,
PartialTxOutput, PartialTxOutput,
TxOutpoint, TxOutpoint,
# TxOutput, TxOutput,
) )
from electrum.util import ( from electrum.util import (
bfh, bfh,
@@ -44,11 +30,11 @@ from electrum.util import (
from .util import Util from .util import Util
from .willexecutors import Willexecutors from .willexecutors import Willexecutors
from electrum.util import BitcoinException from electrum.util import BitcoinException
from electrum import constants
if TYPE_CHECKING: if TYPE_CHECKING:
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .wallet_db import WalletDB
# from .wallet_db import WalletDB
_logger = get_logger(__name__) _logger = get_logger(__name__)
@@ -101,7 +87,7 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
x.value_sats(), x.prevout.txid, x.prevout.out_idx x.value_sats(), x.prevout.txid, x.prevout.out_idx
), ),
) )
# total_used_utxos = [] total_used_utxos = []
txsout = {} txsout = {}
locktime, _ = Util.get_lowest_locktimes(locktimes) locktime, _ = Util.get_lowest_locktimes(locktimes)
if not locktime: if not locktime:
@@ -110,16 +96,16 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
locktime = locktime[0] locktime = locktime[0]
heirs = locktimes[locktime] heirs = locktimes[locktime]
true = True vero = True
while true: while vero:
true = False vero = False
fee = fees.get(locktime, 0) fee = fees.get(locktime, 0)
out_amount = fee out_amount = fee
description = "" description = ""
outputs = [] outputs = []
paid_heirs = {} paid_heirs = {}
for name, heir in heirs.items(): for name, heir in heirs.items():
if len(heir) > HEIR_REAL_AMOUNT and "DUST" not in str( if len(heir) > HEIR_REAL_AMOUNT and not "DUST" in str(
heir[HEIR_REAL_AMOUNT] heir[HEIR_REAL_AMOUNT]
): ):
try: try:
@@ -132,12 +118,12 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
out_amount += real_amount out_amount += real_amount
description += f"{name}\n" description += f"{name}\n"
except BitcoinException as e: except BitcoinException as e:
_logger.info("exception decoding output {} - {}".format(type(e), e)) _logger.info("exception decoding output{} - {}".format(type(e), e))
heir[HEIR_REAL_AMOUNT] = e heir[HEIR_REAL_AMOUNT] = e
except Exception as e: except Exception as e:
heir[HEIR_REAL_AMOUNT] = e heir[HEIR_REAL_AMOUNT] = e
_logger.error(f"error preparing transactions: {e}") _logger.info(f"error preparing transactions {e}")
pass pass
paid_heirs[name] = heir paid_heirs[name] = heir
@@ -152,15 +138,15 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
break break
except IndexError as e: except IndexError as e:
_logger.error( _logger.info(
f"error preparing transactions index error {e} {in_amount}, {out_amount}" f"error preparing transactions index error {e} {in_amount}, {out_amount}"
) )
pass pass
if int(in_amount) < int(out_amount): if int(in_amount) < int(out_amount):
_logger.error( _logger.info(
"error preparing transactions in_amount < out_amount ({} < {}) " "error preparing transactions in_amount < out_amount ({} < {}) "
) )
continue break
heirsvalue = out_amount heirsvalue = out_amount
change = get_change_output(wallet, in_amount, out_amount, fee) change = get_change_output(wallet, in_amount, out_amount, fee)
if change: if change:
@@ -214,7 +200,7 @@ def get_utxos_from_inputs(tx_inputs, tx, utxos):
# TODO calculate de minimum inputs to be invalidated # TODO calculate de minimum inputs to be invalidated
def invalidate_inheritance_transactions(wallet): def invalidate_inheritance_transactions(wallet):
# listids = [] listids = []
utxos = {} utxos = {}
dtxs = {} dtxs = {}
for k, v in wallet.get_all_labels().items(): for k, v in wallet.get_all_labels().items():
@@ -243,7 +229,7 @@ def invalidate_inheritance_transactions(wallet):
for key, value in utxos: for key, value in utxos:
for tx in value["txs"]: for tx in value["txs"]:
txid = tx.txid() txid = tx.txid()
if txid not in invalidated: if not txid in invalidated:
invalidated.append(tx.txid()) invalidated.append(tx.txid())
remaining[key] = value remaining[key] = value
@@ -251,10 +237,10 @@ def invalidate_inheritance_transactions(wallet):
def print_transaction(heirs, tx, locktimes, tx_fees): def print_transaction(heirs, tx, locktimes, tx_fees):
jtx = tx.to_json() jtx = tx.to_json()
print(f"TX: {tx.txid()}\t-\tLocktime: {jtx['locktime']}") print(f"TX: {tx.txid()}\t-\tLocktime: {jtx['locktime']}")
print("---") print(f"---")
for inp in jtx["inputs"]: for inp in jtx["inputs"]:
print(f"{inp['address']}: {inp['value_sats']}") print(f"{inp['address']}: {inp['value_sats']}")
print("---") print(f"---")
for out in jtx["outputs"]: for out in jtx["outputs"]:
heirname = "" heirname = ""
for key in heirs.keys(): for key in heirs.keys():
@@ -276,7 +262,7 @@ def print_transaction(heirs, tx, locktimes, tx_fees):
print() print()
try: try:
print(tx.serialize_to_network()) print(tx.serialize_to_network())
except Exception: except:
print("impossible to serialize") print("impossible to serialize")
print() print()
@@ -299,7 +285,7 @@ class Heirs(dict, Logger):
d = self.db.get("heirs", {}) d = self.db.get("heirs", {})
try: try:
self.update(d) self.update(d)
except Exception: except e as Exception:
return return
def invalidate_transactions(self, wallet): def invalidate_transactions(self, wallet):
@@ -333,7 +319,7 @@ class Heirs(dict, Logger):
locktime = Util.parse_locktime_string(self[key][HEIR_LOCKTIME]) locktime = Util.parse_locktime_string(self[key][HEIR_LOCKTIME])
if locktime > from_locktime and not a or locktime <= from_locktime and a: if locktime > from_locktime and not a or locktime <= from_locktime and a:
locktimes[int(locktime)] = None locktimes[int(locktime)] = None
return list(locktimes.keys()) return locktimes.keys()
def check_locktime(self): def check_locktime(self):
return False return False
@@ -347,8 +333,6 @@ class Heirs(dict, Logger):
column = HEIR_AMOUNT column = HEIR_AMOUNT
if real: if real:
column = HEIR_REAL_AMOUNT column = HEIR_REAL_AMOUNT
if "DUST" in str(v[column]):
column = HEIR_DUST_AMOUNT
value = int( value = int(
math.floor( math.floor(
total_balance total_balance
@@ -371,10 +355,10 @@ class Heirs(dict, Logger):
def amount_to_float(self, amount): def amount_to_float(self, amount):
try: try:
return float(amount) return float(amount)
except Exception: except:
try: try:
return float(amount[:-1]) return float(amount[:-1])
except Exception: except:
return 0.0 return 0.0
def fixed_percent_lists_amount(self, from_locktime, dust_threshold, reverse=False): def fixed_percent_lists_amount(self, from_locktime, dust_threshold, reverse=False):
@@ -382,44 +366,27 @@ class Heirs(dict, Logger):
fixed_amount = 0.0 fixed_amount = 0.0
percent_heirs = {} percent_heirs = {}
percent_amount = 0.0 percent_amount = 0.0
fixed_amount_with_dust = 0.0
for key in self.keys(): for key in self.keys():
try: try:
cmp = ( cmp = (
Util.parse_locktime_string(self[key][HEIR_LOCKTIME]) - from_locktime Util.parse_locktime_string(self[key][HEIR_LOCKTIME]) - from_locktime
) )
if cmp <= 0: if cmp <= 0:
_logger.debug(
"cmp < 0 {} {} {} {}".format(
cmp, key, self[key][HEIR_LOCKTIME], from_locktime
)
)
continue continue
if Util.is_perc(self[key][HEIR_AMOUNT]): if Util.is_perc(self[key][HEIR_AMOUNT]):
percent_amount += float(self[key][HEIR_AMOUNT][:-1]) percent_amount += float(self[key][HEIR_AMOUNT][:-1])
percent_heirs[key] = list(self[key]) percent_heirs[key] = list(self[key])
else: else:
heir_amount = int(math.floor(float(self[key][HEIR_AMOUNT]))) heir_amount = int(math.floor(float(self[key][HEIR_AMOUNT])))
fixed_amount_with_dust += heir_amount
fixed_heirs[key] = list(self[key])
if heir_amount > dust_threshold: if heir_amount > dust_threshold:
fixed_amount += heir_amount fixed_amount += heir_amount
fixed_heirs[key] = list(self[key])
fixed_heirs[key].insert(HEIR_REAL_AMOUNT, heir_amount) fixed_heirs[key].insert(HEIR_REAL_AMOUNT, heir_amount)
else: else:
fixed_heirs[key] = list(self[key]) pass
fixed_heirs[key].insert(
HEIR_REAL_AMOUNT, f"DUST: {heir_amount}"
)
fixed_heirs[key].insert(HEIR_DUST_AMOUNT, heir_amount)
except Exception as e: except Exception as e:
_logger.error(e) _logger.error(e)
return ( return fixed_heirs, fixed_amount, percent_heirs, percent_amount
fixed_heirs,
fixed_amount,
percent_heirs,
percent_amount,
fixed_amount_with_dust,
)
def prepare_lists( def prepare_lists(
self, balance, total_fees, wallet, willexecutor=False, from_locktime=0 self, balance, total_fees, wallet, willexecutor=False, from_locktime=0
@@ -444,7 +411,7 @@ class Heirs(dict, Logger):
willexecutors[ willexecutors[
'w!ll3x3c"' + willexecutor["url"] + '"' + str(locktime) 'w!ll3x3c"' + willexecutor["url"] + '"' + str(locktime)
] = h ] = h
except Exception: except Exception as e:
return [], False return [], False
else: else:
_logger.error( _logger.error(
@@ -452,16 +419,9 @@ class Heirs(dict, Logger):
), ),
heir_list.update(willexecutors) heir_list.update(willexecutors)
newbalance -= willexecutors_amount newbalance -= willexecutors_amount
if newbalance < 0: fixed_heirs, fixed_amount, percent_heirs, percent_amount = (
raise WillExecutorFeeException(willexecutor) self.fixed_percent_lists_amount(from_locktime, wallet.dust_threshold())
( )
fixed_heirs,
fixed_amount,
percent_heirs,
percent_amount,
fixed_amount_with_dust,
) = self.fixed_percent_lists_amount(from_locktime, wallet.dust_threshold())
if fixed_amount > newbalance: if fixed_amount > newbalance:
fixed_amount = self.normalize_perc( fixed_amount = self.normalize_perc(
fixed_heirs, newbalance, fixed_amount, wallet fixed_heirs, newbalance, fixed_amount, wallet
@@ -482,7 +442,7 @@ class Heirs(dict, Logger):
if newbalance > 0: if newbalance > 0:
newbalance += fixed_amount newbalance += fixed_amount
fixed_amount = self.normalize_perc( fixed_amount = self.normalize_perc(
fixed_heirs, newbalance, fixed_amount_with_dust, wallet, real=True fixed_heirs, newbalance, fixed_amount, wallet, real=True
) )
newbalance -= fixed_amount newbalance -= fixed_amount
heir_list.update(fixed_heirs) heir_list.update(fixed_heirs)
@@ -495,7 +455,7 @@ class Heirs(dict, Logger):
locktimes = {} locktimes = {}
for key, value in heir_list: for key, value in heir_list:
locktime = Util.parse_locktime_string(value[HEIR_LOCKTIME]) locktime = Util.parse_locktime_string(value[HEIR_LOCKTIME])
if locktime not in locktimes: if not locktime in locktimes:
locktimes[locktime] = {key: value} locktimes[locktime] = {key: value}
else: else:
locktimes[locktime][key] = value locktimes[locktime][key] = value
@@ -549,66 +509,52 @@ class Heirs(dict, Logger):
break break
fees = {} fees = {}
i = 0 i = 0
while i < 10: while True:
txs = {} txs = {}
redo = False redo = False
i += 1 i += 1
total_fees = 0 total_fees = 0
for fee in fees: for fee in fees:
total_fees += int(fees[fee]) total_fees += int(fees[fee])
# newbalance = balance newbalance = balance
locktimes, onlyfixed = self.prepare_lists(
balance, total_fees, wallet, willexecutor, from_locktime
)
try: try:
locktimes, onlyfixed = self.prepare_lists( txs = prepare_transactions(
balance, total_fees, wallet, willexecutor, from_locktime locktimes, available_utxos[:], fees, wallet
) )
except WillExecutorFeeException: if not txs:
i = 10 return {}
continue except Exception as e:
if locktimes: _logger.info(f"error preparing transactions{e}")
try: try:
txs = prepare_transactions( if "w!ll3x3c" in e.heirname:
locktimes, available_utxos[:], fees, wallet Willexecutors.is_selected(willexecutors[w], False)
) break
if not txs: except:
return {} raise e
except Exception as e: total_fees = 0
_logger.error( total_fees_real = 0
f"build transactions: error preparing transactions: {e}" total_in = 0
) for txid, tx in txs.items():
try: tx.willexecutor = willexecutor
if "w!ll3x3c" in e.heirname: fee = tx.estimated_size() * tx_fees
Willexecutors.is_selected( txs[txid].tx_fees = tx_fees
e.heirname[len("w!ll3x3c") :], False total_fees += fee
) total_fees_real += tx.get_fee()
break total_in += tx.input_value()
except Exception: rfee = tx.input_value() - tx.output_value()
raise e if rfee < fee or rfee > fee + wallet.dust_threshold():
total_fees = 0
total_fees_real = 0
total_in = 0
for txid, tx in txs.items():
tx.willexecutor = willexecutor
fee = tx.estimated_size() * tx_fees
txs[txid].tx_fees = tx_fees
total_fees += fee
total_fees_real += tx.get_fee()
total_in += tx.input_value()
rfee = tx.input_value() - tx.output_value()
if rfee < fee or rfee > fee + wallet.dust_threshold():
redo = True
# oldfees = fees.get(tx.my_locktime, 0)
fees[tx.my_locktime] = fee
if balance - total_in > wallet.dust_threshold():
redo = True redo = True
if not redo: oldfees = fees.get(tx.my_locktime, 0)
break fees[tx.my_locktime] = fee
if i >= 10:
break if balance - total_in > wallet.dust_threshold():
else: redo = True
_logger.info( if not redo:
f"no locktimes for willexecutor {willexecutor} skipped" break
) if i >= 10:
break break
alltxs.update(txs) alltxs.update(txs)
@@ -768,17 +714,3 @@ class HeirExpiredException(LocktimeNotValid):
class HeirAmountIsDustException(Exception): class HeirAmountIsDustException(Exception):
pass pass
class NoHeirsException(Exception):
pass
class WillExecutorFeeException(Exception):
def __init__(self, willexecutor):
self.willexecutor = willexecutor
def __str__(self):
return "WillExecutorFeeException: {} fee:{}".format(
self.willexecutor["url"], self.willexecutor["base_fee"]
)

View File

@@ -1,7 +1,7 @@
{ {
"name": "BAL", "name": "BAL",
"fullname": "Bitcoin After Life", "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.4",
"author":"Svatantrya", "author":"Svatantrya",
"available_for": ["qt"], "available_for": ["qt"],
"icon":"icons/bal32x32.png" "icon":"icons/bal32x32.png"

190
qt.py
View File

@@ -31,8 +31,10 @@ if QT_VERSION == 5:
) )
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QColor, QColor,
QIcon,
QPainter, QPainter,
QPalette, QPalette,
QPixmap,
QStandardItem, QStandardItem,
QStandardItemModel, QStandardItemModel,
) )
@@ -66,8 +68,10 @@ else: # QT6
) )
from PyQt6.QtGui import ( from PyQt6.QtGui import (
QColor, QColor,
QIcon,
QPainter, QPainter,
QPalette, QPalette,
QPixmap,
QStandardItem, QStandardItem,
QStandardItemModel, QStandardItemModel,
) )
@@ -120,6 +124,7 @@ from electrum.gui.qt.util import (
WindowModalDialog, WindowModalDialog,
char_width_in_lineedit, char_width_in_lineedit,
import_meta_gui, import_meta_gui,
read_QIcon,
read_QIcon_from_bytes, read_QIcon_from_bytes,
read_QPixmap_from_bytes, read_QPixmap_from_bytes,
) )
@@ -139,6 +144,7 @@ from electrum.util import (
) )
from .bal import BalPlugin from .bal import BalPlugin
from .bal_resources import DEFAULT_ICON, icon_path
from .heirs import Heirs, HEIR_REAL_AMOUNT, HEIR_DUST_AMOUNT from .heirs import Heirs, HEIR_REAL_AMOUNT, HEIR_DUST_AMOUNT
from .util import Util from .util import Util
from .will import ( from .will import (
@@ -191,10 +197,8 @@ class Plugin(BalPlugin, Logger):
w.init_menubar_tools(menu_child) w.init_menubar_tools(menu_child)
except Exception as e: except Exception as e:
self.logger.error(
("init_qt except:", menu_child.text())
)
raise e raise e
self.logger.error(("except:", menu_child.text()))
except Exception as e: except Exception as e:
self.logger.error("Error loading plugini {}".format(e)) self.logger.error("Error loading plugini {}".format(e))
@@ -293,7 +297,7 @@ class Plugin(BalPlugin, Logger):
# heir_ping_willexecutors = bal_checkbox(self.PING_WILLEXECUTORS) # heir_ping_willexecutors = bal_checkbox(self.PING_WILLEXECUTORS)
# heir_ask_ping_willexecutors = bal_checkbox(self.ASK_PING_WILLEXECUTORS) # heir_ask_ping_willexecutors = bal_checkbox(self.ASK_PING_WILLEXECUTORS)
# heir_no_willexecutor = bal_checkbox(self.NO_WILLEXECUTOR) heir_no_willexecutor = bal_checkbox(self.NO_WILLEXECUTOR)
def on_multiverse_change(): def on_multiverse_change():
self.update_all() self.update_all()
@@ -477,7 +481,7 @@ class BalWindow(Logger):
self.willitems = {} self.willitems = {}
try: try:
self.load_willitems() self.load_willitems()
except Exception: except:
self.disable_plugin = True self.disable_plugin = True
self.show_warning( self.show_warning(
_("Please restart Electrum to activate the BAL plugin"), _("Please restart Electrum to activate the BAL plugin"),
@@ -496,7 +500,6 @@ class BalWindow(Logger):
self.logger.debug("not_will_settings {}".format(self.will_settings)) self.logger.debug("not_will_settings {}".format(self.will_settings))
self.bal_plugin.validate_will_settings(self.will_settings) self.bal_plugin.validate_will_settings(self.will_settings)
self.heir_list.update_will_settings() self.heir_list.update_will_settings()
self.heir_list.update()
def init_wizard(self): def init_wizard(self):
wizard_dialog = BalWizardDialog(self) wizard_dialog = BalWizardDialog(self)
@@ -507,15 +510,15 @@ class BalWindow(Logger):
self.willexecutor_dialog.show() self.willexecutor_dialog.show()
def create_heirs_tab(self): def create_heirs_tab(self):
self.heir_list = HeirList(self, self.window) self.heir_list = l = HeirList(self, self.window)
tab = self.window.create_list_tab(self.heir_list) tab = self.window.create_list_tab(l)
tab.is_shown_cv = shown_cv(False) tab.is_shown_cv = shown_cv(False)
return tab return tab
def create_will_tab(self): def create_will_tab(self):
self.will_list = PreviewList(self, self.window, None) self.will_list = l = PreviewList(self, self.window, None)
tab = self.window.create_list_tab(self.will_list) tab = self.window.create_list_tab(l)
tab.is_shown_cv = shown_cv(True) tab.is_shown_cv = shown_cv(True)
return tab return tab
@@ -549,7 +552,7 @@ class BalWindow(Logger):
if heir: if heir:
self.heir_locktime.set_locktime(heir[2]) self.heir_locktime.set_locktime(heir[2])
# heir_is_xpub = QCheckBox() heir_is_xpub = QCheckBox()
new_heir_button = QPushButton(_("Add another heir")) new_heir_button = QPushButton(_("Add another heir"))
self.add_another_heir = False self.add_another_heir = False
@@ -621,11 +624,7 @@ class BalWindow(Logger):
def delete_heirs(self, heirs): def delete_heirs(self, heirs):
for heir in heirs: for heir in heirs:
try: del self.heirs[heir]
del self.heirs[heir]
except Exception as e:
_logger.debug(f"error deleting heir: {heir} {e}")
pass
self.heirs.save() self.heirs.save()
self.heir_list.update() self.heir_list.update()
return True return True
@@ -657,8 +656,8 @@ class BalWindow(Logger):
def build_will(self, ignore_duplicate=True, keep_original=True): def build_will(self, ignore_duplicate=True, keep_original=True):
_logger.debug("building will...") _logger.debug("building will...")
will = {} will = {}
# willtodelete = [] willtodelete = []
# willtoappend = {} willtoappend = {}
try: try:
self.init_class_variables() self.init_class_variables()
self.willexecutors = Willexecutors.get_willexecutors( self.willexecutors = Willexecutors.get_willexecutors(
@@ -687,7 +686,7 @@ class BalWindow(Logger):
creation_time = time.time() creation_time = time.time()
if txs: if txs:
for txid in txs: for txid in txs:
# txtodelete = [] txtodelete = []
_break = False _break = False
tx = {} tx = {}
tx["tx"] = txs[txid] tx["tx"] = txs[txid]
@@ -707,7 +706,6 @@ class BalWindow(Logger):
self.logger.info(f"will-settings: {self.will_settings}") self.logger.info(f"will-settings: {self.will_settings}")
self.logger.info(f"date_to_check:{self.date_to_check}") self.logger.info(f"date_to_check:{self.date_to_check}")
self.logger.info(f"heirs: {self.heirs}") self.logger.info(f"heirs: {self.heirs}")
return {}
except Exception as e: except Exception as e:
self.logger.info(f"Exception build_will: {e}") self.logger.info(f"Exception build_will: {e}")
raise e raise e
@@ -754,7 +752,7 @@ class BalWindow(Logger):
self.date_to_check = Util.parse_locktime_string( self.date_to_check = Util.parse_locktime_string(
self.will_settings["threshold"] self.will_settings["threshold"]
) )
# found = False found = False
self.locktime_blocks = self.bal_plugin.LOCKTIME_BLOCKS.get() self.locktime_blocks = self.bal_plugin.LOCKTIME_BLOCKS.get()
self.current_block = Util.get_current_height(self.wallet.network) self.current_block = Util.get_current_height(self.wallet.network)
self.block_to_check = self.current_block + self.locktime_blocks self.block_to_check = self.current_block + self.locktime_blocks
@@ -765,7 +763,7 @@ class BalWindow(Logger):
self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get()) self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get())
except Exception as e: except Exception as e:
self.logger.error(f"init_class_variables: {e}") self.logger.error(e)
raise e raise e
def build_inheritance_transaction(self, ignore_duplicate=True, keep_original=True): def build_inheritance_transaction(self, ignore_duplicate=True, keep_original=True):
@@ -888,7 +886,7 @@ class BalWindow(Logger):
def show_transaction(self, tx=None, txid=None, parent=None): def show_transaction(self, tx=None, txid=None, parent=None):
if not parent: if not parent:
parent = self.window parent = self.window
if txid is not None and txid in self.willitems: if txid != None and txid in self.willitems:
tx = self.willitems[txid].tx tx = self.willitems[txid].tx
if not tx: if not tx:
raise Exception(_("no tx")) raise Exception(_("no tx"))
@@ -903,7 +901,7 @@ class BalWindow(Logger):
) )
) )
self.wallet.set_label(result.txid(), "BAL Invalidate") self.wallet.set_label(result.txid(), "BAL Invalidate")
self.show_transaction(result) a = self.show_transaction(result)
else: else:
self.show_message(_("No transactions to invalidate")) self.show_message(_("No transactions to invalidate"))
@@ -939,7 +937,7 @@ class BalWindow(Logger):
tosign = txid tosign = txid
try: try:
self.waiting_dialog.update(get_message()) self.waiting_dialog.update(get_message())
except Exception: except:
pass pass
for txin in tx.inputs(): for txin in tx.inputs():
prevout = txin.prevout.to_json() prevout = txin.prevout.to_json()
@@ -948,7 +946,7 @@ class BalWindow(Logger):
txin._trusted_value_sats = change.value txin._trusted_value_sats = change.value
try: try:
txin.script_descriptor = change.script_descriptor txin.script_descriptor = change.script_descriptor
except Exception: except:
pass pass
txin.is_mine = True txin.is_mine = True
txin._TxInput__address = change.address txin._TxInput__address = change.address
@@ -957,9 +955,9 @@ class BalWindow(Logger):
self.wallet.sign_transaction(tx, password, ignore_warnings=True) self.wallet.sign_transaction(tx, password, ignore_warnings=True)
signed = tosign signed = tosign
# is_complete = False is_complete = False
if tx.is_complete(): if tx.is_complete():
# is_complete = True is_complete = True
wi.set_status("COMPLETE", True) wi.set_status("COMPLETE", True)
txs[txid] = tx txs[txid] = tx
except Exception: except Exception:
@@ -1036,7 +1034,7 @@ class BalWindow(Logger):
) )
def on_failure(err): def on_failure(err):
self.logger.error(f"fail to broadcast transactions:{err}") self.logger.error(err)
task = partial(self.push_transactions_to_willexecutors, force) task = partial(self.push_transactions_to_willexecutors, force)
msg = _("Selecting Will-Executors") msg = _("Selecting Will-Executors")
@@ -1191,14 +1189,11 @@ class BalWindow(Logger):
try: try:
parent.willexecutor_list.update() parent.willexecutor_list.update()
except Exception as e: except Exception as e:
pass _logger.error(f"error updating willexecutors {e}")
try:
parent.willexecutors_list.update()
except Exception as e:
pass pass
def on_failure(e): def on_failure(e):
self.logger.error(f"fail to ping willexecutors: {e}") self.logger.error(e)
pass pass
self.logger.info("ping willexecutors") self.logger.info("ping willexecutors")
@@ -1692,7 +1687,7 @@ class BalWizardHeirsWidget(BalWizardWidget):
) )
def get_content(self): def get_content(self):
self.heirs_list = HeirList(self.bal_window, self) self.heirs_list = HeirList(self.bal_window, self.parent)
button_add = QPushButton(_("Add")) button_add = QPushButton(_("Add"))
button_add.clicked.connect(self.add_heir) button_add.clicked.connect(self.add_heir)
button_import = QPushButton(_("Import")) button_import = QPushButton(_("Import"))
@@ -1761,7 +1756,7 @@ class BalWizardWEDownloadWidget(BalWizardWidget):
import_meta_gui( import_meta_gui(
self.bal_window.window, self.bal_window.window,
_("willexecutors"), _("willexecutors.json"),
self.import_json_file, self.import_json_file,
doNothing, doNothing,
) )
@@ -1785,7 +1780,7 @@ class BalWizardWEDownloadWidget(BalWizardWidget):
_logger.debug(f"Failed to download willexecutors list {fail}") _logger.debug(f"Failed to download willexecutors list {fail}")
pass pass
task = partial(Willexecutors.download_list, self.bal_window.bal_plugin,self.bal_window.willexecutors) task = partial(Willexecutors.download_list, self.bal_window.bal_plugin)
msg = _("Downloading Will-Executors list") msg = _("Downloading Will-Executors list")
self.waiting_dialog = BalWaitingDialog( self.waiting_dialog = BalWaitingDialog(
self.bal_window, msg, task, on_success, on_failure, exe=False self.bal_window, msg, task, on_success, on_failure, exe=False
@@ -2037,18 +2032,15 @@ class bal_checkbox(QCheckBox):
class BalBuildWillDialog(BalDialog): class BalBuildWillDialog(BalDialog):
updatemessage = pyqtSignal() updatemessage = pyqtSignal()
COLOR_WARNING = "#cfa808"
COLOR_ERROR = "#ff0000"
COLOR_OK = "#05ad05"
def __init__(self, bal_window, parent=None): def __init__(self, bal_window, parent=None):
if not parent: if not parent:
parent = bal_window.window parent = bal_window.window
BalDialog.__init__(self, parent, bal_window.bal_plugin, _("Building Will")) BalDialog.__init__(self, parent, bal_window.bal_plugin, "Building Will")
self.parent = parent self.parent = parent
self.updatemessage.connect(self.update) self.updatemessage.connect(self.update)
self.bal_window = bal_window self.bal_window = bal_window
self.message_label = QLabel(_("Building Will:")) self.message_label = QLabel("Building Will:")
self.vbox = QVBoxLayout(self) self.vbox = QVBoxLayout(self)
self.vbox.addWidget(self.message_label) self.vbox.addWidget(self.message_label)
self.qwidget = QWidget() self.qwidget = QWidget()
@@ -2096,24 +2088,25 @@ class BalBuildWillDialog(BalDialog):
self.bal_window.window.wallet.dust_threshold(), self.bal_window.window.wallet.dust_threshold(),
) )
_logger.debug("variables ok") _logger.debug("variables ok")
self.msg_set_status("checking variables:", varrow, "Ok", self.COLOR_OK) self.msg_set_status("checking variables:", varrow, "Ok")
except AmountException: except AmountException:
self.msg_set_status( self.msg_set_status(
"checking variables", "checking variables",
varrow, varrow,
_( '<font color="#ff0000">'
+ _(
"In the inheritance process, " "In the inheritance process, "
+ "the entire wallet will always be fully emptied. \n" + "the entire wallet will always be fully emptied. \n"
+ "Your settings require an adjustment of the amounts" + "Your settings require an adjustment of the amounts"
), )
self.COLOR_WARNING, + "</font>",
) )
self.msg_set_checking() self.msg_set_checking()
have_to_build = False have_to_build = False
try: try:
self.bal_window.check_will() self.bal_window.check_will()
self.msg_set_checking(self.msg_ok()) self.msg_set_checking("Ok")
except WillExpiredException: except WillExpiredException:
_logger.debug("expired") _logger.debug("expired")
self.msg_set_checking("Expired") self.msg_set_checking("Expired")
@@ -2129,15 +2122,15 @@ class BalBuildWillDialog(BalDialog):
message = False message = False
have_to_build = True have_to_build = True
if isinstance(e, HeirChangeException): if isinstance(e, HeirChangeException):
message = _("Heirs changed:") message = "Heirs changed:"
elif isinstance(e, WillExecutorNotPresent): elif isinstance(e, WillExecutorNotPresent):
message = _("Will-Executor not present") message = "Will-Executor not present"
elif isinstance(e, WillexecutorChangeException): elif isinstance(e, WillexecutorChangeException):
message = _("Will-Executor changed") message = "Will-Executor changed"
elif isinstance(e, TxFeesChangedException): elif isinstance(e, TxFeesChangedException):
message = _("Txfees are changed") message = "Txfees are changed"
elif isinstance(e, HeirNotFoundException): elif isinstance(e, HeirNotFoundException):
message = _("Heir not found") message = "Heir not found"
if message: if message:
_logger.debug(f"message: {message}") _logger.debug(f"message: {message}")
self.msg_set_checking(message) self.msg_set_checking(message)
@@ -2147,37 +2140,24 @@ class BalBuildWillDialog(BalDialog):
if have_to_build: if have_to_build:
self.msg_set_building() self.msg_set_building()
try: try:
if not self.bal_window.build_will(): self.bal_window.build_will()
self.msg_set_status(
_("Balance is too low. No transaction was built"),
None,
_("Skipped"),
self.COLOR_ERROR,
)
self.bal_window.check_will() self.bal_window.check_will()
for wid in Will.only_valid(self.bal_window.willitems): for wid in Will.only_valid(self.bal_window.willitems):
self.bal_window.wallet.set_label(wid, "BAL Transaction") self.bal_window.wallet.set_label(wid, "BAL Transaction")
self.msg_set_building(self.msg_ok()) self.msg_set_building("Ok")
except WillExecutorNotPresent:
self.msg_set_status(
_("Will-Executor excluded"), None, _("Skipped"), self.COLOR_ERROR
)
except Exception as e: except Exception as e:
self.msg_set_building(self.msg_error(e)) self.msg_set_building(self.msg_error(e))
return False, None return False, None
# excluded_heirs = [] excluded_heirs = []
for wid in Will.only_valid(self.bal_window.willitems): for wid in Will.only_valid(self.bal_window.willitems):
heirs = self.bal_window.willitems[wid].heirs heirs = self.bal_window.willitems[wid].heirs
for hid, heir in heirs.items(): for hid, heir in heirs.items():
if "DUST" in str(heir[HEIR_REAL_AMOUNT]): if "DUST" in str(heir[HEIR_REAL_AMOUNT]):
self.msg_set_status( self.msg_set_status(
f"{hid},{heir[HEIR_DUST_AMOUNT]} is DUST", f'<font color="red">{hid},{heir[HEIR_DUST_AMOUNT]} is DUST',
None, None,
f"Excluded from will {wid}", f"Excluded from will {wid}</font>",
self.COLOR_WARNING,
) )
have_to_sign = False have_to_sign = False
@@ -2201,7 +2181,7 @@ class BalBuildWillDialog(BalDialog):
for i in range(secs, 0, -1): for i in range(secs, 0, -1):
if self._stopping: if self._stopping:
return return
wait_row = self.msg_edit_row(_(f"Please wait {i}secs"), wait_row) wait_row = self.msg_edit_row(f"Please wait {i}secs", wait_row)
time.sleep(1) time.sleep(1)
self.msg_del_row(wait_row) self.msg_del_row(wait_row)
@@ -2213,19 +2193,19 @@ class BalBuildWillDialog(BalDialog):
txid = self.network.run_from_another_thread( txid = self.network.run_from_another_thread(
self.network.broadcast_transaction(tx, timeout=120), timeout=120 self.network.broadcast_transaction(tx, timeout=120), timeout=120
) )
self.msg_set_invalidating(self.msg_ok()) self.msg_set_invalidating("Ok")
if not txid: if not txid:
_logger.debug(f"should not be none txid: {txid}") _logger.debug(f"should not be none txid: {txid}")
except TxBroadcastError as e: except TxBroadcastError as e:
_logger.error(f"fail to broadcast transaction:{e}") _logger.error(e)
msg = e.get_message_for_gui() msg = e.get_message_for_gui()
self.msg_set_invalidating(self.msg_error(msg)) self.msg_set_invalidating(self.msg_error(msg))
except BestEffortRequestFailed as e: except BestEffortRequestFailed as e:
self.msg_set_invalidating(self.msg_error(e)) self.msg_set_invalidating(self.msg_error(e))
def loop_push(self): def loop_push(self):
self.msg_set_pushing(_("Broadcasting")) self.msg_set_pushing("Broadcasting")
retry = False retry = False
try: try:
willexecutors = Willexecutors.get_willexecutor_transactions( willexecutors = Willexecutors.get_willexecutor_transactions(
@@ -2269,7 +2249,7 @@ class BalBuildWillDialog(BalDialog):
except Exception as e: except Exception as e:
_logger.error(f"loop push error:{e}") _logger.error(e)
raise e raise e
if retry: if retry:
raise Exception("retry") raise Exception("retry")
@@ -2282,7 +2262,7 @@ class BalBuildWillDialog(BalDialog):
def invalidate_task(self, password, bal_window, tx): def invalidate_task(self, password, bal_window, tx):
_logger.debug(f"invalidate tx: {tx}") _logger.debug(f"invalidate tx: {tx}")
# fee_per_byte = bal_window.will_settings.get("baltx_fees", 1) fee_per_byte = bal_window.will_settings.get("baltx_fees", 1)
tx = self.bal_window.wallet.sign_transaction(tx, password) tx = self.bal_window.wallet.sign_transaction(tx, password)
try: try:
if tx: if tx:
@@ -2320,10 +2300,10 @@ class BalBuildWillDialog(BalDialog):
# need to sign invalidate and restart phase 1 # need to sign invalidate and restart phase 1
password = self.bal_window.get_wallet_password( password = self.bal_window.get_wallet_password(
_("Invalidate your old will"), parent=self "Invalidate your old will", parent=self
) )
if password is False: if password is False:
self.msg_set_invalidating(_("Aborted")) self.msg_set_invalidating("Aborted")
self.wait(3) self.wait(3)
self.close() self.close()
return return
@@ -2338,12 +2318,12 @@ class BalBuildWillDialog(BalDialog):
elif self.have_to_sign: elif self.have_to_sign:
password = self.bal_window.get_wallet_password( password = self.bal_window.get_wallet_password(
_("Sign your will"), parent=self "Sign your will", parent=self
) )
if password is False: if password is False:
self.msg_set_signing(_("Aborted")) self.msg_set_signing("Aborted")
else: else:
self.msg_set_signing(_("Nothing to do")) self.msg_set_signing("Nothing to do")
self.thread.add( self.thread.add(
partial(self.task_phase2, password), partial(self.task_phase2, password),
on_success=self.on_success_phase2, on_success=self.on_success_phase2,
@@ -2355,7 +2335,7 @@ class BalBuildWillDialog(BalDialog):
def on_success_phase2(self, arg=False): def on_success_phase2(self, arg=False):
self.thread.stop() self.thread.stop()
self.bal_window.save_willitems() self.bal_window.save_willitems()
self.msg_edit_row(_("Finished")) self.msg_edit_row("Finished")
self.close() self.close()
def closeEvent(self, event): def closeEvent(self, event):
@@ -2370,7 +2350,7 @@ class BalBuildWillDialog(BalDialog):
for txid, tx in txs.items(): for txid, tx in txs.items():
self.bal_window.willitems[txid].tx = copy.deepcopy(tx) self.bal_window.willitems[txid].tx = copy.deepcopy(tx)
self.bal_window.save_willitems() self.bal_window.save_willitems()
self.msg_set_signing(self.msg_ok()) self.msg_set_signing("Ok")
except Exception as e: except Exception as e:
self.msg_set_signing(self.msg_error(e)) self.msg_set_signing(self.msg_error(e))
@@ -2381,31 +2361,31 @@ class BalBuildWillDialog(BalDialog):
if w.we and w.get_status("COMPLETE") and not w.get_status("PUSHED"): if w.we and w.get_status("COMPLETE") and not w.get_status("PUSHED"):
have_to_push = True have_to_push = True
if not have_to_push: if not have_to_push:
self.msg_set_pushing(_("Nothing to do")) self.msg_set_pushing("Nothing to do")
else: else:
try: try:
self.loop_push() self.loop_push()
self.msg_set_pushing(self.msg_ok()) self.msg_set_pushing("Ok")
except Exception as e: except Exception as e:
self.msg_set_pushing(self.msg_error(e)) self.msg_set_pushing(self.msg_error(e))
self.msg_edit_row(self.msg_ok()) self.msg_edit_row("Ok")
self.wait(5) self.wait(5)
def on_error_phase1(self, error): def on_error_phase1(self, error):
_logger.error(f"error phase1: {error}") _logger.error(f"error phase1: {error}")
def on_error_phase2(self, error): def on_error_phase2(self, error):
_logger.error(f"error phase2: { error}") _logger.error("error phase2: { error}")
def msg_set_checking(self, status="Waiting", row=None): def msg_set_checking(self, status="Waiting", row=None):
row = self.check_row if row is None else row row = self.check_row if row is None else row
self.check_row = self.msg_set_status(_("Checking your will"), row, status) self.check_row = self.msg_set_status("Checking your will", row, status)
def msg_set_invalidating(self, status=None, row=None): def msg_set_invalidating(self, status=None, row=None):
row = self.inval_row if row is None else row row = self.inval_row if row is None else row
self.inval_row = self.msg_set_status( self.inval_row = self.msg_set_status(
_("Invalidating old will"), self.inval_row, status "Invalidating old will", self.inval_row, status
) )
def msg_set_building(self, status=None, row=None): def msg_set_building(self, status=None, row=None):
@@ -2429,17 +2409,11 @@ class BalBuildWillDialog(BalDialog):
self.wait_row = self.msg_edit_row(f"Please wait {status}secs", self.wait_row) self.wait_row = self.msg_edit_row(f"Please wait {status}secs", self.wait_row)
def msg_error(self, e): def msg_error(self, e):
return "<font color='{}'>{}</font>".format(self.COLOR_ERROR, e) return "Error: {}".format(e)
def msg_ok(self, e="Ok"): def msg_set_status(self, msg, row=None, status=None):
return "<font color='{}'>{}</font>".format(self.COLOR_OK, e)
def msg_warning(self, e):
return "<font color='{}'>{}</font".format(self.COLOR_WARNING, e)
def msg_set_status(self, msg, row=None, status=None, color="#000000"):
status = "Wait" if status is None else status status = "Wait" if status is None else status
line = "<font color={}>{}:\t{}</font>".format(color, _(msg), status) line = "{}:\t{}".format(_(msg), status)
return self.msg_edit_row(line, row) return self.msg_edit_row(line, row)
def ask_password(self, msg=None): def ask_password(self, msg=None):
@@ -2558,10 +2532,6 @@ class HeirList(MyTreeView, MessageBoxMixin):
except Exception: except Exception:
self.update() self.update()
def delete_heirs(self, selected_keys):
self.bal_window.delete_heirs(selected_keys)
self.update()
def create_menu(self, position): def create_menu(self, position):
menu = QMenu() menu = QMenu()
idx = self.indexAt(position) idx = self.indexAt(position)
@@ -2589,7 +2559,9 @@ class HeirList(MyTreeView, MessageBoxMixin):
_("Edit {}").format(column_title), _("Edit {}").format(column_title),
lambda p=persistent: self.edit(QModelIndex(p)), lambda p=persistent: self.edit(QModelIndex(p)),
) )
menu.addAction(_("Delete"), lambda: self.delete_heirs(selected_keys)) menu.addAction(
_("Delete"), lambda: self.bal_window.delete_heirs(selected_keys)
)
menu.exec(self.viewport().mapToGlobal(position)) menu.exec(self.viewport().mapToGlobal(position))
# def get_selected_keys(self): # def get_selected_keys(self):
@@ -2599,6 +2571,8 @@ class HeirList(MyTreeView, MessageBoxMixin):
# selected_keys.append(sel_key) # selected_keys.append(sel_key)
# return selected_keys # return selected_keys
def update(self): def update(self):
if self.maybe_defer_update():
return
current_key = self.get_role_data_for_current_item( current_key = self.get_role_data_for_current_item(
col=self.Columns.NAME, role=self.ROLE_HEIR_KEY col=self.Columns.NAME, role=self.ROLE_HEIR_KEY
) )
@@ -2968,7 +2942,7 @@ class PreviewList(MyTreeView):
display = QPushButton(_("Display")) display = QPushButton(_("Display"))
display.clicked.connect(self.bal_window.preview_modal_dialog) display.clicked.connect(self.bal_window.preview_modal_dialog)
display = QPushButton(_("Refresh")) display = QPushButton(_("refresh"))
display.clicked.connect(self.check) display.clicked.connect(self.check)
widget = QWidget() widget = QWidget()
@@ -3455,6 +3429,7 @@ class WillExecutorList(MyTreeView):
pass pass
def update(self): def update(self):
if self.parent.willexecutors_list is None: if self.parent.willexecutors_list is None:
return return
try: try:
@@ -3539,8 +3514,7 @@ class WillExecutorList(MyTreeView):
self.parent.save_willexecutors() self.parent.save_willexecutors()
except Exception as e: except Exception as e:
_logger.error(f"error updating willexcutor {e}") _logger.error(e)
raise e
class WillExecutorWidget(QWidget, MessageBoxMixin): class WillExecutorWidget(QWidget, MessageBoxMixin):
@@ -3608,7 +3582,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
self.willexecutor_list.update() self.willexecutor_list.update()
def download_list(self): def download_list(self):
self.willexecutors_list.update(Willexecutors.download_list(self.bal_plugin,self.bal_window.willexecutors)) self.willexecutors_list.update(Willexecutors.download_list(self.bal_plugin))
self.willexecutor_list.update() self.willexecutor_list.update()
def export_file(self, path): def export_file(self, path):
@@ -3622,7 +3596,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
def import_file(self): def import_file(self):
import_meta_gui( import_meta_gui(
self.bal_window.window, self.bal_window.window,
_("willexecutors"), _("willexecutors.json"),
self.import_json_file, self.import_json_file,
self.willexecutors_list.update, self.willexecutors_list.update,
) )

88
util.py
View File

@@ -1,10 +1,12 @@
import bisect import bisect
import urllib.parse
import urllib.request
from datetime import datetime, timedelta from datetime import datetime, timedelta
from electrum.gui.qt.util import getSaveFileName from electrum.gui.qt.util import getSaveFileName
from electrum.i18n import _ from electrum.i18n import _
from electrum.transaction import PartialTxOutput from electrum.transaction import PartialTxOutput
from electrum.util import FileExportFailed from electrum.util import FileExportFailed, FileImportFailed, write_json_file
LOCKTIME_THRESHOLD = 500000000 LOCKTIME_THRESHOLD = 500000000
@@ -17,7 +19,7 @@ class Util:
dt = datetime.fromtimestamp(locktime).isoformat() dt = datetime.fromtimestamp(locktime).isoformat()
return dt return dt
except Exception: except Exception as e:
pass pass
return str(locktime) return str(locktime)
@@ -27,7 +29,7 @@ class Util:
return locktime return locktime
else: else:
return int(locktime) return int(locktime)
except Exception: except Exception as e:
pass pass
dt_object = datetime.fromisoformat(locktime) dt_object = datetime.fromisoformat(locktime)
timestamp = dt_object.timestamp() timestamp = dt_object.timestamp()
@@ -37,7 +39,7 @@ class Util:
try: try:
return int(locktime) return int(locktime)
except Exception: except Exception as e:
pass pass
try: try:
now = datetime.now() now = datetime.now()
@@ -56,7 +58,7 @@ class Util:
height = Util.get_current_height(w.network) height = Util.get_current_height(w.network)
locktime += int(height) locktime += int(height)
return int(locktime) return int(locktime)
except Exception: except Exception as e:
pass pass
return 0 return 0
@@ -75,34 +77,32 @@ class Util:
else: else:
try: try:
return int(float(amount) * pow(10, decimal_point)) return int(float(amount) * pow(10, decimal_point))
except Exception: except:
return 0 return 0
def decode_amount(amount, decimal_point): def decode_amount(amount, decimal_point):
if Util.is_perc(amount): if Util.is_perc(amount):
return amount return amount
else: else:
basestr = "{{:0.{}f}}".format(decimal_point) num = 8 - decimal_point
try: basestr = "{{:0{}.{}f}}".format(num, num)
return basestr.format(float(amount) / pow(10, decimal_point)) return "{:08.8f}".format(float(amount) / pow(10, decimal_point))
except Exception:
return str(amount)
def is_perc(value): def is_perc(value):
try: try:
return value[-1] == "%" return value[-1] == "%"
except Exception: except:
return False return False
def cmp_array(heira, heirb): def cmp_array(heira, heirb):
try: try:
if len(heira) != len(heirb): if not len(heira) == len(heirb):
return False return False
for h in range(0, len(heira)): for h in range(0, len(heira)):
if heira[h] != heirb[h]: if not heira[h] == heirb[h]:
return False return False
return True return True
except Exception: except:
return False return False
def cmp_heir(heira, heirb): def cmp_heir(heira, heirb):
@@ -120,7 +120,7 @@ class Util:
and willexecutora["base_fee"] == willexecutorb["base_fee"] and willexecutora["base_fee"] == willexecutorb["base_fee"]
): ):
return True return True
except Exception: except:
return False return False
return False return False
@@ -146,7 +146,7 @@ class Util:
): ):
for heira in heirsa: for heira in heirsa:
if ( if (
exclude_willexecutors and 'w!ll3x3c"' not in heira exclude_willexecutors and not 'w!ll3x3c"' in heira
) or not exclude_willexecutors: ) or not exclude_willexecutors:
found = False found = False
for heirb in heirsb: for heirb in heirsb:
@@ -173,8 +173,8 @@ class Util:
): ):
try: try:
for heir in heirsa: for heir in heirsa:
if 'w!ll3x3c"' not in heir: if not 'w!ll3x3c"' in heir:
if heir not in heirsb or not cmp_function( if not heir in heirsb or not cmp_function(
heirsa[heir], heirsb[heir] heirsa[heir], heirsb[heir]
): ):
if not Util.search_heir_by_values(heirsb, heirsa[heir], [0, 3]): if not Util.search_heir_by_values(heirsb, heirsa[heir], [0, 3]):
@@ -213,7 +213,7 @@ class Util:
def get_value_amount(txa, txb): def get_value_amount(txa, txb):
outputsa = txa.outputs() outputsa = txa.outputs()
# outputsb = txb.outputs() outputsb = txb.outputs()
value_amount = 0 value_amount = 0
for outa in outputsa: for outa in outputsa:
@@ -258,10 +258,10 @@ class Util:
def cmp_locktime(locktimea, locktimeb): def cmp_locktime(locktimea, locktimeb):
if locktimea == locktimeb: if locktimea == locktimeb:
return 0 return 0
strlocktimea = str(locktimea) strlocktime = str(locktimea)
strlocktimeb = str(locktimeb) strlocktimeb = str(locktimeb)
# intlocktimea = Util.str_to_locktime(strlocktimea) intlocktimea = Util.str_to_locktime(strlocktimea)
# intlocktimeb = Util.str_to_locktime(strlocktimeb) intlocktimeb = Util.str_to_locktime(strlocktimeb)
if locktimea[-1] in "ydb": if locktimea[-1] in "ydb":
if locktimeb[-1] == locktimea[-1]: if locktimeb[-1] == locktimea[-1]:
return int(strlocktimea[-1]) - int(strlocktimeb[-1]) return int(strlocktimea[-1]) - int(strlocktimeb[-1])
@@ -282,12 +282,12 @@ class Util:
def get_lowest_locktimes(locktimes): def get_lowest_locktimes(locktimes):
sorted_timestamp = [] sorted_timestamp = []
sorted_block = [] sorted_block = []
for locktime in locktimes: for l in locktimes:
locktime = Util.parse_locktime_string(locktime) l = Util.parse_locktime_string(l)
if locktime < LOCKTIME_THRESHOLD: if l < LOCKTIME_THRESHOLD:
bisect.insort(sorted_block, locktime) bisect.insort(sorted_block, l)
else: else:
bisect.insort(sorted_timestamp, locktime) bisect.insort(sorted_timestamp, l)
return sorted(sorted_timestamp), sorted(sorted_block) return sorted(sorted_timestamp), sorted(sorted_block)
@@ -313,11 +313,11 @@ class Util:
def utxo_to_str(utxo): def utxo_to_str(utxo):
try: try:
return utxo.to_str() return utxo.to_str()
except Exception: except Exception as e:
pass pass
try: try:
return utxo.prevout.to_str() return utxo.prevout.to_str()
except Exception: except Exception as e:
pass pass
return str(utxo) return str(utxo)
@@ -380,7 +380,7 @@ class Util:
out.is_change = True out.is_change = True
return out return out
def get_current_height(network): def get_current_height(network: "Network"):
# if no network or not up to date, just set locktime to zero # if no network or not up to date, just set locktime to zero
if not network: if not network:
return 0 return 0
@@ -403,34 +403,38 @@ class Util:
def print_var(var, name="", veryverbose=False): def print_var(var, name="", veryverbose=False):
print(f"---{name}---") print(f"---{name}---")
if var is not None: if not var is None:
try:
print("doc:", doc(var))
except:
pass
try: try:
print("str:", str(var)) print("str:", str(var))
except Exception: except:
pass pass
try: try:
print("repr", repr(var)) print("repr", repr(var))
except Exception: except:
pass pass
try: try:
print("dict", dict(var)) print("dict", dict(var))
except Exception: except:
pass pass
try: try:
print("dir", dir(var)) print("dir", dir(var))
except Exception: except:
pass pass
try: try:
print("type", type(var)) print("type", type(var))
except Exception: except:
pass pass
try: try:
print("to_json", var.to_json()) print("to_json", var.to_json())
except Exception: except:
pass pass
try: try:
print("__slotnames__", var.__slotnames__) print("__slotnames__", var.__slotnames__)
except Exception: except:
pass pass
print(f"---end {name}---") print(f"---end {name}---")
@@ -452,11 +456,11 @@ class Util:
Util.print_var(prevout._asdict()) Util.print_var(prevout._asdict())
print(f"---prevout-end {name}---") print(f"---prevout-end {name}---")
def export_meta_gui(electrum_window, title, exporter): def export_meta_gui(electrum_window: "ElectrumWindow", title, exporter):
filter_ = "All files (*)" filter_ = "All files (*)"
filename = getSaveFileName( filename = getSaveFileName(
parent=electrum_window, parent=electrum_window,
title=_("Select file to save your {}".format(title)), title=_("Select file to save your {}").format(title),
filename="BALplugin_{}".format(title), filename="BALplugin_{}".format(title),
filter=filter_, filter=filter_,
config=electrum_window.config, config=electrum_window.config,
@@ -469,7 +473,7 @@ class Util:
electrum_window.show_critical(str(e)) electrum_window.show_critical(str(e))
else: else:
electrum_window.show_message( electrum_window.show_message(
_("Your {0} were exported to '{1}'".format(title, str(filename))) _("Your {0} were exported to '{1}'").format(title, str(filename))
) )
def copy(dicto, dictfrom): def copy(dicto, dictfrom):

View File

@@ -1,7 +1,7 @@
#!env/bin/python3 #!env/bin/python3
from electrum.storage import WalletStorage from electrum.storage import WalletStorage
import json
from electrum.util import MyEncoder from electrum.util import MyEncoder
import json
import sys import sys
import getpass import getpass
import os import os
@@ -47,12 +47,12 @@ def save(json_wallet, storage):
def read_wallet(path, password=False): def read_wallet(path, password=False):
storage = WalletStorage(path) storage = WalletStorage(path)
if storage.is_encrypted(): if storage.is_encrypted():
if not password: if password == False:
password = getpass.getpass("Enter wallet password: ", stream=None) password = getpass.getpass("Enter wallet password: ", stream=None)
storage.decrypt(password) storage.decrypt(password)
data = storage.read() data = storage.read()
json_wallet = json.loads("[" + data + "]")[0] json_wallet = json.loads("[" + data + "]")[0]
return json_wallet, storage return json_wallet
if __name__ == "__main__": if __name__ == "__main__":
@@ -65,7 +65,7 @@ if __name__ == "__main__":
exit(1) exit(1)
command = sys.argv[1] command = sys.argv[1]
path = sys.argv[2] path = sys.argv[2]
json_wallet, storage = read_wallet(path) json_wallet = read_wallet(path)
have_to_save = False have_to_save = False
if command == "fix": if command == "fix":
have_to_save = fix_will_settings_tx_fees(json_wallet) have_to_save = fix_will_settings_tx_fees(json_wallet)

View File

@@ -15,8 +15,10 @@ from PyQt6.QtWidgets import (
QGroupBox, QGroupBox,
QTextEdit, QTextEdit,
) )
from PyQt6.QtCore import Qt
from electrum.storage import WalletStorage from electrum.storage import WalletStorage
from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, save from electrum.util import MyEncoder
from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, read_wallet, save
class WalletUtilityGUI(QMainWindow): class WalletUtilityGUI(QMainWindow):
@@ -188,6 +190,14 @@ class WalletUtilityGUI(QMainWindow):
def main(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)
# Check if dependencies are available
try:
from electrum.storage import WalletStorage
from electrum.util import MyEncoder
except ImportError as e:
print(f"ERROR: Cannot import Electrum dependencies: {str(e)}")
return 1
window = WalletUtilityGUI() window = WalletUtilityGUI()
window.show() window.show()

94
will.py
View File

@@ -12,7 +12,11 @@ from electrum.transaction import (
tx_from_any, tx_from_any,
) )
from electrum.util import ( from electrum.util import (
FileImportFailed,
bfh, bfh,
decimal_point_to_base_unit_name,
read_json_file,
write_json_file,
) )
from .util import Util from .util import Util
@@ -81,7 +85,7 @@ class Will:
txin._trusted_value_sats = change.value txin._trusted_value_sats = change.value
try: try:
txin.script_descriptor = change.script_descriptor txin.script_descriptor = change.script_descriptor
except Exception: except:
pass pass
txin.is_mine = True txin.is_mine = True
txin._TxInput__address = change.address txin._TxInput__address = change.address
@@ -128,8 +132,8 @@ class Will:
to_delete.append(wid) to_delete.append(wid)
to_add[ow.tx.txid()] = ow.to_dict() to_add[ow.tx.txid()] = ow.to_dict()
# for eid, err in errors.items(): for eid, err in errors.items():
# new_txid = err.tx.txid() new_txid = err.tx.txid()
for k, w in to_add.items(): for k, w in to_add.items():
will[k] = w will[k] = w
@@ -197,7 +201,7 @@ class Will:
outputs = w.tx.outputs() outputs = w.tx.outputs()
found = False found = False
old_txid = w.tx.txid() old_txid = w.tx.txid()
# ntx = None ntx = None
for i in range(0, len(inputs)): for i in range(0, len(inputs)):
if ( if (
inputs[i].prevout.txid.hex() == otxid inputs[i].prevout.txid.hex() == otxid
@@ -208,7 +212,7 @@ class Will:
will[wid].tx.set_rbf(True) will[wid].tx.set_rbf(True)
will[wid].tx._inputs[i] = Will.new_input(wid, idx, change) will[wid].tx._inputs[i] = Will.new_input(wid, idx, change)
found = True found = True
if found: if found == True:
pass pass
new_txid = will[wid].tx.txid() new_txid = will[wid].tx.txid()
@@ -235,7 +239,7 @@ class Will:
for i in inputs: for i in inputs:
prevout_str = i.prevout.to_str() prevout_str = i.prevout.to_str()
inp = [w, will[w], i] inp = [w, will[w], i]
if prevout_str not in all_inputs: if not prevout_str in all_inputs:
all_inputs[prevout_str] = [inp] all_inputs[prevout_str] = [inp]
else: else:
all_inputs[prevout_str].append(inp) all_inputs[prevout_str].append(inp)
@@ -248,7 +252,7 @@ class Will:
min_locktime = min(values, key=lambda x: x[1].tx.locktime)[1].tx.locktime min_locktime = min(values, key=lambda x: x[1].tx.locktime)[1].tx.locktime
for w in values: for w in values:
if w[1].tx.locktime == min_locktime: if w[1].tx.locktime == min_locktime:
if i not in all_inputs_min_locktime: if not i in all_inputs_min_locktime:
all_inputs_min_locktime[i] = [w] all_inputs_min_locktime[i] = [w]
else: else:
all_inputs_min_locktime[i].append(w) all_inputs_min_locktime[i].append(w)
@@ -286,7 +290,7 @@ class Will:
for w in to_delete: for w in to_delete:
try: try:
del will[w] del will[w]
except Exception: except:
pass pass
for k, w in to_append.items(): for k, w in to_append.items():
will[k] = w will[k] = w
@@ -296,8 +300,8 @@ class Will:
def update_will(old_will, new_will): def update_will(old_will, new_will):
all_old_inputs = Will.get_all_inputs(old_will, only_valid=True) all_old_inputs = Will.get_all_inputs(old_will, only_valid=True)
# all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs) all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
# all_new_inputs = Will.get_all_inputs(new_will) all_new_inputs = Will.get_all_inputs(new_will)
# check if the new input is already spent by other transaction # check if the new input is already spent by other transaction
# if it is use the same locktime, or anticipate. # if it is use the same locktime, or anticipate.
Will.search_anticipate_rec(new_will, all_old_inputs) Will.search_anticipate_rec(new_will, all_old_inputs)
@@ -341,9 +345,9 @@ class Will:
prevout_to_spend = [] prevout_to_spend = []
for prevout_str, ws in inputs.items(): for prevout_str, ws in inputs.items():
for w in ws: for w in ws:
if w[0] not in filtered_inputs: if not w[0] in filtered_inputs:
filtered_inputs.append(w[0]) filtered_inputs.append(w[0])
if prevout_str not in prevout_to_spend: if not prevout_str in prevout_to_spend:
prevout_to_spend.append(prevout_str) prevout_to_spend.append(prevout_str)
balance = 0 balance = 0
utxo_to_spend = [] utxo_to_spend = []
@@ -387,7 +391,7 @@ class Will:
return True return True
def search_rai(all_inputs, all_utxos, will, wallet): def search_rai(all_inputs, all_utxos, will, wallet):
# will_only_valid = Will.only_valid_or_replaced_list(will) will_only_valid = Will.only_valid_or_replaced_list(will)
for inp, ws in all_inputs.items(): for inp, ws in all_inputs.items():
inutxo = Util.in_utxo(inp, all_utxos) inutxo = Util.in_utxo(inp, all_utxos)
for w in ws: for w in ws:
@@ -425,7 +429,7 @@ class Will:
def set_invalidate(wid, will=[]): def set_invalidate(wid, will=[]):
will[wid].set_status("INVALIDATED", True) will[wid].set_status("INVALIDATED", True)
if will[wid].children: if will[wid].children:
for c in will[wid].children.items(): for c in self.children.items():
Will.set_invalidate(c[0], will) Will.set_invalidate(c[0], will)
def check_tx_height(tx, wallet): def check_tx_height(tx, wallet):
@@ -442,7 +446,7 @@ class Will:
): ):
for inp in w.tx.inputs(): for inp in w.tx.inputs():
inp_str = Util.utxo_to_str(inp) inp_str = Util.utxo_to_str(inp)
if inp_str not in utxos_list: if not inp_str in utxos_list:
if wallet: if wallet:
height = Will.check_tx_height(w.tx, wallet) height = Will.check_tx_height(w.tx, wallet)
if height < 0: if height < 0:
@@ -452,21 +456,21 @@ class Will:
else: else:
w.set_status("CONFIRMED", True) w.set_status("CONFIRMED", True)
# def reflect_to_children(treeitem): def reflect_to_children(treeitem):
# if not treeitem.get_status("VALID"): if not treeitem.get_status("VALID"):
# _logger.debug(f"{tree:item._id} status not valid looking for children") _logger.debug(f"{tree:item._id} status not valid looking for children")
# for child in treeitem.children: for child in treeitem.children:
# wc = willtree[child] wc = willtree[child]
# if wc.get_status("VALID"): if wc.get_status("VALID"):
# if treeitem.get_status("INVALIDATED"): if treeitem.get_status("INVALIDATED"):
# wc.set_status("INVALIDATED", True) wc.set_status("INVALIDATED", True)
# if treeitem.get_status("REPLACED"): if treeitem.get_status("REPLACED"):
# wc.set_status("REPLACED", True) wc.set_status("REPLACED", True)
# if wc.children: if wc.children:
# Will.reflect_to_children(wc) Will.reflect_to_children(wc)
def check_amounts(heirs, willexecutors, all_utxos, timestamp_to_check, dust): def check_amounts(heirs, willexecutors, all_utxos, timestamp_to_check, dust):
fixed_heirs, fixed_amount, perc_heirs, perc_amount, fixed_amount_with_dust = ( fixed_heirs, fixed_amount, perc_heirs, perc_amount = (
heirs.fixed_percent_lists_amount(timestamp_to_check, dust, reverse=True) heirs.fixed_percent_lists_amount(timestamp_to_check, dust, reverse=True)
) )
wallet_balance = 0 wallet_balance = 0
@@ -561,16 +565,16 @@ class Will:
f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}" f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}"
) )
# def check_all_input_spent_are_in_wallet(): def check_all_input_spent_are_in_wallet():
# _logger.info("check all input spent are in wallet or valid txs") _logger.info("check all input spent are in wallet or valid txs")
# for inp, ws in all_inputs.items(): for inp, ws in all_inputs.items():
# if not Util.in_utxo(inp, all_utxos): if not Util.in_utxo(inp, all_utxos):
# for w in ws: for w in ws:
# if w[1].get_status("VALID"): if w[1].get_status("VALID"):
# prevout_id = w[2].prevout.txid.hex() prevout_id = w[2].prevout.txid.hex()
# parentwill = will.get(prevout_id, False) parentwill = will.get(prevout_id, False)
# if not parentwill or not parentwill.get_status("VALID"): if not parentwill or not parentwill.get_status("VALID"):
# w[1].set_status("INVALIDATED", True) w[1].set_status("INVALIDATED", True)
def only_valid_list(will): def only_valid_list(will):
out = {} out = {}
@@ -616,9 +620,9 @@ class Will:
heirs_found[wheir] = count + 1 heirs_found[wheir] = count + 1
else: else:
_logger.debug( _logger.debug(
f"heir not present transaction is not valid:{wheir} {wid}, {w}" "heir not present transaction is not valid:", wid, w
) )
continue
if willexecutor := w.we: if willexecutor := w.we:
count = willexecutors_found.get(willexecutor["url"], 0) count = willexecutors_found.get(willexecutor["url"], 0)
if Util.cmp_willexecutor( if Util.cmp_willexecutor(
@@ -630,19 +634,19 @@ class Will:
no_willexecutor += 1 no_willexecutor += 1
count_heirs = 0 count_heirs = 0
for h in heirs: for h in heirs:
if Util.parse_locktime_string(heirs[h][2]) >= check_date: if Util.parse_locktime_string(heirs[h][2]) >= check_date:
count_heirs += 1 count_heirs += 1
if h not in heirs_found: if not h in heirs_found:
_logger.debug(f"heir: {h} not found") _logger.debug(f"heir: {h} not found")
raise HeirNotFoundException(h) raise HeirNotFoundException(h)
if not count_heirs: if not count_heirs:
raise NoHeirsException("there are not valid heirs") raise NoHeirsException("there are not valid heirs")
if self_willexecutor and no_willexecutor == 0: if self_willexecutor and no_willexecutor == 0:
raise NoWillExecutorNotPresent("Backup tx") raise NoWillExecutorNotPresent("Backup tx")
for url, we in willexecutors.items(): for url, we in willexecutors.items():
if Willexecutors.is_selected(we): if Willexecutors.is_selected(we):
if url not in willexecutors_found: if not url in willexecutors_found:
_logger.debug(f"will-executor: {url} not fount") _logger.debug(f"will-executor: {url} not fount")
raise WillExecutorNotPresent(url) raise WillExecutorNotPresent(url)
_logger.info("will is coherent with heirs and will-executors") _logger.info("will is coherent with heirs and will-executors")
@@ -884,3 +888,7 @@ class PercAmountException(AmountException):
class FixedAmountException(AmountException): class FixedAmountException(AmountException):
pass pass
def test_check_invalidated():
Will.check_invalidated(will, utxos_list, wallet)

View File

@@ -1,37 +1,33 @@
import json import json
from datetime import datetime from datetime import datetime
import time from functools import partial
from aiohttp import ClientResponse from aiohttp import ClientResponse
from electrum import constants from electrum import constants
from electrum.gui.qt.util import WaitingDialog
from electrum.i18n import _ from electrum.i18n import _
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.network import Network from electrum.network import Network
from .bal import BalPlugin from .bal import BalPlugin
from .util import Util
DEFAULT_TIMEOUT = 5 DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__) _logger = get_logger(__name__)
chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin"
class Willexecutors: class Willexecutors:
def save(bal_plugin, willexecutors): def save(bal_plugin, willexecutors):
_logger.debug(f"save {willexecutors},{chainname}")
aw = bal_plugin.WILLEXECUTORS.get() aw = bal_plugin.WILLEXECUTORS.get()
aw[chainname] = willexecutors aw[constants.net.NET_NAME] = willexecutors
bal_plugin.WILLEXECUTORS.set(aw) bal_plugin.WILLEXECUTORS.set(aw)
_logger.debug(f"saved: {aw}")
# bal_plugin.WILLEXECUTORS.set(willexecutors)
def get_willexecutors( def get_willexecutors(
bal_plugin, update=False, bal_window=False, force=False, task=True bal_plugin, update=False, bal_window=False, force=False, task=True
): ):
willexecutors = bal_plugin.WILLEXECUTORS.get() willexecutors = bal_plugin.WILLEXECUTORS.get()
willexecutors = willexecutors.get(chainname, {}) willexecutors = willexecutors.get(constants.net.NET_NAME, {})
to_del = [] to_del = []
for w in willexecutors: for w in willexecutors:
if not isinstance(willexecutors[w], dict): if not isinstance(willexecutors[w], dict):
@@ -40,14 +36,12 @@ class Willexecutors:
Willexecutors.initialize_willexecutor(willexecutors[w], w) Willexecutors.initialize_willexecutor(willexecutors[w], w)
for w in to_del: for w in to_del:
_logger.error( _logger.error(
"error Willexecutor to delete type:{} {}".format( "error Willexecutor to delete type:{} ", type(willexecutor[w]), w
type(willexecutors[w]), w
)
) )
del willexecutors[w] del willexecutors[w]
bal = bal_plugin.WILLEXECUTORS.default.get(chainname, {}) bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME, {})
for bal_url, bal_executor in bal.items(): for bal_url, bal_executor in bal.items():
if bal_url not in willexecutors: if not bal_url in willexecutors:
_logger.debug(f"force add {bal_url} willexecutor") _logger.debug(f"force add {bal_url} willexecutor")
willexecutors[bal_url] = bal_executor willexecutors[bal_url] = bal_executor
# if update: # if update:
@@ -81,11 +75,11 @@ class Willexecutors:
def is_selected(willexecutor, value=None): def is_selected(willexecutor, value=None):
if not willexecutor: if not willexecutor:
return False return False
if value is not None: if not value is None:
willexecutor["selected"] = value willexecutor["selected"] = value
try: try:
return willexecutor["selected"] return willexecutor["selected"]
except Exception: except:
willexecutor["selected"] = False willexecutor["selected"] = False
return False return False
@@ -98,7 +92,7 @@ class Willexecutors:
if willexecutor := willitem.we: if willexecutor := willitem.we:
url = willexecutor["url"] url = willexecutor["url"]
if willexecutor and Willexecutors.is_selected(willexecutor): if willexecutor and Willexecutors.is_selected(willexecutor):
if url not in willexecutors: if not url in willexecutors:
willexecutor["txs"] = "" willexecutor["txs"] = ""
willexecutor["txsids"] = [] willexecutor["txsids"] = []
willexecutor["broadcast_status"] = _("Waiting...") willexecutor["broadcast_status"] = _("Waiting...")
@@ -108,25 +102,23 @@ class Willexecutors:
return willexecutors return willexecutors
# def only_selected_list(willexecutors): def only_selected_list(willexecutors):
# out = {} out = {}
# for url, v in willexecutors.items(): for url, v in willexecutors.items():
# if Willexecutors.is_selected(url): if Willexecutors.is_selected(willexecutor):
# out[url] = v out[url] = v
# def push_transactions_to_willexecutors(will): def push_transactions_to_willexecutors(will):
# willexecutors = Willexecutors.get_transactions_to_be_pushed() willexecutors = get_transactions_to_be_pushed()
# for url in willexecutors: for url in willexecutors:
# willexecutor = willexecutors[url] willexecutor = willexecutors[url]
# if Willexecutors.is_selected(willexecutor): if Willexecutors.is_selected(willexecutor):
# if "txs" in willexecutor: if "txs" in willexecutor:
# Willexecutors.push_transactions_to_willexecutor( Willexecutors.push_transactions_to_willexecutor(
# willexecutors[url]["txs"], url willexecutors[url]["txs"], url
# ) )
def send_request( def send_request(method, url, data=None, *, timeout=10):
method, url, data=None, *, timeout=10, handle_response=None, count_reply=0
):
network = Network.get_instance() network = Network.get_instance()
if not network: if not network:
raise Exception("You are offline.") raise Exception("You are offline.")
@@ -134,8 +126,7 @@ class Willexecutors:
headers = {} headers = {}
headers["user-agent"] = f"BalPlugin v:{BalPlugin.version()}" headers["user-agent"] = f"BalPlugin v:{BalPlugin.version()}"
headers["Content-Type"] = "text/plain" headers["Content-Type"] = "text/plain"
if not handle_response:
handle_response = Willexecutors.handle_response
try: try:
if method == "get": if method == "get":
response = Network.send_http_on_proxy( response = Network.send_http_on_proxy(
@@ -143,7 +134,7 @@ class Willexecutors:
url, url,
params=data, params=data,
headers=headers, headers=headers,
on_finish=handle_response, on_finish=Willexecutors.handle_response,
timeout=timeout, timeout=timeout,
) )
elif method == "post": elif method == "post":
@@ -152,47 +143,26 @@ class Willexecutors:
url, url,
body=data, body=data,
headers=headers, headers=headers,
on_finish=handle_response, on_finish=Willexecutors.handle_response,
timeout=timeout, timeout=timeout,
) )
else: else:
raise Exception(f"unexpected {method=!r}") raise Exception(f"unexpected {method=!r}")
except TimeoutError:
if count_reply < 10:
_logger.debug(f"timeout({count_reply}) error: retry in 3 sec...")
time.sleep(3)
return Willexecutors.send_request(
method,
url,
data,
timeout=timeout,
handle_response=handle_response,
count_reply=count_reply + 1,
)
else:
_logger.debug(f"Too many timeouts: {count_reply}")
except Exception as e: except Exception as e:
_logger.error(f"exception sending request {e}")
raise e raise e
else: else:
_logger.debug(f"--> {response}") _logger.debug(f"--> {response}")
return response return response
def get_we_url_from_response(resp):
url_slices = str(resp.url).split("/")
if len(url_slices) > 2:
url_slices = url_slices[:-2]
return "/".join(url_slices)
async def handle_response(resp: ClientResponse): async def handle_response(resp: ClientResponse):
r = await resp.text() r = await resp.text()
try: try:
r = json.loads(r) r = json.loads(r)
# url = Willexecutors.get_we_url_from_response(resp) r["status"] = resp.status
# r["url"]= url r["selected"] = Willexecutors.is_selected(willexecutor)
# r["status"]=resp.status r["url"] = url
except Exception as e: except:
_logger.debug(f"error handling response:{e}")
pass pass
return r return r
@@ -202,10 +172,11 @@ class Willexecutors:
def push_transactions_to_willexecutor(willexecutor): def push_transactions_to_willexecutor(willexecutor):
out = True out = True
try: try:
_logger.debug(f"{willexecutor['url']}: {willexecutor['txs']}")
_logger.debug(f"{willexecutor[url]}: {willexecutor['txs']}")
if w := Willexecutors.send_request( if w := Willexecutors.send_request(
"post", "post",
willexecutor["url"] + "/" + chainname + "/pushtxs", willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs",
data=willexecutor["txs"].encode("ascii"), data=willexecutor["txs"].encode("ascii"),
): ):
willexecutor["broadcast_status"] = _("Success") willexecutor["broadcast_status"] = _("Success")
@@ -233,14 +204,18 @@ class Willexecutors:
try: try:
_logger.info("GETINFO_WILLEXECUTOR") _logger.info("GETINFO_WILLEXECUTOR")
_logger.debug(url) _logger.debug(url)
w = Willexecutors.send_request("get", url + "/" + chainname + "/info") netname = "bitcoin"
if isinstance(w, dict): if constants.net.NET_NAME != "mainnet":
willexecutor["url"] = url netname = constants.net.NET_NAME
willexecutor["status"] = 200 w = Willexecutors.send_request("get", url + "/" + netname + "/info")
willexecutor["base_fee"] = w["base_fee"]
willexecutor["address"] = w["address"] willexecutor["url"] = url
willexecutor["status"] = w["status"]
willexecutor["base_fee"] = w["base_fee"]
willexecutor["address"] = w["address"]
if not willexecutor["info"]:
willexecutor["info"] = w["info"] willexecutor["info"] = w["info"]
_logger.debug(f"response_data {w}") _logger.debug(f"response_data {w['address']}")
except Exception as e: except Exception as e:
_logger.error(f"error {e} contacting {url}: {w}") _logger.error(f"error {e} contacting {url}: {w}")
willexecutor["status"] = "KO" willexecutor["status"] = "KO"
@@ -248,33 +223,24 @@ class Willexecutors:
willexecutor["last_update"] = datetime.now().timestamp() willexecutor["last_update"] = datetime.now().timestamp()
return willexecutor return willexecutor
def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor={}): def initialize_willexecutor(willexecutor, url, status=None, selected=None):
willexecutor["url"] = url willexecutor["url"] = url
if status is not None: if not status is None:
willexecutor["status"] = status willexecutor["status"] = status
else: willexecutor["selected"] = Willexecutors.is_selected(willexecutor, selected)
willexecutor["status"] = old_willexecutor.get("status",willexecutor.get("status","Ko"))
willexecutor["selected"]=Willexecutors.is_selected(old_willexecutor) or willexecutor.get("selected",False)
willexecutor["address"]=old_willexecutor.get("address",willexecutor.get("address",""))
willexecutor["promo_code"]=old_willexecutor.get("promo_code",willexecutor.get("promo_code"))
def download_list(bal_plugin):
def download_list(bal_plugin,old_willexecutors):
try: try:
willexecutors = Willexecutors.send_request( l = Willexecutors.send_request(
"get", "get", "https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100"
f"https://welist.bitcoin-after.life/data/{chainname}?page=0&limit=100",
) )
# del willexecutors["status"] del l["status"]
for w in willexecutors: for w in l:
if w not in ("status", "url"): willexecutor = l[w]
Willexecutors.initialize_willexecutor( Willexecutors.initialize_willexecutor(willexecutor, w, "New", False)
willexecutors[w], w, None, old_willexecutors.get(w,{})
)
# bal_plugin.WILLEXECUTORS.set(l) # bal_plugin.WILLEXECUTORS.set(l)
# bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True) # bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True)
return willexecutors return l
except Exception as e: except Exception as e:
_logger.error(f"Failed to download willexecutors list: {e}") _logger.error(f"Failed to download willexecutors list: {e}")
@@ -288,7 +254,7 @@ class Willexecutors:
willexecutor = willexecutors[w] willexecutor = willexecutors[w]
Willexecutors.initialize_willexecutor(willexecutor, w, "New", False) Willexecutors.initialize_willexecutor(willexecutor, w, "New", False)
# bal_plugin.WILLEXECUTORS.set(willexecutors) # bal_plugin.WILLEXECUTORS.set(willexecutors)
return willexecutors return h
except Exception as e: except Exception as e:
_logger.error(f"error opening willexecutors json: {e}") _logger.error(f"error opening willexecutors json: {e}")
@@ -305,53 +271,14 @@ class Willexecutors:
_logger.error(f"error contacting {url} for checking txs {e}") _logger.error(f"error contacting {url} for checking txs {e}")
raise e raise e
def compute_id(willexecutor):
return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain"))
class WillExecutor: class WillExecutor:
def __init__( def __init__(self, url, base_fee, chain, info, version):
self,
url,
base_fee,
chain,
info,
version,
status,
is_selected=False,
promo_code="",
):
self.url = url self.url = url
self.base_fee = base_fee self.base_fee = base_fee
self.chain = chain self.chain = chain
self.info = info self.info = info
self.version = version self.version = version
self.status = status
self.promo_code = promo_code
self.is_selected = is_selected
self.id = self.compute_id()
def from_dict(d): def from_dict(d):
return WillExecutor( we = WillExecutor(d["url"], d["base_fee"], d["chain"], d["info"], d["version"])
url=d.get("url", "http://localhost:8000"),
base_fee=d.get("base_fee", 1000),
chain=d.get("chain", chainname),
info=d.get("info", ""),
version=d.get("version", 0),
status=d.get("status", "Ko"),
is_selected=d.get("is_selected", "False"),
promo_code=d.get("promo_code", ""),
)
def to_dict(self):
return {
"url": self.url,
"base_fee": self.base_fee,
"chain": self.chain,
"info": self.info,
"version": self.version,
"promo_code": self.promo_code,
}
def compute_id(self):
return f"{self.url}-{self.chain}"