7 Commits

Author SHA1 Message Date
9737221914 fix send_request version message 2026-03-18 16:25:59 -04:00
a022c413cc willexecutor manager improved 2026-03-17 02:34:01 -04:00
716d4dd5c5 version 2026-03-05 10:50:03 -04:00
b012dd7a68 black reformatted 2026-03-05 10:47:59 -04:00
ef0ab56de4 fixed refresh and some minor bug about dust amounts and empty wallet 2026-03-05 10:46:38 -04:00
c5ad5a61bb bug delete heirs from wizard 2026-02-10 12:22:43 -04:00
f7bd09df91 dust bugfix 2026-02-09 12:10:31 -04:00
10 changed files with 719 additions and 443 deletions

View File

@@ -1 +1 @@
0.2.4
0.2.6

49
bal.py
View File

@@ -1,6 +1,7 @@
import os
#import random
#import zipfile as zipfile_lib
# import random
# import zipfile as zipfile_lib
from electrum import json_db
from electrum.logging import get_logger
@@ -9,7 +10,7 @@ from electrum.transaction import tx_from_any
def get_will_settings(x):
#print(x)
# print(x)
pass
@@ -18,7 +19,6 @@ json_db.register_dict("will", dict, None)
json_db.register_dict("will_settings", lambda x: x, None)
def get_will(x):
try:
x["tx"] = tx_from_any(x["tx"])
@@ -47,18 +47,19 @@ class BalConfig:
class BalPlugin(BasePlugin):
LATEST_VERSION = "1"
KNOWN_VERSIONS = ("0", "1")
assert LATEST_VERSION in KNOWN_VERSIONS
_version=None
def version():
try:
f = ""
with open("VERSION", "r") as fi:
f = str(fi.readline())
return f
except:
return "unknown"
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)
@@ -90,10 +91,10 @@ class BalPlugin(BasePlugin):
self.PREVIEW = BalConfig(config, "bal_preview", True)
self.SAVE_TXS = BalConfig(config, "bal_save_txs", True)
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", True)
#self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True)
#self.ASK_PING_WILLEXECUTORS = BalConfig(
# self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True)
# self.ASK_PING_WILLEXECUTORS = BalConfig(
# config, "bal_ask_ping_willexecutors", True
#)
# )
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True)
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
@@ -111,7 +112,16 @@ class BalPlugin(BasePlugin):
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"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(
@@ -149,4 +159,3 @@ class BalPlugin(BasePlugin):
def default_will_settings(self):
return {"baltx_fees": 100, "threshold": "180d", "locktime": "1y"}

259
heirs.py
View File

@@ -1,23 +1,37 @@
import datetime
import json
# import datetime
# import json
import math
import random
import re
import threading
import urllib.parse
import urllib.request
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple
# import urllib.parse
# import urllib.request
from typing import (
TYPE_CHECKING,
Any,
Dict,
# List,
Optional,
# Sequence,
Tuple,
)
import dns
from dns.exception import DNSException
from electrum import bitcoin, constants, descriptor, dnssec
from electrum import (
bitcoin,
constants,
# descriptor,
dnssec,
)
from electrum.logging import Logger, get_logger
from electrum.transaction import (
PartialTransaction,
PartialTxInput,
PartialTxOutput,
TxOutpoint,
TxOutput,
# TxOutput,
)
from electrum.util import (
bfh,
@@ -29,10 +43,12 @@ 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
from .wallet_db import WalletDB
# from .wallet_db import WalletDB
_logger = get_logger(__name__)
@@ -41,6 +57,7 @@ HEIR_ADDRESS = 0
HEIR_AMOUNT = 1
HEIR_LOCKTIME = 2
HEIR_REAL_AMOUNT = 3
HEIR_DUST_AMOUNT = 4
TRANSACTION_LABEL = "inheritance transaction"
@@ -84,39 +101,45 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
x.value_sats(), x.prevout.txid, x.prevout.out_idx
),
)
total_used_utxos = []
# total_used_utxos = []
txsout = {}
locktime, _ = Util.get_lowest_locktimes(locktimes)
if not locktime:
_logger.info("prepare transactions, no locktime")
return
locktime = locktime[0]
heirs = locktimes[locktime]
vero = True
while vero:
vero = False
true = True
while true:
true = False
fee = fees.get(locktime, 0)
out_amount = fee
description = ""
outputs = []
paid_heirs = {}
for name, heir in heirs.items():
try:
if len(heir) > HEIR_REAL_AMOUNT:
if len(heir) > HEIR_REAL_AMOUNT and "DUST" not in str(
heir[HEIR_REAL_AMOUNT]
):
try:
real_amount = heir[HEIR_REAL_AMOUNT]
out_amount += real_amount
description += f"{name}\n"
paid_heirs[name] = heir
outputs.append(
PartialTxOutput.from_address_and_value(
heir[HEIR_ADDRESS], real_amount
)
)
else:
out_amount += real_amount
description += f"{name}\n"
except BitcoinException as e:
_logger.info("exception decoding output {} - {}".format(type(e), e))
heir[HEIR_REAL_AMOUNT] = e
except Exception as e:
heir[HEIR_REAL_AMOUNT] = e
_logger.error(f"error preparing transactions: {e}")
pass
except Exception as e:
pass
paid_heirs[name] = heir
in_amount = 0.0
used_utxos = []
@@ -125,13 +148,19 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
value = utxo.value_sats()
in_amount += value
used_utxos.append(utxo)
if in_amount > out_amount:
if in_amount >= out_amount:
break
except IndexError as e:
_logger.error(
f"error preparing transactions index error {e} {in_amount}, {out_amount}"
)
pass
if int(in_amount) < int(out_amount):
break
_logger.error(
"error preparing transactions in_amount < out_amount ({} < {}) "
)
continue
heirsvalue = out_amount
change = get_change_output(wallet, in_amount, out_amount, fee)
if change:
@@ -185,7 +214,7 @@ def get_utxos_from_inputs(tx_inputs, tx, utxos):
# TODO calculate de minimum inputs to be invalidated
def invalidate_inheritance_transactions(wallet):
listids = []
# listids = []
utxos = {}
dtxs = {}
for k, v in wallet.get_all_labels().items():
@@ -214,7 +243,7 @@ def invalidate_inheritance_transactions(wallet):
for key, value in utxos:
for tx in value["txs"]:
txid = tx.txid()
if not txid in invalidated:
if txid not in invalidated:
invalidated.append(tx.txid())
remaining[key] = value
@@ -222,10 +251,10 @@ def invalidate_inheritance_transactions(wallet):
def print_transaction(heirs, tx, locktimes, tx_fees):
jtx = tx.to_json()
print(f"TX: {tx.txid()}\t-\tLocktime: {jtx['locktime']}")
print(f"---")
print("---")
for inp in jtx["inputs"]:
print(f"{inp['address']}: {inp['value_sats']}")
print(f"---")
print("---")
for out in jtx["outputs"]:
heirname = ""
for key in heirs.keys():
@@ -247,7 +276,7 @@ def print_transaction(heirs, tx, locktimes, tx_fees):
print()
try:
print(tx.serialize_to_network())
except:
except Exception:
print("impossible to serialize")
print()
@@ -263,13 +292,14 @@ def get_change_output(wallet, in_amount, out_amount, fee):
class Heirs(dict, Logger):
def __init__(self, db: "WalletDB"):
def __init__(self, wallet):
Logger.__init__(self)
self.db = db
self.db = wallet.db
self.wallet = wallet
d = self.db.get("heirs", {})
try:
self.update(d)
except e as Exception:
except Exception:
return
def invalidate_transactions(self, wallet):
@@ -303,7 +333,7 @@ class Heirs(dict, Logger):
locktime = Util.parse_locktime_string(self[key][HEIR_LOCKTIME])
if locktime > from_locktime and not a or locktime <= from_locktime and a:
locktimes[int(locktime)] = None
return locktimes.keys()
return list(locktimes.keys())
def check_locktime(self):
return False
@@ -317,6 +347,8 @@ class Heirs(dict, Logger):
column = HEIR_AMOUNT
if real:
column = HEIR_REAL_AMOUNT
if "DUST" in str(v[column]):
column = HEIR_DUST_AMOUNT
value = int(
math.floor(
total_balance
@@ -327,6 +359,10 @@ class Heirs(dict, Logger):
if value > wallet.dust_threshold():
heir_list[key].insert(HEIR_REAL_AMOUNT, value)
amount += value
else:
heir_list[key].insert(HEIR_REAL_AMOUNT, f"DUST: {value}")
heir_list[key].insert(HEIR_DUST_AMOUNT, value)
_logger.info(f"{key}, {value} is dust will be ignored")
except Exception as e:
raise e
@@ -335,10 +371,10 @@ class Heirs(dict, Logger):
def amount_to_float(self, amount):
try:
return float(amount)
except:
except Exception:
try:
return float(amount[:-1])
except:
except Exception:
return 0.0
def fixed_percent_lists_amount(self, from_locktime, dust_threshold, reverse=False):
@@ -346,27 +382,44 @@ class Heirs(dict, Logger):
fixed_amount = 0.0
percent_heirs = {}
percent_amount = 0.0
fixed_amount_with_dust = 0.0
for key in self.keys():
try:
cmp = (
Util.parse_locktime_string(self[key][HEIR_LOCKTIME]) - from_locktime
)
if cmp <= 0:
_logger.debug(
"cmp < 0 {} {} {} {}".format(
cmp, key, self[key][HEIR_LOCKTIME], from_locktime
)
)
continue
if Util.is_perc(self[key][HEIR_AMOUNT]):
percent_amount += float(self[key][HEIR_AMOUNT][:-1])
percent_heirs[key] = list(self[key])
else:
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:
fixed_amount += heir_amount
fixed_heirs[key] = list(self[key])
fixed_heirs[key].insert(HEIR_REAL_AMOUNT, heir_amount)
else:
pass
fixed_heirs[key] = list(self[key])
fixed_heirs[key].insert(
HEIR_REAL_AMOUNT, f"DUST: {heir_amount}"
)
fixed_heirs[key].insert(HEIR_DUST_AMOUNT, heir_amount)
except Exception as e:
_logger.error(e)
return fixed_heirs, fixed_amount, percent_heirs, percent_amount
return (
fixed_heirs,
fixed_amount,
percent_heirs,
percent_amount,
fixed_amount_with_dust,
)
def prepare_lists(
self, balance, total_fees, wallet, willexecutor=False, from_locktime=0
@@ -391,7 +444,7 @@ class Heirs(dict, Logger):
willexecutors[
'w!ll3x3c"' + willexecutor["url"] + '"' + str(locktime)
] = h
except Exception as e:
except Exception:
return [], False
else:
_logger.error(
@@ -399,9 +452,16 @@ class Heirs(dict, Logger):
),
heir_list.update(willexecutors)
newbalance -= willexecutors_amount
fixed_heirs, fixed_amount, percent_heirs, percent_amount = (
self.fixed_percent_lists_amount(from_locktime, wallet.dust_threshold())
)
if newbalance < 0:
raise WillExecutorFeeException(willexecutor)
(
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:
fixed_amount = self.normalize_perc(
fixed_heirs, newbalance, fixed_amount, wallet
@@ -422,7 +482,7 @@ class Heirs(dict, Logger):
if newbalance > 0:
newbalance += fixed_amount
fixed_amount = self.normalize_perc(
fixed_heirs, newbalance, fixed_amount, wallet, real=True
fixed_heirs, newbalance, fixed_amount_with_dust, wallet, real=True
)
newbalance -= fixed_amount
heir_list.update(fixed_heirs)
@@ -435,7 +495,7 @@ class Heirs(dict, Logger):
locktimes = {}
for key, value in heir_list:
locktime = Util.parse_locktime_string(value[HEIR_LOCKTIME])
if not locktime in locktimes:
if locktime not in locktimes:
locktimes[locktime] = {key: value}
else:
locktimes[locktime][key] = value
@@ -449,6 +509,7 @@ class Heirs(dict, Logger):
):
Heirs._validate(self)
if len(self) <= 0:
_logger.info("while building transactions there was no heirs")
return
balance = 0.0
len_utxo_set = 0
@@ -464,6 +525,7 @@ class Heirs(dict, Logger):
len_utxo_set += 1
available_utxos.append(utxo)
if len_utxo_set == 0:
_logger.info("no usable utxos")
return
j = -2
willexecutorsitems = list(willexecutors.items())
@@ -487,51 +549,66 @@ class Heirs(dict, Logger):
break
fees = {}
i = 0
while True:
while i < 10:
txs = {}
redo = False
i += 1
total_fees = 0
for fee in fees:
total_fees += int(fees[fee])
newbalance = balance
locktimes, onlyfixed = self.prepare_lists(
balance, total_fees, wallet, willexecutor, from_locktime
)
# newbalance = balance
try:
txs = prepare_transactions(
locktimes, available_utxos[:], fees, wallet
locktimes, onlyfixed = self.prepare_lists(
balance, total_fees, wallet, willexecutor, from_locktime
)
if not txs:
return {}
except Exception as e:
except WillExecutorFeeException:
i = 10
continue
if locktimes:
try:
if "w!ll3x3c" in e.heirname:
Willexecutors.is_selected(willexecutors[w], False)
break
except:
raise e
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
txs = prepare_transactions(
locktimes, available_utxos[:], fees, wallet
)
if not txs:
return {}
except Exception as e:
_logger.error(
f"build transactions: error preparing transactions: {e}"
)
try:
if "w!ll3x3c" in e.heirname:
Willexecutors.is_selected(
e.heirname[len("w!ll3x3c") :], False
)
break
except Exception:
raise e
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
if not redo:
break
if i >= 10:
if balance - total_in > wallet.dust_threshold():
redo = True
if not redo:
break
if i >= 10:
break
else:
_logger.info(
f"no locktimes for willexecutor {willexecutor} skipped"
)
break
alltxs.update(txs)
@@ -632,7 +709,7 @@ class Heirs(dict, Logger):
return None
def validate_address(address):
if not bitcoin.is_address(address):
if not bitcoin.is_address(address, net=constants.net):
raise NotAnAddress(f"not an address,{address}")
return address
@@ -661,12 +738,14 @@ class Heirs(dict, Logger):
return (address, amount, locktime)
def _validate(data, timestamp_to_check=False):
for k, v in list(data.items()):
if k == "heirs":
return Heirs._validate(v)
return Heirs._validate(v, timestamp_to_check)
try:
Heirs.validate_heir(k, v)
Heirs.validate_heir(k, v, timestamp_to_check)
except Exception as e:
_logger.info(f"exception heir removed {e}")
data.pop(k)
return data
@@ -685,3 +764,21 @@ class LocktimeNotValid(ValueError):
class HeirExpiredException(LocktimeNotValid):
pass
class HeirAmountIsDustException(Exception):
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",
"fullname": "Bitcoin After Life",
"description": "Provides free and decentralized inheritance support<br> Version: 0.2.4",
"description": "Provides free and decentralized inheritance support<br> Version: 0.2.6",
"author":"Svatantrya",
"available_for": ["qt"],
"icon":"icons/bal32x32.png"

360
qt.py
View File

@@ -31,10 +31,8 @@ if QT_VERSION == 5:
)
from PyQt5.QtGui import (
QColor,
QIcon,
QPainter,
QPalette,
QPixmap,
QStandardItem,
QStandardItemModel,
)
@@ -68,10 +66,8 @@ else: # QT6
)
from PyQt6.QtGui import (
QColor,
QIcon,
QPainter,
QPalette,
QPixmap,
QStandardItem,
QStandardItemModel,
)
@@ -124,7 +120,6 @@ from electrum.gui.qt.util import (
WindowModalDialog,
char_width_in_lineedit,
import_meta_gui,
read_QIcon,
read_QIcon_from_bytes,
read_QPixmap_from_bytes,
)
@@ -144,8 +139,7 @@ from electrum.util import (
)
from .bal import BalPlugin
from .bal_resources import DEFAULT_ICON, icon_path
from .heirs import Heirs
from .heirs import Heirs, HEIR_REAL_AMOUNT, HEIR_DUST_AMOUNT
from .util import Util
from .will import (
AmountException,
@@ -197,8 +191,10 @@ class Plugin(BalPlugin, Logger):
w.init_menubar_tools(menu_child)
except Exception as e:
self.logger.error(
("init_qt except:", menu_child.text())
)
raise e
self.logger.error(("except:", menu_child.text()))
except Exception as e:
self.logger.error("Error loading plugini {}".format(e))
@@ -295,9 +291,9 @@ class Plugin(BalPlugin, Logger):
lbl_logo = QLabel()
lbl_logo.setPixmap(qicon)
#heir_ping_willexecutors = bal_checkbox(self.PING_WILLEXECUTORS)
#heir_ask_ping_willexecutors = bal_checkbox(self.ASK_PING_WILLEXECUTORS)
heir_no_willexecutor = bal_checkbox(self.NO_WILLEXECUTOR)
# heir_ping_willexecutors = bal_checkbox(self.PING_WILLEXECUTORS)
# heir_ask_ping_willexecutors = bal_checkbox(self.ASK_PING_WILLEXECUTORS)
# heir_no_willexecutor = bal_checkbox(self.NO_WILLEXECUTOR)
def on_multiverse_change():
self.update_all()
@@ -327,27 +323,27 @@ class Plugin(BalPlugin, Logger):
2,
"Hide invalidated transactions from will detail and list",
)
#add_widget(
# add_widget(
# grid,
# "Ping Willexecutors",
# heir_ping_willexecutors,
# 3,
# "Ping willexecutors to get payment info before compiling will",
#)
#add_widget(
# )
# add_widget(
# grid,
# " - Ask before",
# heir_ask_ping_willexecutors,
# 4,
# "Ask before to ping willexecutor",
#)
#add_widget(
# )
# add_widget(
# grid,
# "Backup Transaction",
# heir_no_willexecutor,
# 5,
# "Add transactions without willexecutor",
#)
# )
# add_widget(grid,"Enable Multiverse(EXPERIMENTAL/BROKEN)",heir_enable_multiverse,6,"enable multiple locktimes, will import.... ")
grid.addWidget(heir_repush, 7, 0)
grid.addWidget(
@@ -473,7 +469,7 @@ class BalWindow(Logger):
self.bal_plugin, update=False, bal_window=self
)
if not self.heirs:
self.heirs = Heirs._validate(Heirs(self.wallet.db))
self.heirs = Heirs._validate(Heirs(self.wallet))
if not self.will:
self.will = self.wallet.db.get_dict("will")
Util.fix_will_tx_fees(self.will)
@@ -481,7 +477,7 @@ class BalWindow(Logger):
self.willitems = {}
try:
self.load_willitems()
except:
except Exception:
self.disable_plugin = True
self.show_warning(
_("Please restart Electrum to activate the BAL plugin"),
@@ -500,6 +496,7 @@ class BalWindow(Logger):
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()
def init_wizard(self):
wizard_dialog = BalWizardDialog(self)
@@ -510,19 +507,18 @@ class BalWindow(Logger):
self.willexecutor_dialog.show()
def create_heirs_tab(self):
self.heir_list = l = HeirList(self, self.window)
self.heir_list = HeirList(self, self.window)
tab = self.window.create_list_tab(l)
tab = self.window.create_list_tab(self.heir_list)
tab.is_shown_cv = shown_cv(False)
return tab
def create_will_tab(self):
self.will_list = l = PreviewList(self, self.window, None)
tab = self.window.create_list_tab(l)
self.will_list = PreviewList(self, self.window, None)
tab = self.window.create_list_tab(self.will_list)
tab.is_shown_cv = shown_cv(True)
return tab
def new_heir_dialog(self, heir_key=None):
heir = self.heirs.get(heir_key)
title = "New heir"
@@ -553,7 +549,7 @@ class BalWindow(Logger):
if heir:
self.heir_locktime.set_locktime(heir[2])
heir_is_xpub = QCheckBox()
# heir_is_xpub = QCheckBox()
new_heir_button = QPushButton(_("Add another heir"))
self.add_another_heir = False
@@ -625,7 +621,11 @@ class BalWindow(Logger):
def delete_heirs(self, heirs):
for heir in heirs:
del self.heirs[heir]
try:
del self.heirs[heir]
except Exception as e:
_logger.debug(f"error deleting heir: {heir} {e}")
pass
self.heirs.save()
self.heir_list.update()
return True
@@ -655,9 +655,10 @@ class BalWindow(Logger):
Will.normalize_will(self.willitems, self.wallet)
def build_will(self, ignore_duplicate=True, keep_original=True):
_logger.debug("building will...")
will = {}
willtodelete = []
willtoappend = {}
# willtodelete = []
# willtoappend = {}
try:
self.init_class_variables()
self.willexecutors = Willexecutors.get_willexecutors(
@@ -670,6 +671,7 @@ class BalWindow(Logger):
if Willexecutors.is_selected(w):
f = True
if not f:
_logger.error("No Will-Executor or backup transaction selected")
raise NoWillExecutorNotPresent(
"No Will-Executor or backup transaction selected"
)
@@ -680,11 +682,12 @@ class BalWindow(Logger):
None,
self.date_to_check,
)
self.logger.info(txs)
self.logger.info(f"txs built: {txs}")
creation_time = time.time()
if txs:
for txid in txs:
txtodelete = []
# txtodelete = []
_break = False
tx = {}
tx["tx"] = txs[txid]
@@ -699,7 +702,14 @@ class BalWindow(Logger):
tx["txchildren"] = []
will[txid] = WillItem(tx, _id=txid, wallet=self.wallet)
self.update_will(will)
else:
self.logger.info("No transactions was built")
self.logger.info(f"will-settings: {self.will_settings}")
self.logger.info(f"date_to_check:{self.date_to_check}")
self.logger.info(f"heirs: {self.heirs}")
return {}
except Exception as e:
self.logger.info(f"Exception build_will: {e}")
raise e
pass
return self.willitems
@@ -744,7 +754,7 @@ class BalWindow(Logger):
self.date_to_check = Util.parse_locktime_string(
self.will_settings["threshold"]
)
found = False
# 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
@@ -755,7 +765,7 @@ class BalWindow(Logger):
self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get())
except Exception as e:
self.logger.error(e)
self.logger.error(f"init_class_variables: {e}")
raise e
def build_inheritance_transaction(self, ignore_duplicate=True, keep_original=True):
@@ -878,7 +888,7 @@ class BalWindow(Logger):
def show_transaction(self, tx=None, txid=None, parent=None):
if not parent:
parent = self.window
if txid != None and txid in self.willitems:
if txid is not None and txid in self.willitems:
tx = self.willitems[txid].tx
if not tx:
raise Exception(_("no tx"))
@@ -893,7 +903,7 @@ class BalWindow(Logger):
)
)
self.wallet.set_label(result.txid(), "BAL Invalidate")
a = self.show_transaction(result)
self.show_transaction(result)
else:
self.show_message(_("No transactions to invalidate"))
@@ -929,7 +939,7 @@ class BalWindow(Logger):
tosign = txid
try:
self.waiting_dialog.update(get_message())
except:
except Exception:
pass
for txin in tx.inputs():
prevout = txin.prevout.to_json()
@@ -938,7 +948,7 @@ class BalWindow(Logger):
txin._trusted_value_sats = change.value
try:
txin.script_descriptor = change.script_descriptor
except:
except Exception:
pass
txin.is_mine = True
txin._TxInput__address = change.address
@@ -947,9 +957,9 @@ class BalWindow(Logger):
self.wallet.sign_transaction(tx, password, ignore_warnings=True)
signed = tosign
is_complete = False
# is_complete = False
if tx.is_complete():
is_complete = True
# is_complete = True
wi.set_status("COMPLETE", True)
txs[txid] = tx
except Exception:
@@ -1026,7 +1036,7 @@ class BalWindow(Logger):
)
def on_failure(err):
self.logger.error(err)
self.logger.error(f"fail to broadcast transactions:{err}")
task = partial(self.push_transactions_to_willexecutors, force)
msg = _("Selecting Will-Executors")
@@ -1036,7 +1046,7 @@ class BalWindow(Logger):
self.waiting_dialog.exe()
def push_transactions_to_willexecutors(self, force=False):
willexecutors = Willexecutors.get_willexecutor_transactions(self.willitems)
willexecutors = Willexecutors.get_willexecutor_transactions(self.bal_plugin, self.willitems)
def getMsg(willexecutors):
msg = "Broadcasting Transactions to Will-Executors:\n"
@@ -1050,7 +1060,7 @@ class BalWindow(Logger):
self.waiting_dialog.update(getMsg(willexecutors))
if "txs" in willexecutor:
try:
if Willexecutors.push_transactions_to_willexecutor(
if Willexecutors.push_transactions_to_willexecutor(self.bal_plugin,
willexecutors[url]
):
for wid in willexecutors[url]["txsids"]:
@@ -1069,7 +1079,14 @@ 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(
self.bal_plugin,
w._id,
w.we["url"]
)
)
self.waiting_dialog.update(
"checked {} - {} : {}".format(
self.willitems[wid].we["url"],
@@ -1118,7 +1135,8 @@ 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(self.bal_plugin,w._id, w.we["url"]))
if time.time() - start < 3:
time.sleep(3 - (time.time() - start))
@@ -1166,7 +1184,7 @@ class BalWindow(Logger):
self.waiting_dialog.update(get_title())
except Exception:
pass
wes[url] = Willexecutors.get_info_task(url, we)
wes[url] = Willexecutors.get_info_task(self.bal_plugin, url, we)
if wes[url]["status"] == "KO":
failed.append(url)
else:
@@ -1177,15 +1195,18 @@ class BalWindow(Logger):
parent = self
def on_success(result):
#del self.waiting_dialog
# del self.waiting_dialog
try:
parent.willexecutor_list.update()
except Exception as e:
_logger.error(f"error updating willexecutors {e}")
pass
try:
parent.willexecutors_list.update()
except Exception as e:
pass
def on_failure(e):
self.logger.error(e)
self.logger.error(f"fail to ping willexecutors: {e}")
pass
self.logger.info("ping willexecutors")
@@ -1679,7 +1700,7 @@ class BalWizardHeirsWidget(BalWizardWidget):
)
def get_content(self):
self.heirs_list = HeirList(self.bal_window, self.parent)
self.heirs_list = HeirList(self.bal_window, self)
button_add = QPushButton(_("Add"))
button_add.clicked.connect(self.add_heir)
button_import = QPushButton(_("Import"))
@@ -1748,7 +1769,7 @@ class BalWizardWEDownloadWidget(BalWizardWidget):
import_meta_gui(
self.bal_window.window,
_("willexecutors.json"),
_("willexecutors"),
self.import_json_file,
doNothing,
)
@@ -1757,7 +1778,9 @@ class BalWizardWEDownloadWidget(BalWizardWidget):
def on_success(willexecutors):
self.bal_window.willexecutors.update(willexecutors)
self.bal_window.ping_willexecutors(self.bal_window.willexecutors,False)
self.bal_window.ping_willexecutors(
self.bal_window.willexecutors, False
)
if index < 1:
for we in self.bal_window.willexecutors:
if self.bal_window.willexecutors[we]["status"] == 200:
@@ -1770,7 +1793,7 @@ class BalWizardWEDownloadWidget(BalWizardWidget):
_logger.debug(f"Failed to download willexecutors list {fail}")
pass
task = partial(Willexecutors.download_list, self.bal_window.bal_plugin)
task = partial(Willexecutors.download_list, self.bal_window.bal_plugin,self.bal_window.willexecutors)
msg = _("Downloading Will-Executors list")
self.waiting_dialog = BalWaitingDialog(
self.bal_window, msg, task, on_success, on_failure, exe=False
@@ -1828,7 +1851,6 @@ class BalWizardLocktimeAndFeeWidget(BalWizardWidget):
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)
@@ -1846,7 +1868,6 @@ class BalWizardLocktimeAndFeeWidget(BalWizardWidget):
)
self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings)
self.heir_threshold.valueEdited.connect(on_heir_threshold)
self.heir_tx_fees = QSpinBox(widget)
@@ -2024,15 +2045,19 @@ class bal_checkbox(QCheckBox):
class BalBuildWillDialog(BalDialog):
updatemessage = pyqtSignal()
COLOR_WARNING = "#cfa808"
COLOR_ERROR = "#ff0000"
COLOR_OK = "#05ad05"
def __init__(self, bal_window, parent=None):
if not parent:
parent = bal_window.window
BalDialog.__init__(self, parent, bal_window.bal_plugin, "Building Will")
self.parent=parent
BalDialog.__init__(self, parent, bal_window.bal_plugin, _("Building Will"))
self.parent = parent
self.updatemessage.connect(self.update)
self.bal_window = bal_window
self.message_label = QLabel("Building Will:")
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()
@@ -2080,25 +2105,24 @@ class BalBuildWillDialog(BalDialog):
self.bal_window.window.wallet.dust_threshold(),
)
_logger.debug("variables ok")
self.msg_set_status("checking variables:", varrow,"Ok")
self.msg_set_status("checking variables:", varrow, "Ok", self.COLOR_OK)
except AmountException:
self.msg_set_status(
"checking variables",
varrow,
'<font color="#ff0000">'
+ _(
_(
"In the inheritance process, "
+ "the entire wallet will always be fully emptied. \n"
+ "Your settings require an adjustment of the amounts"
)
+ "</font>",
),
self.COLOR_WARNING,
)
self.msg_set_checking()
have_to_build = False
try:
self.bal_window.check_will()
self.msg_set_checking("Ok")
self.msg_set_checking(self.msg_ok())
except WillExpiredException:
_logger.debug("expired")
self.msg_set_checking("Expired")
@@ -2110,21 +2134,21 @@ class BalBuildWillDialog(BalDialog):
_logger.debug("no heirs")
self.msg_set_checking("No Heirs")
except NotCompleteWillException as e:
_logger.debug("not complete", e)
_logger.debug(f"not complete {e} true")
message = False
have_to_build = True
if isinstance(e, HeirChangeException):
message = "Heirs changed:"
message = _("Heirs changed:")
elif isinstance(e, WillExecutorNotPresent):
message = "Will-Executor not present"
message = _("Will-Executor not present")
elif isinstance(e, WillexecutorChangeException):
message = "Will-Executor changed"
message = _("Will-Executor changed")
elif isinstance(e, TxFeesChangedException):
message = "Txfees are changed"
message = _("Txfees are changed")
elif isinstance(e, HeirNotFoundException):
message = "Heir not found"
message = _("Heir not found")
if message:
_logger.debug("message")
_logger.debug(f"message: {message}")
self.msg_set_checking(message)
else:
self.msg_set_checking("New")
@@ -2132,14 +2156,39 @@ class BalBuildWillDialog(BalDialog):
if have_to_build:
self.msg_set_building()
try:
self.bal_window.build_will()
if not 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()
for wid in Will.only_valid(self.bal_window.willitems):
self.bal_window.wallet.set_label(wid, "BAL Transaction")
self.msg_set_building("Ok")
self.msg_set_building(self.msg_ok())
except WillExecutorNotPresent:
self.msg_set_status(
_("Will-Executor excluded"), None, _("Skipped"), self.COLOR_ERROR
)
except Exception as e:
self.msg_set_building(self.msg_error(e))
return False, None
# excluded_heirs = []
for wid in Will.only_valid(self.bal_window.willitems):
heirs = self.bal_window.willitems[wid].heirs
for hid, heir in heirs.items():
if "DUST" in str(heir[HEIR_REAL_AMOUNT]):
self.msg_set_status(
f"{hid},{heir[HEIR_DUST_AMOUNT]} is DUST",
None,
f"Excluded from will {wid}",
self.COLOR_WARNING,
)
have_to_sign = False
for wid in Will.only_valid(self.bal_window.willitems):
if not self.bal_window.willitems[wid].get_status("COMPLETE"):
@@ -2161,7 +2210,7 @@ class BalBuildWillDialog(BalDialog):
for i in range(secs, 0, -1):
if self._stopping:
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)
self.msg_del_row(wait_row)
@@ -2173,19 +2222,19 @@ class BalBuildWillDialog(BalDialog):
txid = self.network.run_from_another_thread(
self.network.broadcast_transaction(tx, timeout=120), timeout=120
)
self.msg_set_invalidating("Ok")
self.msg_set_invalidating(self.msg_ok())
if not txid:
_logger.debug(f"should not be none txid: {txid}")
except TxBroadcastError as e:
_logger.error(e)
_logger.error(f"fail to broadcast transaction:{e}")
msg = e.get_message_for_gui()
self.msg_set_invalidating(self.msg_error(msg))
except BestEffortRequestFailed as e:
self.msg_set_invalidating(self.msg_error(e))
def loop_push(self):
self.msg_set_pushing("Broadcasting")
self.msg_set_pushing(_("Broadcasting"))
retry = False
try:
willexecutors = Willexecutors.get_willexecutor_transactions(
@@ -2197,7 +2246,7 @@ class BalBuildWillDialog(BalDialog):
self.bal_window.willexecutors.get(url)
):
_logger.debug(f"{url}: {willexecutor}")
if not Willexecutors.push_transactions_to_willexecutor(
if not Willexecutors.push_transactions_to_willexecutor(self.bal_plugin,
willexecutor
):
for wid in willexecutor["txsids"]:
@@ -2217,7 +2266,9 @@ class BalBuildWillDialog(BalDialog):
self.bal_window.willitems[wid].we["url"], wid, "Waiting"
)
)
self.bal_window.willitems[wid].check_willexecutor()
self.bal_plugin = bal_window.bal_plugin
w = self.bal_window.willitems[wid]
w.set_check_willexecutor(Willexecutors.check_transaction(self.bal_plugin,w._id, w.we["url"]))
row = self.msg_edit_row(
"checked {} - {} : {}".format(
self.bal_window.willitems[wid].we["url"],
@@ -2229,7 +2280,7 @@ class BalBuildWillDialog(BalDialog):
except Exception as e:
_logger.error(e)
_logger.error(f"loop push error:{e}")
raise e
if retry:
raise Exception("retry")
@@ -2240,9 +2291,9 @@ class BalBuildWillDialog(BalDialog):
if not self._stopping:
self.loop_push()
def invalidate_task(self,password,bal_window,tx):
def invalidate_task(self, password, bal_window, 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)
try:
if tx:
@@ -2269,6 +2320,7 @@ class BalBuildWillDialog(BalDialog):
def on_error(self, error):
_logger.error(error)
pass
def on_success_phase1(self, result):
self.have_to_sign, tx = list(result)
_logger.debug("have to sign {}".format(self.have_to_sign))
@@ -2279,15 +2331,15 @@ class BalBuildWillDialog(BalDialog):
# need to sign invalidate and restart phase 1
password = self.bal_window.get_wallet_password(
"Invalidate your old will", parent=self
_("Invalidate your old will"), parent=self
)
if password is False:
self.msg_set_invalidating("Aborted")
self.msg_set_invalidating(_("Aborted"))
self.wait(3)
self.close()
return
self.thread.add(
partial(self.invalidate_task,password,self.bal_window,tx),
partial(self.invalidate_task, password, self.bal_window, tx),
on_success=self.on_success_invalidate,
on_done=self.on_accept,
on_error=self.on_error,
@@ -2297,12 +2349,12 @@ class BalBuildWillDialog(BalDialog):
elif self.have_to_sign:
password = self.bal_window.get_wallet_password(
"Sign your will", parent=self
_("Sign your will"), parent=self
)
if password is False:
self.msg_set_signing("Aborted")
self.msg_set_signing(_("Aborted"))
else:
self.msg_set_signing("Nothing to do")
self.msg_set_signing(_("Nothing to do"))
self.thread.add(
partial(self.task_phase2, password),
on_success=self.on_success_phase2,
@@ -2314,7 +2366,7 @@ class BalBuildWillDialog(BalDialog):
def on_success_phase2(self, arg=False):
self.thread.stop()
self.bal_window.save_willitems()
self.msg_edit_row("Finished")
self.msg_edit_row(_("Finished"))
self.close()
def closeEvent(self, event):
@@ -2329,7 +2381,7 @@ class BalBuildWillDialog(BalDialog):
for txid, tx in txs.items():
self.bal_window.willitems[txid].tx = copy.deepcopy(tx)
self.bal_window.save_willitems()
self.msg_set_signing("Ok")
self.msg_set_signing(self.msg_ok())
except Exception as e:
self.msg_set_signing(self.msg_error(e))
@@ -2340,31 +2392,31 @@ class BalBuildWillDialog(BalDialog):
if w.we and w.get_status("COMPLETE") and not w.get_status("PUSHED"):
have_to_push = True
if not have_to_push:
self.msg_set_pushing("Nothing to do")
self.msg_set_pushing(_("Nothing to do"))
else:
try:
self.loop_push()
self.msg_set_pushing("Ok")
self.msg_set_pushing(self.msg_ok())
except Exception as e:
self.msg_set_pushing(self.msg_error(e))
self.msg_edit_row("Ok")
self.msg_edit_row(self.msg_ok())
self.wait(5)
def on_error_phase1(self, error):
_logger.error(f"error phase1: {error}")
def on_error_phase2(self, error):
_logger.error("error phase2: { error}")
_logger.error(f"error phase2: { error}")
def msg_set_checking(self, status="Waiting", row=None):
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):
row = self.inval_row if row is None else row
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):
@@ -2388,19 +2440,23 @@ class BalBuildWillDialog(BalDialog):
self.wait_row = self.msg_edit_row(f"Please wait {status}secs", self.wait_row)
def msg_error(self, e):
return "Error: {}".format(e)
return "<font color='{}'>{}</font>".format(self.COLOR_ERROR, e)
def msg_set_status(self, msg, row=None, status=None):
def msg_ok(self, e="Ok"):
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
line = "{}:\t{}".format(_(msg), status)
line = "<font color={}>{}:\t{}</font>".format(color, _(msg), status)
return self.msg_edit_row(line, row)
def ask_password(self, msg=None):
self.password = self.bal_window.get_wallet_password(msg, parent=self)
def msg_edit_row(self, line, row=None):
_logger.debug(f"{row},{line}")
try:
self.labels[row] = line
except Exception:
@@ -2464,6 +2520,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)
@@ -2513,6 +2570,10 @@ class HeirList(MyTreeView, MessageBoxMixin):
except Exception:
self.update()
def delete_heirs(self, selected_keys):
self.bal_window.delete_heirs(selected_keys)
self.update()
def create_menu(self, position):
menu = QMenu()
idx = self.indexAt(position)
@@ -2540,20 +2601,10 @@ class HeirList(MyTreeView, MessageBoxMixin):
_("Edit {}").format(column_title),
lambda p=persistent: self.edit(QModelIndex(p)),
)
menu.addAction(
_("Delete"), lambda: self.bal_window.delete_heirs(selected_keys)
)
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):
if self.maybe_defer_update():
return
current_key = self.get_role_data_for_current_item(
col=self.Columns.NAME, role=self.ROLE_HEIR_KEY
)
@@ -2573,9 +2624,15 @@ class HeirList(MyTreeView, MessageBoxMixin):
items[self.Columns.NAME].setEditable(True)
items[self.Columns.ADDRESS].setEditable(True)
items[self.Columns.AMOUNT].setEditable(True)
items[self.Columns.NAME].setData(key, self.ROLE_HEIR_KEY + self.Columns.NAME)
items[self.Columns.ADDRESS].setData(key, self.ROLE_HEIR_KEY + self.Columns.ADDRESS)
items[self.Columns.AMOUNT].setData(key, self.ROLE_HEIR_KEY + self.Columns.AMOUNT)
items[self.Columns.NAME].setData(
key, self.ROLE_HEIR_KEY + self.Columns.NAME
)
items[self.Columns.ADDRESS].setData(
key, self.ROLE_HEIR_KEY + self.Columns.ADDRESS
)
items[self.Columns.AMOUNT].setData(
key, self.ROLE_HEIR_KEY + self.Columns.AMOUNT
)
row_count = self.model().rowCount()
self.model().insertRow(row_count, items)
@@ -2594,10 +2651,9 @@ class HeirList(MyTreeView, MessageBoxMixin):
pass
def get_edit_key_from_coordinate(self, row, col):
a= self.get_role_data_from_coordinate(
row, col, role=self.ROLE_HEIR_KEY + col
)
a = self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY + col)
return a
def create_toolbar(self, config):
toolbar, menu = self.create_toolbar_with_menu("")
menu.addAction(_("&New Heir"), self.bal_window.new_heir_dialog)
@@ -2605,6 +2661,7 @@ class HeirList(MyTreeView, MessageBoxMixin):
menu.addAction(_("Export"), lambda: self.bal_window.export_heirs())
self.heir_locktime = HeirsLockTimeEdit(self, 0)
def on_heir_locktime():
if not self.heir_locktime.get_locktime():
self.heir_locktime.set_locktime("1y")
@@ -2695,8 +2752,8 @@ class HeirList(MyTreeView, MessageBoxMixin):
self.heir_threshold.set_locktime(self.bal_window.will_settings["threshold"])
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):
@@ -2918,7 +2975,7 @@ class PreviewList(MyTreeView):
display = QPushButton(_("Display"))
display.clicked.connect(self.bal_window.preview_modal_dialog)
display = QPushButton(_("refresh"))
display = QPushButton(_("Refresh"))
display.clicked.connect(self.check)
widget = QWidget()
@@ -2963,16 +3020,22 @@ class PreviewList(MyTreeView):
def check(self):
Will.add_willtree(self.bal_window.willitems)
all_utxos =self.bal_window.wallet.get_utxos()
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)
Will.check_invalidated(
self.bal_window.willitems, utxos_list, self.bal_window.wallet
)
close_window = BalBuildWillDialog(self.bal_window)
close_window.build_will_task()
will = {}
for wid, w in self.bal_window.willitems.items():
if w.get_status("VALID") and w.get_status("PUSHED") and not w.get_status("CHECKED"):
if (
w.get_status("VALID")
and w.get_status("PUSHED")
and not w.get_status("CHECKED")
):
will[wid] = w
if will:
self.bal_window.check_transactions(will)
@@ -3290,7 +3353,7 @@ class WillExecutorList(MyTreeView):
self.Columns.INFO,
],
)
self.parent=parent
self.parent = parent
try:
self.setModel(QStandardItemModel(self))
self.sortByColumn(self.Columns.SELECTED, Qt.SortOrder.AscendingOrder)
@@ -3356,7 +3419,7 @@ class WillExecutorList(MyTreeView):
self.update()
def get_edit_key_from_coordinate(self, row, col):
role=self.ROLE_HEIR_KEY+col
role = self.ROLE_HEIR_KEY + col
a = self.get_role_data_from_coordinate(row, col, role=role)
return a
@@ -3399,7 +3462,6 @@ class WillExecutorList(MyTreeView):
pass
def update(self):
if self.parent.willexecutors_list is None:
return
try:
@@ -3416,16 +3478,33 @@ class WillExecutorList(MyTreeView):
labels[self.Columns.URL] = url
if Willexecutors.is_selected(value):
labels[self.Columns.SELECTED] = [read_QIcon_from_bytes(self.parent.bal_plugin.read_file("icons/confirmed.png")),""]
labels[self.Columns.SELECTED] = [
read_QIcon_from_bytes(
self.parent.bal_plugin.read_file("icons/confirmed.png")
),
"",
]
else:
labels[self.Columns.SELECTED] = ""
labels[self.Columns.BASE_FEE] = Util.decode_amount(
value.get("base_fee", 0), self.get_decimal_point()
)
if str(value.get("status", 0)) == "200":
labels[self.Columns.STATUS] = [read_QIcon_from_bytes(self.parent.bal_plugin.read_file("icons/status_connected.png")),""]
labels[self.Columns.STATUS] = [
read_QIcon_from_bytes(
self.parent.bal_plugin.read_file(
"icons/status_connected.png"
)
),
"",
]
else:
labels[self.Columns.STATUS] = [read_QIcon_from_bytes(self.parent.bal_plugin.read_file("icons/unconfirmed.png")),""]
labels[self.Columns.STATUS] = [
read_QIcon_from_bytes(
self.parent.bal_plugin.read_file("icons/unconfirmed.png")
),
"",
]
labels[self.Columns.ADDRESS] = str(value.get("address", ""))
labels[self.Columns.INFO] = str(value.get("info", ""))
@@ -3445,10 +3524,18 @@ class WillExecutorList(MyTreeView):
items[self.Columns.BASE_FEE].setEditable(True)
items[self.Columns.STATUS].setEditable(False)
items[self.Columns.URL].setData(url, self.ROLE_HEIR_KEY + self.Columns.URL)
items[self.Columns.BASE_FEE].setData(url, self.ROLE_HEIR_KEY + self.Columns.BASE_FEE)
items[self.Columns.INFO].setData(url, self.ROLE_HEIR_KEY + self.Columns.INFO)
items[self.Columns.ADDRESS].setData(url, self.ROLE_HEIR_KEY + self.Columns.ADDRESS)
items[self.Columns.URL].setData(
url, self.ROLE_HEIR_KEY + self.Columns.URL
)
items[self.Columns.BASE_FEE].setData(
url, self.ROLE_HEIR_KEY + self.Columns.BASE_FEE
)
items[self.Columns.INFO].setData(
url, self.ROLE_HEIR_KEY + self.Columns.INFO
)
items[self.Columns.ADDRESS].setData(
url, self.ROLE_HEIR_KEY + self.Columns.ADDRESS
)
row_count = self.model().rowCount()
self.model().insertRow(row_count, items)
if url == current_key:
@@ -3459,7 +3546,8 @@ class WillExecutorList(MyTreeView):
self.parent.save_willexecutors()
except Exception as e:
_logger.error(e)
_logger.error(f"error updating willexcutor {e}")
raise e
class WillExecutorWidget(QWidget, MessageBoxMixin):
@@ -3516,7 +3604,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
buttonbox.addWidget(b)
vbox.addLayout(buttonbox)
#self.willexecutor_list.update()
# self.willexecutor_list.update()
def add(self):
self.willexecutors_list["http://localhost:8080"] = {
@@ -3527,7 +3615,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
self.willexecutor_list.update()
def download_list(self):
self.willexecutors_list.update(Willexecutors.download_list(self.bal_plugin))
self.willexecutors_list.update(Willexecutors.download_list(self.bal_plugin,self.bal_window.willexecutors))
self.willexecutor_list.update()
def export_file(self, path):
@@ -3541,14 +3629,14 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
def import_file(self):
import_meta_gui(
self.bal_window.window,
_("willexecutors.json"),
_("willexecutors"),
self.import_json_file,
self.willexecutors_list.update,
)
def update_willexecutors(self, wes=None):
if not wes:
wes=self.willexecutors_list
wes = self.willexecutors_list
self.bal_window.ping_willexecutors(wes, self.parent)
self.willexecutors_list.update(wes)
self.willexecutor_list.update()

88
util.py
View File

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

View File

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

View File

@@ -15,10 +15,8 @@ from PyQt6.QtWidgets import (
QGroupBox,
QTextEdit,
)
from PyQt6.QtCore import Qt
from electrum.storage import WalletStorage
from electrum.util import MyEncoder
from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, read_wallet, save
from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, save
class WalletUtilityGUI(QMainWindow):
@@ -190,14 +188,6 @@ class WalletUtilityGUI(QMainWindow):
def main():
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.show()

155
will.py
View File

@@ -12,11 +12,7 @@ from electrum.transaction import (
tx_from_any,
)
from electrum.util import (
FileImportFailed,
bfh,
decimal_point_to_base_unit_name,
read_json_file,
write_json_file,
)
from .util import Util
@@ -28,7 +24,6 @@ _logger = get_logger(__name__)
class Will:
# return an array with the list of children
def get_children(will, willid):
out = []
for _id in will:
@@ -86,7 +81,7 @@ class Will:
txin._trusted_value_sats = change.value
try:
txin.script_descriptor = change.script_descriptor
except:
except Exception:
pass
txin.is_mine = True
txin._TxInput__address = change.address
@@ -133,8 +128,8 @@ class Will:
to_delete.append(wid)
to_add[ow.tx.txid()] = ow.to_dict()
for eid, err in errors.items():
new_txid = err.tx.txid()
# for eid, err in errors.items():
# new_txid = err.tx.txid()
for k, w in to_add.items():
will[k] = w
@@ -153,6 +148,16 @@ 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):
@@ -192,7 +197,7 @@ class Will:
outputs = w.tx.outputs()
found = False
old_txid = w.tx.txid()
ntx = None
# ntx = None
for i in range(0, len(inputs)):
if (
inputs[i].prevout.txid.hex() == otxid
@@ -203,7 +208,7 @@ class Will:
will[wid].tx.set_rbf(True)
will[wid].tx._inputs[i] = Will.new_input(wid, idx, change)
found = True
if found == True:
if found:
pass
new_txid = will[wid].tx.txid()
@@ -230,7 +235,7 @@ class Will:
for i in inputs:
prevout_str = i.prevout.to_str()
inp = [w, will[w], i]
if not prevout_str in all_inputs:
if prevout_str not in all_inputs:
all_inputs[prevout_str] = [inp]
else:
all_inputs[prevout_str].append(inp)
@@ -243,7 +248,7 @@ class Will:
min_locktime = min(values, key=lambda x: x[1].tx.locktime)[1].tx.locktime
for w in values:
if w[1].tx.locktime == min_locktime:
if not i in all_inputs_min_locktime:
if i not in all_inputs_min_locktime:
all_inputs_min_locktime[i] = [w]
else:
all_inputs_min_locktime[i].append(w)
@@ -256,7 +261,7 @@ class Will:
to_append = {}
new_inputs = Will.get_all_inputs(will, only_valid=True)
for nid, nwi in will.items():
if nwi.search_anticipate(new_inputs) or nwi.search_anticipate(old_inputs):
if nwi.search_anticipate(new_inputs):
if nid != nwi.tx.txid():
redo = True
to_delete.append(nid)
@@ -266,25 +271,36 @@ class Will:
Will.change_input(
will, nid, i, outputs[i], new_inputs, to_delete, to_append
)
if nwi.search_anticipate(old_inputs):
if nid != nwi.tx.txid():
redo = True
to_delete.append(nid)
to_append[nwi.tx.txid()] = nwi
outputs = nwi.tx.outputs()
for i in range(0, len(outputs)):
Will.change_input(
will, nid, i, outputs[i], new_inputs, to_delete, to_append
)
for w in to_delete:
try:
del will[w]
except:
except Exception:
pass
for k, w in to_append.items():
will[k] = w
if redo:
Will.search_anticipate_rec(will, old_inputs)
def update_will(old_will, new_will):
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_new_inputs = Will.get_all_inputs(new_will)
# all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
# all_new_inputs = Will.get_all_inputs(new_will)
# check if the new input is already spent by other transaction
# if it is use the same locktime, or anticipate.
Will.search_anticipate_rec(new_will, all_old_inputs)
other_inputs = Will.get_all_inputs(old_will, {})
try:
Will.normalize_will(new_will, others_inputs=other_inputs)
@@ -325,9 +341,9 @@ class Will:
prevout_to_spend = []
for prevout_str, ws in inputs.items():
for w in ws:
if not w[0] in filtered_inputs:
if w[0] not in filtered_inputs:
filtered_inputs.append(w[0])
if not prevout_str in prevout_to_spend:
if prevout_str not in prevout_to_spend:
prevout_to_spend.append(prevout_str)
balance = 0
utxo_to_spend = []
@@ -371,7 +387,7 @@ class Will:
return True
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():
inutxo = Util.in_utxo(inp, all_utxos)
for w in ws:
@@ -409,7 +425,7 @@ class Will:
def set_invalidate(wid, will=[]):
will[wid].set_status("INVALIDATED", True)
if will[wid].children:
for c in self.children.items():
for c in will[wid].children.items():
Will.set_invalidate(c[0], will)
def check_tx_height(tx, wallet):
@@ -419,36 +435,38 @@ class Will:
# check if transactions are stil valid tecnically valid
def check_invalidated(willtree, utxos_list, wallet):
for wid, w in willtree.items():
#if not w.father:
for inp in w.tx.inputs():
inp_str = Util.utxo_to_str(inp)
if not inp_str in utxos_list:
if wallet:
height = Will.check_tx_height(w.tx, wallet)
if height < 0:
Will.set_invalidate(wid, willtree)
elif height == 0:
w.set_status("PENDING", True)
else:
w.set_status("CONFIRMED", True)
#else:
# print("father",w.father)
if (
not w.father
or willtree[w.father].get_status("CONFIRMED")
or willtree[w.father].get_status("PENDING")
):
for inp in w.tx.inputs():
inp_str = Util.utxo_to_str(inp)
if inp_str not in utxos_list:
if wallet:
height = Will.check_tx_height(w.tx, wallet)
if height < 0:
Will.set_invalidate(wid, willtree)
elif height == 0:
w.set_status("PENDING", True)
else:
w.set_status("CONFIRMED", True)
def reflect_to_children(treeitem):
if not treeitem.get_status("VALID"):
_logger.debug(f"{tree:item._id} status not valid looking for children")
for child in treeitem.children:
wc = willtree[child]
if wc.get_status("VALID"):
if treeitem.get_status("INVALIDATED"):
wc.set_status("INVALIDATED", True)
if treeitem.get_status("REPLACED"):
wc.set_status("REPLACED", True)
if wc.children:
Will.reflect_to_children(wc)
# def reflect_to_children(treeitem):
# if not treeitem.get_status("VALID"):
# _logger.debug(f"{tree:item._id} status not valid looking for children")
# for child in treeitem.children:
# wc = willtree[child]
# if wc.get_status("VALID"):
# if treeitem.get_status("INVALIDATED"):
# wc.set_status("INVALIDATED", True)
# if treeitem.get_status("REPLACED"):
# wc.set_status("REPLACED", True)
# if wc.children:
# Will.reflect_to_children(wc)
def check_amounts(heirs, willexecutors, all_utxos, timestamp_to_check, dust):
fixed_heirs, fixed_amount, perc_heirs, perc_amount = (
fixed_heirs, fixed_amount, perc_heirs, perc_amount, fixed_amount_with_dust = (
heirs.fixed_percent_lists_amount(timestamp_to_check, dust, reverse=True)
)
wallet_balance = 0
@@ -543,16 +561,16 @@ class Will:
f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}"
)
def check_all_input_spent_are_in_wallet():
_logger.info("check all input spent are in wallet or valid txs")
for inp, ws in all_inputs.items():
if not Util.in_utxo(inp, all_utxos):
for w in ws:
if w[1].get_status("VALID"):
prevout_id = w[2].prevout.txid.hex()
parentwill = will.get(prevout_id, False)
if not parentwill or not parentwill.get_status("VALID"):
w[1].set_status("INVALIDATED", True)
# def check_all_input_spent_are_in_wallet():
# _logger.info("check all input spent are in wallet or valid txs")
# for inp, ws in all_inputs.items():
# if not Util.in_utxo(inp, all_utxos):
# for w in ws:
# if w[1].get_status("VALID"):
# prevout_id = w[2].prevout.txid.hex()
# parentwill = will.get(prevout_id, False)
# if not parentwill or not parentwill.get_status("VALID"):
# w[1].set_status("INVALIDATED", True)
def only_valid_list(will):
out = {}
@@ -598,9 +616,9 @@ class Will:
heirs_found[wheir] = count + 1
else:
_logger.debug(
"heir not present transaction is not valid:", wid, w
f"heir not present transaction is not valid:{wheir} {wid}, {w}"
)
continue
if willexecutor := w.we:
count = willexecutors_found.get(willexecutor["url"], 0)
if Util.cmp_willexecutor(
@@ -612,19 +630,19 @@ class Will:
no_willexecutor += 1
count_heirs = 0
for h in heirs:
if Util.parse_locktime_string(heirs[h][2]) >= check_date:
count_heirs += 1
if not h in heirs_found:
if h not in heirs_found:
_logger.debug(f"heir: {h} not found")
raise HeirNotFoundException(h)
if not count_heirs:
raise NoHeirsException("there are not valid heirs")
if self_willexecutor and no_willexecutor == 0:
raise NoWillExecutorNotPresent("Backup tx")
for url, we in willexecutors.items():
if Willexecutors.is_selected(we):
if not url in willexecutors_found:
if url not in willexecutors_found:
_logger.debug(f"will-executor: {url} not fount")
raise WillExecutorNotPresent(url)
_logger.info("will is coherent with heirs and will-executors")
@@ -632,7 +650,6 @@ class Will:
class WillItem(Logger):
STATUS_DEFAULT = {
"ANTICIPATED": ["Anticipated", False],
"BROADCASTED": ["Broadcasted", False],
@@ -654,15 +671,15 @@ class WillItem(Logger):
}
def set_status(self, status, value=True):
#_logger.trace(
# _logger.trace(
# "set status {} - {} {} -> {}".format(
# self._id, status, self.STATUS[status][1], value
# )
#)
# )
if self.STATUS[status][1] == bool(value):
return None
self.status += "." + ("NOT " if not value else "" + _(self.STATUS[status][0]))
self.status += "." + (("NOT " if not value else "") + _(self.STATUS[status][0]))
self.STATUS[status][1] = bool(value)
if value:
if status in ["INVALIDATED", "REPLACED", "CONFIRMED", "PENDING"]:
@@ -776,9 +793,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")

View File

@@ -1,33 +1,37 @@
import json
from datetime import datetime
from functools import partial
import time
from aiohttp import ClientResponse
from electrum import constants
from electrum.gui.qt.util import WaitingDialog
from electrum.i18n import _
from electrum.logging import get_logger
from electrum.network import Network
from .bal import BalPlugin
from .util import Util
DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__)
chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin"
class Willexecutors:
def save(bal_plugin, willexecutors):
_logger.debug(f"save {willexecutors},{chainname}")
aw = bal_plugin.WILLEXECUTORS.get()
aw[constants.net.NET_NAME] = willexecutors
aw[chainname] = willexecutors
bal_plugin.WILLEXECUTORS.set(aw)
_logger.debug(f"saved: {aw}")
# bal_plugin.WILLEXECUTORS.set(willexecutors)
def get_willexecutors(
bal_plugin, update=False, bal_window=False, force=False, task=True
):
willexecutors = bal_plugin.WILLEXECUTORS.get()
willexecutors = willexecutors.get(constants.net.NET_NAME, {})
willexecutors = willexecutors.get(chainname, {})
to_del = []
for w in willexecutors:
if not isinstance(willexecutors[w], dict):
@@ -35,14 +39,18 @@ class Willexecutors:
continue
Willexecutors.initialize_willexecutor(willexecutors[w], w)
for w in to_del:
_logger.error("error Willexecutor to delete type:{} ", type(willexecutor[w]),w)
_logger.error(
"error Willexecutor to delete type:{} {}".format(
type(willexecutors[w]), w
)
)
del willexecutors[w]
bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME, {})
bal = bal_plugin.WILLEXECUTORS.default.get(chainname, {})
for bal_url, bal_executor in bal.items():
if not bal_url in willexecutors:
if bal_url not in willexecutors:
_logger.debug(f"force add {bal_url} willexecutor")
willexecutors[bal_url] = bal_executor
#if update:
# if update:
# found = False
# for url, we in willexecutors.items():
# if Willexecutors.is_selected(we):
@@ -73,11 +81,11 @@ class Willexecutors:
def is_selected(willexecutor, value=None):
if not willexecutor:
return False
if not value is None:
if value is not None:
willexecutor["selected"] = value
try:
return willexecutor["selected"]
except:
except Exception:
willexecutor["selected"] = False
return False
@@ -90,7 +98,7 @@ class Willexecutors:
if willexecutor := willitem.we:
url = willexecutor["url"]
if willexecutor and Willexecutors.is_selected(willexecutor):
if not url in willexecutors:
if url not in willexecutors:
willexecutor["txs"] = ""
willexecutor["txsids"] = []
willexecutor["broadcast_status"] = _("Waiting...")
@@ -100,31 +108,34 @@ class Willexecutors:
return willexecutors
def only_selected_list(willexecutors):
out = {}
for url, v in willexecutors.items():
if Willexecutors.is_selected(willexecutor):
out[url] = v
# def only_selected_list(willexecutors):
# out = {}
# for url, v in willexecutors.items():
# if Willexecutors.is_selected(url):
# out[url] = v
def push_transactions_to_willexecutors(will):
willexecutors = get_transactions_to_be_pushed()
for url in willexecutors:
willexecutor = willexecutors[url]
if Willexecutors.is_selected(willexecutor):
if "txs" in willexecutor:
Willexecutors.push_transactions_to_willexecutor(
willexecutors[url]["txs"], url
)
# def push_transactions_to_willexecutors(will):
# willexecutors = Willexecutors.get_transactions_to_be_pushed()
# for url in willexecutors:
# willexecutor = willexecutors[url]
# if Willexecutors.is_selected(willexecutor):
# if "txs" in willexecutor:
# Willexecutors.push_transactions_to_willexecutor(
# willexecutors[url]["txs"], url
# )
def send_request(method, url, data=None, *, timeout=10):
def send_request(
bal_plugin,method, url, data=None, *, timeout=10, handle_response=None, count_reply=0
):
network = Network.get_instance()
if not network:
raise ErrorConnectingServer("You are offline.")
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:{bal_plugin.version()}"
headers["Content-Type"] = "text/plain"
if not handle_response:
handle_response = Willexecutors.handle_response
try:
if method == "get":
response = Network.send_http_on_proxy(
@@ -132,7 +143,7 @@ class Willexecutors:
url,
params=data,
headers=headers,
on_finish=Willexecutors.handle_response,
on_finish=handle_response,
timeout=timeout,
)
elif method == "post":
@@ -141,40 +152,62 @@ class Willexecutors:
url,
body=data,
headers=headers,
on_finish=Willexecutors.handle_response,
on_finish=handle_response,
timeout=timeout,
)
else:
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(
bal_plugin,
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:
_logger.error(f"exception sending request {e}")
raise e
else:
_logger.debug(f"--> {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):
r = await resp.text()
try:
r = json.loads(r)
r["status"] = resp.status
r["selected"] = Willexecutors.is_selected(willexecutor)
r["url"] = url
except:
# url = Willexecutors.get_we_url_from_response(resp)
# r["url"]= url
# r["status"]=resp.status
except Exception as e:
_logger.debug(f"error handling response:{e}")
pass
return r
class AlreadyPresentException(Exception):
pass
def push_transactions_to_willexecutor(willexecutor):
def push_transactions_to_willexecutor(bal_plugin, willexecutor):
out = True
try:
_logger.debug(f"willexecutor['txs']")
_logger.debug(f"{willexecutor['url']}: {willexecutor['txs']}")
if w := Willexecutors.send_request(
bal_plugin,
"post",
willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs",
willexecutor["url"] + "/" + chainname + "/pushtxs",
data=willexecutor["txs"].encode("ascii"),
):
willexecutor["broadcast_status"] = _("Success")
@@ -193,27 +226,23 @@ class Willexecutors:
return out
def ping_servers(willexecutors):
def ping_servers(bal_plugin, willexecutors):
for url, we in willexecutors.items():
Willexecutors.get_info_task(url, we)
Willexecutors.get_info_task(bal_plugin,url, we)
def get_info_task(url, willexecutor):
def get_info_task(bal_plugin,url, willexecutor):
w = None
try:
_logger.info("GETINFO_WILLEXECUTOR")
_logger.debug(url)
netname = "bitcoin"
if constants.net.NET_NAME != "mainnet":
netname = constants.net.NET_NAME
w = Willexecutors.send_request("get", url + "/" + netname + "/info")
willexecutor["url"] = url
willexecutor["status"] = w["status"]
willexecutor["base_fee"] = w["base_fee"]
willexecutor["address"] = w["address"]
if not willexecutor["info"]:
w = Willexecutors.send_request(bal_plugin,"get", url + "/" + chainname + "/info")
if isinstance(w, dict):
willexecutor["url"] = url
willexecutor["status"] = 200
willexecutor["base_fee"] = w["base_fee"]
willexecutor["address"] = w["address"]
willexecutor["info"] = w["info"]
_logger.debug(f"response_data {w['address']}")
_logger.debug(f"response_data {w}")
except Exception as e:
_logger.error(f"error {e} contacting {url}: {w}")
willexecutor["status"] = "KO"
@@ -221,24 +250,34 @@ class Willexecutors:
willexecutor["last_update"] = datetime.now().timestamp()
return willexecutor
def initialize_willexecutor(willexecutor, url, status=None, selected=None):
def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor={}):
willexecutor["url"] = url
if not status is None:
if status is not None:
willexecutor["status"] = status
willexecutor["selected"] = Willexecutors.is_selected(willexecutor, selected)
else:
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:
l = Willexecutors.send_request(
"get", "https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100"
willexecutors = Willexecutors.send_request(
bal_plugin,
"get",
f"https://welist.bitcoin-after.life/data/{chainname}?page=0&limit=100",
)
del l["status"]
for w in l:
willexecutor = l[w]
Willexecutors.initialize_willexecutor(willexecutor, w, "New", False)
# del willexecutors["status"]
for w in willexecutors:
if w not in ("status", "url"):
Willexecutors.initialize_willexecutor(
willexecutors[w], w, None, old_willexecutors.get(w,{})
)
# bal_plugin.WILLEXECUTORS.set(l)
# bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True)
return l
return willexecutors
except Exception as e:
_logger.error(f"Failed to download willexecutors list: {e}")
@@ -252,16 +291,17 @@ class Willexecutors:
willexecutor = willexecutors[w]
Willexecutors.initialize_willexecutor(willexecutor, w, "New", False)
# bal_plugin.WILLEXECUTORS.set(willexecutors)
return h
return willexecutors
except Exception as e:
_logger.error(f"error opening willexecutors json: {e}")
return {}
def check_transaction(txid, url):
def check_transaction(bal_plugin,txid, url):
_logger.debug(f"{url}:{txid}")
try:
w = Willexecutors.send_request(
bal_plugin,
"post", url + "/searchtx", data=txid.encode("ascii")
)
return w
@@ -269,14 +309,53 @@ class Willexecutors:
_logger.error(f"error contacting {url} for checking txs {e}")
raise e
def compute_id(willexecutor):
return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain"))
class WillExecutor:
def __init__(self, url, base_fee, chain, info, version):
def __init__(
self,
url,
base_fee,
chain,
info,
version,
status,
is_selected=False,
promo_code="",
):
self.url = url
self.base_fee = base_fee
self.chain = chain
self.info = info
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):
we = WillExecutor(d["url"], d["base_fee"], d["chain"], d["info"], d["version"])
return WillExecutor(
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}"