30 Commits
v0.2.3 ... main

Author SHA1 Message Date
b5eda4f05a partial commit to fix wallet utils
this commit provide a lot of changes in will-settings including export to ics calendar file.
2026-04-27 10:03:05 -04:00
9204c90e4c WillException.init bug 2026-04-15 13:08:59 -04:00
6f44a3bb54 checkalive sulla finestra will 2026-04-10 16:34:27 -04:00
8f966a974a checkalive sulla finestra will 2026-04-10 16:08:33 -04:00
a59b5c47b6 checkalive sulla finestra will 2026-04-10 16:02:40 -04:00
56586ef0a7 default locktime as date or as intervall depending on value. 2026-04-09 16:45:47 -04:00
d439b1fdde msg_update sizehint 2026-04-09 06:20:19 -04:00
c99f0fd70f msg_update not recreate labels update text 2026-04-09 05:58:01 -04:00
ab6aa7a698 msg_update window size 2026-04-09 05:46:43 -04:00
b55493221d bug in log 2026-04-09 05:02:21 -04:00
dff508c25b version 2026-04-08 11:17:54 -04:00
2056ffae7f check alive updated 2026-04-08 11:16:59 -04:00
c8ab85b735 invalidation and locktime 2026-04-05 11:39:17 -04:00
e2de4a3afa skip willexecutor with dust amount 2026-03-27 23:06:27 -04:00
3a44b492e4 __version__0.2.7 2026-03-21 11:31:46 -04:00
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
2a4eab81fd print 2026-02-05 17:51:17 -04:00
d86b941fcb refresh button. locktim is correctly saved, minor bugfix in checking confirmed transaction 2026-02-05 17:11:11 -04:00
1836cdd892 version 2026-02-03 22:12:30 -04:00
2416d0ce8d fix willexecutor list edit 2026-02-03 22:11:33 -04:00
8e4e401d1b fix plugin settings removed willexecutor ping 2026-02-03 16:14:12 -04:00
b8859ee5c1 qt thread panic
heirs import wizard
2026-02-03 13:56:47 -04:00
faeff1ff3c gui willexecutor list status moved after url to be more visible.
heirs tab start hidden
2026-02-03 11:25:21 -04:00
437105477d missing icons 2026-01-28 14:47:24 -04:00
14 changed files with 1722 additions and 1070 deletions

View File

@@ -1 +1 @@
0.2.3 0.2.8

122
bal.py
View File

@@ -1,15 +1,16 @@
import os import os
#import random from datetime import date, datetime, timedelta
#import zipfile as zipfile_lib import platform
# import random
from electrum import json_db # import zipfile as zipfile_lib
from electrum import constants, json_db
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.plugin import BasePlugin from electrum.plugin import BasePlugin
from electrum.transaction import tx_from_any from electrum.transaction import tx_from_any
_logger = get_logger(__name__)
def get_will_settings(x): def get_will_settings(x):
#print(x) # print(x)
pass pass
@@ -18,7 +19,6 @@ json_db.register_dict("will", dict, None)
json_db.register_dict("will_settings", lambda x: x, None) json_db.register_dict("will_settings", lambda x: x, None)
def get_will(x): def get_will(x):
try: try:
x["tx"] = tx_from_any(x["tx"]) x["tx"] = tx_from_any(x["tx"])
@@ -47,18 +47,25 @@ class BalConfig:
class BalPlugin(BasePlugin): class BalPlugin(BasePlugin):
LATEST_VERSION = "1" _version=None
KNOWN_VERSIONS = ("0", "1") __version__ = "0.2.8" #AUTOMATICALLY GENERATED DO NOT EDIT
assert LATEST_VERSION in KNOWN_VERSIONS default_app={
"Linux":"xdg-open",
def version(): "Window":"start",
try: "Darwin":"open"
f = "" }
with open("VERSION", "r") as fi: chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin"
f = str(fi.readline()) def version(self):
return f if not self._version:
except: try:
return "unknown" 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) SIZE = (159, 97)
@@ -68,15 +75,9 @@ class BalPlugin(BasePlugin):
self.base_dir = os.path.join(config.electrum_path(), "bal") self.base_dir = os.path.join(config.electrum_path(), "bal")
self.plugin_dir = os.path.split(os.path.realpath(__file__))[0] self.plugin_dir = os.path.split(os.path.realpath(__file__))[0]
zipfile = "/".join(self.plugin_dir.split("/")[:-1]) zipfile = "/".join(self.plugin_dir.split("/")[:-1])
# print("real path",os.path.realpath(__file__))
# self.logger.info(self.base_dir)
# print("base_dir:", self.base_dir)
# print("suca:",zipfile)
# print("plugin_dir:", self.plugin_dir)
import sys import sys
sys.path.insert(0, zipfile) sys.path.insert(0, zipfile)
# print("sono state listate?")
self.parent = parent self.parent = parent
self.config = config self.config = config
self.name = name self.name = name
@@ -96,20 +97,30 @@ class BalPlugin(BasePlugin):
self.PREVIEW = BalConfig(config, "bal_preview", True) self.PREVIEW = BalConfig(config, "bal_preview", True)
self.SAVE_TXS = BalConfig(config, "bal_save_txs", True) self.SAVE_TXS = BalConfig(config, "bal_save_txs", True)
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", True) self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", True)
self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True) # self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True)
self.ASK_PING_WILLEXECUTORS = BalConfig( # self.ASK_PING_WILLEXECUTORS = BalConfig(
config, "bal_ask_ping_willexecutors", True # config, "bal_ask_ping_willexecutors", True
) # )
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True) self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True) self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True)
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True) self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True) self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True)
self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True) self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True)
self.WELIST_SERVER = BalConfig(config,"bal_welist_server","https://welist.bitcoin-after.life/")
self.WILLEXECUTORS = BalConfig( self.WILLEXECUTORS = BalConfig(
config, config,
"bal_willexecutors", "bal_willexecutors",
{ {
"mainnet": { "mainnet": {
"https://we.bitcoin-after.life": {
"base_fee": 100000,
"status": "New",
"info": "Bitcoin After Life Will Executor",
"address": "bc1qusymuetsz2psaqzqxv8qmzcy64d9meckj3lxxf",
"selected": True,
}
},
"testnet": {
"https://we.bitcoin-after.life": { "https://we.bitcoin-after.life": {
"base_fee": 100000, "base_fee": 100000,
"status": "New", "status": "New",
@@ -117,19 +128,34 @@ class BalPlugin(BasePlugin):
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected": True, "selected": True,
} }
} },
"testnet4": {
"https://we.bitcoin-after.life": {
"base_fee": 100000,
"status": "New",
"info": "Bitcoin After Life Will Executor",
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected": True,
}
},
"regtest": {
"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(
config, config,
"bal_will_settings", "bal_will_settings",
{ BalPlugin.default_will_settings(),
"baltx_fees": 100,
"threshold": "180d",
"locktime": "1y",
},
) )
self.system = platform.system()
self.CALENDAR_APP = BalConfig(config,"bal_open_app",self.default_app[self.system])
self._hide_invalidated = self.HIDE_INVALIDATED.get() self._hide_invalidated = self.HIDE_INVALIDATED.get()
self._hide_replaced = self.HIDE_REPLACED.get() self._hide_replaced = self.HIDE_REPLACED.get()
@@ -145,16 +171,22 @@ class BalPlugin(BasePlugin):
self.HIDE_REPLACED.set(self._hide_replaced) self.HIDE_REPLACED.set(self._hide_replaced)
def validate_will_settings(self, will_settings): def validate_will_settings(self, will_settings):
# print(type(will_settings)) defaults=BalPlugin.default_will_settings()
# print(will_settings.get('baltx_fees',1),1) if not will_settings:
if int(will_settings.get("baltx_fees", 1)) < 1: will_settings=[]
will_settings["baltx_fees"] = 1 if int(will_settings.get("baltx_fees", 0)) < 1:
will_settings["baltx_fees"] = defaults['baltx_fees']
if not will_settings.get("threshold"): if not will_settings.get("threshold"):
will_settings["threshold"] = "180d" will_settings["threshold"] = defaults['threshold']
if not will_settings.get("locktime") == "": if not will_settings.get("locktime"):
will_settings["locktime"] = "1y" will_settings["locktime"] = defaults['locktime']
return will_settings return will_settings
def default_will_settings(self): @staticmethod
return {"baltx_fees": 100, "threshold": "180d", "locktime": "1y"} def default_will_settings():
today = date.today()
dt = datetime(today.year, today.month, today.day, 0, 0, 0)
threshold =(dt + timedelta(days=180)).timestamp()
locktime =(dt + timedelta(days=365)).timestamp()
return {"baltx_fees": 100, "threshold": threshold, "locktime": locktime}

316
heirs.py
View File

@@ -1,25 +1,40 @@
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.request # import urllib.parse
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple # import urllib.request
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 bitcoin, constants, descriptor, dnssec from electrum import (
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 (
BitcoinException,
bfh, bfh,
read_json_file, read_json_file,
to_string, to_string,
@@ -32,7 +47,8 @@ from .willexecutors import Willexecutors
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__)
@@ -41,6 +57,7 @@ HEIR_ADDRESS = 0
HEIR_AMOUNT = 1 HEIR_AMOUNT = 1
HEIR_LOCKTIME = 2 HEIR_LOCKTIME = 2
HEIR_REAL_AMOUNT = 3 HEIR_REAL_AMOUNT = 3
HEIR_DUST_AMOUNT = 4
TRANSACTION_LABEL = "inheritance transaction" TRANSACTION_LABEL = "inheritance transaction"
@@ -54,28 +71,22 @@ def reduce_outputs(in_amount, out_amount, fee, outputs):
output.value = math.floor((in_amount - fee) / out_amount * output.value) output.value = math.floor((in_amount - fee) / out_amount * output.value)
""" def create_op_return_script(data_hex: str) -> bytes:
#TODO: put this method inside wallet.db to replace or complete get_locktime_for_new_transaction """Crea scriptpubkey OP_RETURN in bytes"""
def get_current_height(network:'Network'): data = bytes.fromhex(data_hex)
#if no network or not up to date, just set locktime to zero
if not network:
return 0
chain = network.blockchain()
if chain.is_tip_stale():
return 0
# figure out current block height
chain_height = chain.height() # learnt from all connected servers, SPV-checked
server_height = network.get_server_height() # height claimed by main server, unverified
# note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork)
# - if it's lagging too much, it is the network's job to switch away
if server_height < chain_height - 10:
# the diff is suspiciously large... give up and use something non-fingerprintable
return 0
# discourage "fee sniping"
height = min(chain_height, server_height)
return height
"""
if len(data) > 80:
raise ValueError("OP_RETURN data too big (max 80 bytes)")
# Costruzione manuale: OP_RETURN + push data
if len(data) <= 75:
# Formato più comune: OP_RETURN + 1-byte length + data
script = b'\x6a' + bytes([len(data)]) + data
else:
# Per dati più grandi (fino a 80) si usa OP_PUSHDATA1
script = b'\x6a\x4c' + bytes([len(data)]) + data
return script
def prepare_transactions(locktimes, available_utxos, fees, wallet): def prepare_transactions(locktimes, available_utxos, fees, wallet):
available_utxos = sorted( available_utxos = sorted(
@@ -84,39 +95,45 @@ 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:
_logger.info("prepare transactions, no locktime")
return return
locktime = locktime[0] locktime = locktime[0]
heirs = locktimes[locktime] heirs = locktimes[locktime]
vero = True true = True
while vero: while true:
vero = False true = 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(
try: heir[HEIR_REAL_AMOUNT]
if len(heir) > HEIR_REAL_AMOUNT: ):
try:
real_amount = heir[HEIR_REAL_AMOUNT] real_amount = heir[HEIR_REAL_AMOUNT]
out_amount += real_amount
description += f"{name}\n"
paid_heirs[name] = heir
outputs.append( outputs.append(
PartialTxOutput.from_address_and_value( PartialTxOutput.from_address_and_value(
heir[HEIR_ADDRESS], real_amount 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 pass
except Exception as e: paid_heirs[name] = heir
pass
in_amount = 0.0 in_amount = 0.0
used_utxos = [] used_utxos = []
@@ -125,19 +142,32 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
value = utxo.value_sats() value = utxo.value_sats()
in_amount += value in_amount += value
used_utxos.append(utxo) used_utxos.append(utxo)
if in_amount > out_amount: if in_amount >= out_amount:
break break
except IndexError as e: except IndexError as e:
_logger.error(
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):
break _logger.error(
"error preparing transactions in_amount < out_amount ({} < {}) "
)
continue
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:
outputs.append(change) outputs.append(change)
for i in range(0, 100): for i in range(0, 100):
random.shuffle(outputs) random.shuffle(outputs)
#op_return_text = "Hello Bal!"
## Convert text to hex
#op_return_hex = op_return_text.encode('utf-8').hex()
#op_return_script = create_op_return_script(op_return_hex)
#outputs.append(PartialTxOutput(value=0, scriptpubkey=op_return_script))
tx = PartialTransaction.from_io( tx = PartialTransaction.from_io(
used_utxos, used_utxos,
outputs, outputs,
@@ -153,7 +183,7 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
tx.remove_signatures() tx.remove_signatures()
txid = tx.txid() txid = tx.txid()
if txid is None: if txid is None:
raise Exception("txid is none", tx) raise Exception(f"txid is none: {tx}")
tx.heirs = paid_heirs tx.heirs = paid_heirs
tx.my_locktime = locktime tx.my_locktime = locktime
@@ -185,7 +215,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():
@@ -214,7 +244,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 not txid in invalidated: if txid not in invalidated:
invalidated.append(tx.txid()) invalidated.append(tx.txid())
remaining[key] = value remaining[key] = value
@@ -222,10 +252,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(f"---") print("---")
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(f"---") print("---")
for out in jtx["outputs"]: for out in jtx["outputs"]:
heirname = "" heirname = ""
for key in heirs.keys(): for key in heirs.keys():
@@ -247,7 +277,7 @@ def print_transaction(heirs, tx, locktimes, tx_fees):
print() print()
try: try:
print(tx.serialize_to_network()) print(tx.serialize_to_network())
except: except Exception:
print("impossible to serialize") print("impossible to serialize")
print() print()
@@ -263,13 +293,14 @@ def get_change_output(wallet, in_amount, out_amount, fee):
class Heirs(dict, Logger): class Heirs(dict, Logger):
def __init__(self, db: "WalletDB"): def __init__(self, wallet):
Logger.__init__(self) Logger.__init__(self)
self.db = db self.db = wallet.db
self.wallet = wallet
d = self.db.get("heirs", {}) d = self.db.get("heirs", {})
try: try:
self.update(d) self.update(d)
except e as Exception: except Exception:
return return
def invalidate_transactions(self, wallet): def invalidate_transactions(self, wallet):
@@ -303,7 +334,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 locktimes.keys() return list(locktimes.keys())
def check_locktime(self): def check_locktime(self):
return False return False
@@ -317,6 +348,8 @@ 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
@@ -327,6 +360,10 @@ class Heirs(dict, Logger):
if value > wallet.dust_threshold(): if value > wallet.dust_threshold():
heir_list[key].insert(HEIR_REAL_AMOUNT, value) heir_list[key].insert(HEIR_REAL_AMOUNT, value)
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: except Exception as e:
raise e raise e
@@ -335,10 +372,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: except Exception:
try: try:
return float(amount[:-1]) return float(amount[:-1])
except: except Exception:
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):
@@ -346,31 +383,50 @@ 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:
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: except Exception as e:
_logger.error(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( def prepare_lists(
self, balance, total_fees, wallet, willexecutor=False, from_locktime=0 self, balance, total_fees, wallet, willexecutor=False, from_locktime=0
): ):
if balance<total_fees or balance < wallet.dust_threshold():
raise BalanceTooLowException(balance,wallet.dust_threshold(),total_fees)
willexecutors_amount = 0 willexecutors_amount = 0
willexecutors = {} willexecutors = {}
heir_list = {} heir_list = {}
@@ -391,7 +447,7 @@ class Heirs(dict, Logger):
willexecutors[ willexecutors[
'w!ll3x3c"' + willexecutor["url"] + '"' + str(locktime) 'w!ll3x3c"' + willexecutor["url"] + '"' + str(locktime)
] = h ] = h
except Exception as e: except Exception:
return [], False return [], False
else: else:
_logger.error( _logger.error(
@@ -399,9 +455,15 @@ class Heirs(dict, Logger):
), ),
heir_list.update(willexecutors) heir_list.update(willexecutors)
newbalance -= willexecutors_amount newbalance -= willexecutors_amount
fixed_heirs, fixed_amount, percent_heirs, percent_amount = ( if newbalance < 0:
self.fixed_percent_lists_amount(from_locktime, wallet.dust_threshold()) 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: 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
@@ -411,18 +473,16 @@ class Heirs(dict, Logger):
heir_list.update(fixed_heirs) heir_list.update(fixed_heirs)
newbalance -= fixed_amount newbalance -= fixed_amount
if newbalance > 0: if newbalance > 0:
perc_amount = self.normalize_perc( perc_amount = self.normalize_perc(
percent_heirs, newbalance, percent_amount, wallet percent_heirs, newbalance, percent_amount, wallet
) )
newbalance -= perc_amount newbalance -= perc_amount
heir_list.update(percent_heirs) heir_list.update(percent_heirs)
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, wallet, real=True fixed_heirs, newbalance, fixed_amount_with_dust, wallet, real=True
) )
newbalance -= fixed_amount newbalance -= fixed_amount
heir_list.update(fixed_heirs) heir_list.update(fixed_heirs)
@@ -435,7 +495,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 not locktime in locktimes: if locktime not in locktimes:
locktimes[locktime] = {key: value} locktimes[locktime] = {key: value}
else: else:
locktimes[locktime][key] = value locktimes[locktime][key] = value
@@ -449,6 +509,7 @@ class Heirs(dict, Logger):
): ):
Heirs._validate(self) Heirs._validate(self)
if len(self) <= 0: if len(self) <= 0:
_logger.info("while building transactions there was no heirs")
return return
balance = 0.0 balance = 0.0
len_utxo_set = 0 len_utxo_set = 0
@@ -464,6 +525,7 @@ class Heirs(dict, Logger):
len_utxo_set += 1 len_utxo_set += 1
available_utxos.append(utxo) available_utxos.append(utxo)
if len_utxo_set == 0: if len_utxo_set == 0:
_logger.info("no usable utxos")
return return
j = -2 j = -2
willexecutorsitems = list(willexecutors.items()) willexecutorsitems = list(willexecutors.items())
@@ -475,7 +537,7 @@ class Heirs(dict, Logger):
break break
elif 0 <= j: elif 0 <= j:
url, willexecutor = willexecutorsitems[j] url, willexecutor = willexecutorsitems[j]
if not Willexecutors.is_selected(willexecutor): if not Willexecutors.is_selected(willexecutor) or willexecutor["base_fee"] < wallet.dust_threshold():
continue continue
else: else:
willexecutor["url"] = url willexecutor["url"] = url
@@ -487,51 +549,66 @@ class Heirs(dict, Logger):
break break
fees = {} fees = {}
i = 0 i = 0
while True: while i < 10:
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:
txs = prepare_transactions( locktimes, onlyfixed = self.prepare_lists(
locktimes, available_utxos[:], fees, wallet balance, total_fees, wallet, willexecutor, from_locktime
) )
if not txs: except WillExecutorFeeException:
return {} i = 10
except Exception as e: continue
if locktimes:
try: try:
if "w!ll3x3c" in e.heirname: txs = prepare_transactions(
Willexecutors.is_selected(willexecutors[w], False) locktimes, available_utxos[:], fees, wallet
break )
except: if not txs:
raise e return {}
total_fees = 0 except Exception as e:
total_fees_real = 0 _logger.error(
total_in = 0 f"build transactions: error preparing transactions: {e}"
for txid, tx in txs.items(): )
tx.willexecutor = willexecutor try:
fee = tx.estimated_size() * tx_fees if "w!ll3x3c" in e.heirname:
txs[txid].tx_fees = tx_fees Willexecutors.is_selected(
total_fees += fee e.heirname[len("w!ll3x3c") :], False
total_fees_real += tx.get_fee() )
total_in += tx.input_value() break
rfee = tx.input_value() - tx.output_value() except Exception:
if rfee < fee or rfee > fee + wallet.dust_threshold(): raise e
redo = True total_fees = 0
oldfees = fees.get(tx.my_locktime, 0) total_fees_real = 0
fees[tx.my_locktime] = fee 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(): if balance - total_in > wallet.dust_threshold():
redo = True redo = True
if not redo: if not redo:
break break
if i >= 10: if i >= 10:
break
else:
_logger.info(
f"no locktimes for willexecutor {willexecutor} skipped"
)
break break
alltxs.update(txs) alltxs.update(txs)
@@ -632,7 +709,7 @@ class Heirs(dict, Logger):
return None return None
def validate_address(address): 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}") raise NotAnAddress(f"not an address,{address}")
return address return address
@@ -661,12 +738,14 @@ class Heirs(dict, Logger):
return (address, amount, locktime) return (address, amount, locktime)
def _validate(data, timestamp_to_check=False): def _validate(data, timestamp_to_check=False):
for k, v in list(data.items()): for k, v in list(data.items()):
if k == "heirs": if k == "heirs":
return Heirs._validate(v) return Heirs._validate(v, timestamp_to_check)
try: try:
Heirs.validate_heir(k, v) Heirs.validate_heir(k, v, timestamp_to_check)
except Exception as e: except Exception as e:
_logger.info(f"exception heir removed {e}")
data.pop(k) data.pop(k)
return data return data
@@ -685,3 +764,28 @@ class LocktimeNotValid(ValueError):
class HeirExpiredException(LocktimeNotValid): class HeirExpiredException(LocktimeNotValid):
pass 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"]
)
class BalanceTooLowException(Exception):
def __init__(self,balance, dust_threshold, fees):
self.balance=balance
self.dust_threshold = dust_threshold
self.fees = fees
def __str__(self):
return f"Balance too low, balance: {self.balance}, dust threshold: {self.dust_threshold}, fees: {self.fees}"

BIN
icons/confirmed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
icons/status_connected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
icons/unconfirmed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

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.3", "description": "Provides free and decentralized inheritance support<br> Version: 0.2.8",
"author":"Svatantrya", "author":"Svatantrya",
"available_for": ["qt"], "available_for": ["qt"],
"icon":"icons/bal32x32.png" "icon":"icons/bal32x32.png"

1608
qt.py

File diff suppressed because it is too large Load Diff

161
util.py
View File

@@ -1,17 +1,13 @@
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.i18n import _
from electrum.transaction import PartialTxOutput from electrum.transaction import PartialTxOutput
from electrum.util import FileExportFailed, FileImportFailed, write_json_file
LOCKTIME_THRESHOLD = 500000000 LOCKTIME_THRESHOLD = 500000000
class Util: class Util:
@staticmethod
def locktime_to_str(locktime): def locktime_to_str(locktime):
try: try:
locktime = int(locktime) locktime = int(locktime)
@@ -19,27 +15,29 @@ class Util:
dt = datetime.fromtimestamp(locktime).isoformat() dt = datetime.fromtimestamp(locktime).isoformat()
return dt return dt
except Exception as e: except Exception:
pass pass
return str(locktime) return str(locktime)
@staticmethod
def str_to_locktime(locktime): def str_to_locktime(locktime):
try: try:
if locktime[-1] in ("y", "d", "b"): if locktime[-1] in ("y", "d", "b"):
return locktime return locktime
else: else:
return int(locktime) return int(locktime)
except Exception as e: except Exception:
pass pass
dt_object = datetime.fromisoformat(locktime) dt_object = datetime.fromisoformat(locktime)
timestamp = dt_object.timestamp() timestamp = dt_object.timestamp()
return int(timestamp) return int(timestamp)
@staticmethod
def parse_locktime_string(locktime, w=None): def parse_locktime_string(locktime, w=None):
try: try:
return int(locktime) return int(locktime)
except Exception as e: except Exception:
pass pass
try: try:
now = datetime.now() now = datetime.now()
@@ -58,10 +56,11 @@ 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 as e: except Exception:
pass pass
return 0 return 0
@staticmethod
def int_locktime(seconds=0, minutes=0, hours=0, days=0, blocks=0): def int_locktime(seconds=0, minutes=0, hours=0, days=0, blocks=0):
return int( return int(
seconds seconds
@@ -71,45 +70,53 @@ class Util:
+ blocks * 600 + blocks * 600
) )
@staticmethod
def encode_amount(amount, decimal_point): def encode_amount(amount, decimal_point):
if Util.is_perc(amount): if Util.is_perc(amount):
return amount return amount
else: else:
try: try:
return int(float(amount) * pow(10, decimal_point)) return int(float(amount) * pow(10, decimal_point))
except: except Exception:
return 0 return 0
@staticmethod
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:
num = 8 - decimal_point basestr = "{{:0.{}f}}".format(decimal_point)
basestr = "{{:0{}.{}f}}".format(num, num) try:
return "{:08.8f}".format(float(amount) / pow(10, decimal_point)) return basestr.format(float(amount) / pow(10, decimal_point))
except Exception:
return str(amount)
@staticmethod
def is_perc(value): def is_perc(value):
try: try:
return value[-1] == "%" return value[-1] == "%"
except: except Exception:
return False return False
@staticmethod
def cmp_array(heira, heirb): def cmp_array(heira, heirb):
try: try:
if not len(heira) == len(heirb): if len(heira) != len(heirb):
return False return False
for h in range(0, len(heira)): for h in range(0, len(heira)):
if not heira[h] == heirb[h]: if heira[h] != heirb[h]:
return False return False
return True return True
except: except Exception:
return False return False
@staticmethod
def cmp_heir(heira, heirb): def cmp_heir(heira, heirb):
if heira[0] == heirb[0] and heira[1] == heirb[1]: if heira[0] == heirb[0] and heira[1] == heirb[1]:
return True return True
return False return False
@staticmethod
def cmp_willexecutor(willexecutora, willexecutorb): def cmp_willexecutor(willexecutora, willexecutorb):
if willexecutora == willexecutorb: if willexecutora == willexecutorb:
return True return True
@@ -120,10 +127,11 @@ class Util:
and willexecutora["base_fee"] == willexecutorb["base_fee"] and willexecutora["base_fee"] == willexecutorb["base_fee"]
): ):
return True return True
except: except Exception:
return False return False
return False return False
@staticmethod
def search_heir_by_values(heirs, heir, values): def search_heir_by_values(heirs, heir, values):
for h, v in heirs.items(): for h, v in heirs.items():
found = False found = False
@@ -135,18 +143,20 @@ class Util:
return h return h
return False return False
@staticmethod
def cmp_heir_by_values(heira, heirb, values): def cmp_heir_by_values(heira, heirb, values):
for v in values: for v in values:
if heira[v] != heirb[v]: if heira[v] != heirb[v]:
return False return False
return True return True
@staticmethod
def cmp_heirs_by_values( def cmp_heirs_by_values(
heirsa, heirsb, values, exclude_willexecutors=False, reverse=True heirsa, heirsb, values, exclude_willexecutors=False, reverse=True
): ):
for heira in heirsa: for heira in heirsa:
if ( if (
exclude_willexecutors and not 'w!ll3x3c"' in heira exclude_willexecutors and 'w!ll3x3c"' not in heira
) or not exclude_willexecutors: ) or not exclude_willexecutors:
found = False found = False
for heirb in heirsb: for heirb in heirsb:
@@ -165,6 +175,7 @@ class Util:
else: else:
return True return True
@staticmethod
def cmp_heirs( def cmp_heirs(
heirsa, heirsa,
heirsb, heirsb,
@@ -173,8 +184,8 @@ class Util:
): ):
try: try:
for heir in heirsa: for heir in heirsa:
if not 'w!ll3x3c"' in heir: if 'w!ll3x3c"' not in heir:
if not heir in heirsb or not cmp_function( if heir not 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]):
@@ -187,6 +198,7 @@ class Util:
raise e raise e
return False return False
@staticmethod
def cmp_inputs(inputsa, inputsb): def cmp_inputs(inputsa, inputsb):
if len(inputsa) != len(inputsb): if len(inputsa) != len(inputsb):
return False return False
@@ -195,6 +207,7 @@ class Util:
return False return False
return True return True
@staticmethod
def cmp_outputs(outputsa, outputsb, willexecutor_output=None): def cmp_outputs(outputsa, outputsb, willexecutor_output=None):
if len(outputsa) != len(outputsb): if len(outputsa) != len(outputsb):
return False return False
@@ -204,6 +217,7 @@ class Util:
return False return False
return True return True
@staticmethod
def cmp_txs(txa, txb): def cmp_txs(txa, txb):
if not Util.cmp_inputs(txa.inputs(), txb.inputs()): if not Util.cmp_inputs(txa.inputs(), txb.inputs()):
return False return False
@@ -211,9 +225,10 @@ class Util:
return False return False
return True return True
@staticmethod
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:
@@ -229,6 +244,7 @@ class Util:
return value_amount return value_amount
@staticmethod
def chk_locktime(timestamp_to_check, block_height_to_check, locktime): def chk_locktime(timestamp_to_check, block_height_to_check, locktime):
# TODO BUG: WHAT HAPPEN AT THRESHOLD? # TODO BUG: WHAT HAPPEN AT THRESHOLD?
locktime = int(locktime) locktime = int(locktime)
@@ -239,6 +255,7 @@ class Util:
else: else:
return False return False
@staticmethod
def anticipate_locktime(locktime, blocks=0, hours=0, days=0): def anticipate_locktime(locktime, blocks=0, hours=0, days=0):
locktime = int(locktime) locktime = int(locktime)
out = 0 out = 0
@@ -255,54 +272,62 @@ class Util:
out = 1 out = 1
return out return out
@staticmethod
def cmp_locktime(locktimea, locktimeb): def cmp_locktime(locktimea, locktimeb):
if locktimea == locktimeb: if locktimea == locktimeb:
return 0 return 0
strlocktime = str(locktimea) strlocktimea = 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])
else: else:
return int(locktimea) - (locktimeb) return int(locktimea) - (locktimeb)
@staticmethod
def get_lowest_valid_tx(available_utxos, will): def get_lowest_valid_tx(available_utxos, will):
will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime) will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
for txid, willitem in will.items(): for txid, willitem in will.items():
pass pass
@staticmethod
def get_locktimes(will): def get_locktimes(will):
locktimes = {} locktimes = {}
for txid, willitem in will.items(): for txid, willitem in will.items():
locktimes[willitem["tx"].locktime] = True locktimes[willitem["tx"].locktime] = True
return locktimes.keys() return locktimes.keys()
@staticmethod
def get_lowest_locktimes(locktimes): def get_lowest_locktimes(locktimes):
sorted_timestamp = [] sorted_timestamp = []
sorted_block = [] sorted_block = []
for l in locktimes: for locktime in locktimes:
l = Util.parse_locktime_string(l) locktime = Util.parse_locktime_string(locktime)
if l < LOCKTIME_THRESHOLD: if locktime < LOCKTIME_THRESHOLD:
bisect.insort(sorted_block, l) bisect.insort(sorted_block, locktime)
else: else:
bisect.insort(sorted_timestamp, l) bisect.insort(sorted_timestamp, locktime)
return sorted(sorted_timestamp), sorted(sorted_block) return sorted(sorted_timestamp), sorted(sorted_block)
@staticmethod
def get_lowest_locktimes_from_will(will): def get_lowest_locktimes_from_will(will):
return Util.get_lowest_locktimes(Util.get_locktimes(will)) return Util.get_lowest_locktimes(Util.get_locktimes(will))
@staticmethod
def search_willtx_per_io(will, tx): def search_willtx_per_io(will, tx):
for wid, w in will.items(): for wid, w in will.items():
if Util.cmp_txs(w["tx"], tx["tx"]): if Util.cmp_txs(w["tx"], tx["tx"]):
return wid, w return wid, w
return None, None return None, None
@staticmethod
def invalidate_will(will): def invalidate_will(will):
raise Exception("not implemented") raise Exception("not implemented")
@staticmethod
def get_will_spent_utxos(will): def get_will_spent_utxos(will):
utxos = [] utxos = []
for txid, willitem in will.items(): for txid, willitem in will.items():
@@ -310,17 +335,19 @@ class Util:
return utxos return utxos
@staticmethod
def utxo_to_str(utxo): def utxo_to_str(utxo):
try: try:
return utxo.to_str() return utxo.to_str()
except Exception as e: except Exception:
pass pass
try: try:
return utxo.prevout.to_str() return utxo.prevout.to_str()
except Exception as e: except Exception:
pass pass
return str(utxo) return str(utxo)
@staticmethod
def cmp_utxo(utxoa, utxob): def cmp_utxo(utxoa, utxob):
utxoa = Util.utxo_to_str(utxoa) utxoa = Util.utxo_to_str(utxoa)
utxob = Util.utxo_to_str(utxob) utxob = Util.utxo_to_str(utxob)
@@ -329,21 +356,25 @@ class Util:
else: else:
return False return False
@staticmethod
def in_utxo(utxo, utxos): def in_utxo(utxo, utxos):
for s_u in utxos: for s_u in utxos:
if Util.cmp_utxo(s_u, utxo): if Util.cmp_utxo(s_u, utxo):
return True return True
return False return False
@staticmethod
def txid_in_utxo(txid, utxos): def txid_in_utxo(txid, utxos):
for s_u in utxos: for s_u in utxos:
if s_u.prevout.txid == txid: if s_u.prevout.txid == txid:
return True return True
return False return False
@staticmethod
def cmp_output(outputa, outputb): def cmp_output(outputa, outputb):
return outputa.address == outputb.address and outputa.value == outputb.value return outputa.address == outputb.address and outputa.value == outputb.value
@staticmethod
def in_output(output, outputs): def in_output(output, outputs):
for s_o in outputs: for s_o in outputs:
if Util.cmp_output(s_o, output): if Util.cmp_output(s_o, output):
@@ -355,6 +386,7 @@ class Util:
# return true false same amount different address # return true false same amount different address
# return false false different amount, different address not found # return false false different amount, different address not found
@staticmethod
def din_output(out, outputs): def din_output(out, outputs):
same_amount = [] same_amount = []
for s_o in outputs: for s_o in outputs:
@@ -370,6 +402,7 @@ class Util:
else: else:
return False, False return False, False
@staticmethod
def get_change_output(wallet, in_amount, out_amount, fee): def get_change_output(wallet, in_amount, out_amount, fee):
change_amount = int(in_amount - out_amount - fee) change_amount = int(in_amount - out_amount - fee)
if change_amount > wallet.dust_threshold(): if change_amount > wallet.dust_threshold():
@@ -380,7 +413,8 @@ class Util:
out.is_change = True out.is_change = True
return out return out
def get_current_height(network: "Network"): @staticmethod
def get_current_height(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
@@ -401,44 +435,42 @@ class Util:
height = min(chain_height, server_height) height = min(chain_height, server_height)
return height return height
@staticmethod
def print_var(var, name="", veryverbose=False): def print_var(var, name="", veryverbose=False):
print(f"---{name}---") print(f"---{name}---")
if not var is None: if var is not None:
try:
print("doc:", doc(var))
except:
pass
try: try:
print("str:", str(var)) print("str:", str(var))
except: except Exception:
pass pass
try: try:
print("repr", repr(var)) print("repr", repr(var))
except: except Exception:
pass pass
try: try:
print("dict", dict(var)) print("dict", dict(var))
except: except Exception:
pass pass
try: try:
print("dir", dir(var)) print("dir", dir(var))
except: except Exception:
pass pass
try: try:
print("type", type(var)) print("type", type(var))
except: except Exception:
pass pass
try: try:
print("to_json", var.to_json()) print("to_json", var.to_json())
except: except Exception:
pass pass
try: try:
print("__slotnames__", var.__slotnames__) print("__slotnames__", var.__slotnames__)
except: except Exception:
pass pass
print(f"---end {name}---") print(f"---end {name}---")
@staticmethod
def print_utxo(utxo, name=""): def print_utxo(utxo, name=""):
print(f"---utxo-{name}---") print(f"---utxo-{name}---")
Util.print_var(utxo, name) Util.print_var(utxo, name)
@@ -450,36 +482,20 @@ class Util:
print("_TxInput__value_sats:", utxo._TxInput__value_sats) print("_TxInput__value_sats:", utxo._TxInput__value_sats)
print(f"---utxo-end {name}---") print(f"---utxo-end {name}---")
@staticmethod
def print_prevout(prevout, name=""): def print_prevout(prevout, name=""):
print(f"---prevout-{name}---") print(f"---prevout-{name}---")
Util.print_var(prevout, f"{name}-prevout") Util.print_var(prevout, f"{name}-prevout")
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: "ElectrumWindow", title, exporter):
filter_ = "All files (*)"
filename = getSaveFileName(
parent=electrum_window,
title=_("Select file to save your {}").format(title),
filename="BALplugin_{}".format(title),
filter=filter_,
config=electrum_window.config,
)
if not filename:
return
try:
exporter(filename)
except FileExportFailed as e:
electrum_window.show_critical(str(e))
else:
electrum_window.show_message(
_("Your {0} were exported to '{1}'").format(title, str(filename))
)
@staticmethod
def copy(dicto, dictfrom): def copy(dicto, dictfrom):
for k, v in dictfrom.items(): for k, v in dictfrom.items():
dicto[k] = v dicto[k] = v
@staticmethod
def fix_will_settings_tx_fees(will_settings): def fix_will_settings_tx_fees(will_settings):
tx_fees = will_settings.get("tx_fees", False) tx_fees = will_settings.get("tx_fees", False)
have_to_update = False have_to_update = False
@@ -489,6 +505,7 @@ class Util:
have_to_update = True have_to_update = True
return have_to_update return have_to_update
@staticmethod
def fix_will_tx_fees(will): def fix_will_tx_fees(will):
have_to_update = False have_to_update = False
for txid, willitem in will.items(): for txid, willitem in will.items():
@@ -498,3 +515,19 @@ class Util:
del will[txid]["tx_fees"] del will[txid]["tx_fees"]
have_to_update = True have_to_update = True
return have_to_update return have_to_update
@staticmethod
def text_to_hex(text: str) -> str:
"""Convert text to hexadecimal string"""
hex_string = text.encode('utf-8').hex()
return hex_string
@staticmethod
def hex_to_text(hex_string: str) -> str:
"""Convert hexadecimal string back to text (for verification)"""
try:
return bytes.fromhex(hex_string).decode('utf-8')
except Exception:
return "Error: Invalid hex string"

50
wallet_util/README.md Normal file
View File

@@ -0,0 +1,50 @@
## README
### Overview
This tool provides two entry points: a CLI script (bal_wallet_utils.py) and a Qt GUI script (bal_wallet_utils_qt.py) that operate against an Electrum source tree.
### Installation / Preparation
1. Copy both files into the Electrum project root (the folder that contains the Electrum source package):
- bal_wallet_utils.py
- bal_wallet_utils_qt.py
2. Activate the Electrum Python environment (the virtualenv used to run Electrum). Example (PowerShell, adjust path to your venv):
```
.\env\Scripts\Activate.ps1
```
or (cmd):
```
env\Scripts\activate.bat
```
### Running
- CLI version:
```
python bal_wallet_utils.py
```
- Qt GUI version:
```
python bal_wallet_utils_qt.py
```
### Building a Windows executable with PyInstaller
From the project root (with the Electrum environment active), you can build the Qt executable using PyInstaller. Example command (adjust the paths if your environment path differs):
```
pyinstaller.exe --onefile --noconsole --add-data "electrum\currencies.json;electrum" --add-data "electrum\bip39_wallet_formats.json;electrum" --add-data "electrum\lnwire\peer_wire.csv;electrum\lnwire" --add-data "electrum\lnwire\onion_wire.csv;electrum\lnwire" --add-binary "env/Lib/site-packages\electrum_ecc\libsecp256k1-6.dll;electrum_ecc" bal_wallet_utils_qt.py
```
Notes:
- Run the command from the project root so relative paths resolve correctly.
- On Windows the --add-data and --add-binary arguments use ";" to separate source and destination.
- If electrum expects additional data files or native DLLs, include them with additional --add-data / --add-binary flags.
- For debugging include --onedir first to inspect the created folder before using --onefile.
### Troubleshooting
- If PyInstaller is not found, run it via Python:
```
python -m PyInstaller <same arguments>
```
- If the frozen exe fails because DLLs or JSON files are missing, add those files explicitly with --add-data or --add-binary.
- Test the build on a clean Windows VM to ensure all runtime dependencies are included.
License and attribution: include your preferred license or attribution details here.

View File

@@ -1,10 +1,11 @@
#!env/bin/python3 #!env/bin/python3
import getpass
import json
import os
import sys
from electrum.storage import WalletStorage from electrum.storage import WalletStorage
from electrum.util import MyEncoder from electrum.util import MyEncoder
import json
import sys
import getpass
import os
default_fees = 100 default_fees = 100
@@ -26,9 +27,12 @@ def fix_will_settings_tx_fees(json_wallet):
def uninstall_bal(json_wallet): def uninstall_bal(json_wallet):
del json_wallet["will_settings"] if "will_settings" in json_wallet:
del json_wallet["will"] del json_wallet["will_settings"]
del json_wallet["heirs"] if "will" in json_wallet:
del json_wallet["will"]
if "heirs" in json_wallet:
del json_wallet["heirs"]
return True return True
@@ -47,12 +51,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 password == False: if not password:
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 return json_wallet, storage
if __name__ == "__main__": if __name__ == "__main__":
@@ -65,7 +69,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 = read_wallet(path) json_wallet, storage = 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

@@ -1,32 +1,31 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys
import os
import json import json
import os
import sys
from bal_wallet_utils import fix_will_settings_tx_fees, save, uninstall_bal
from electrum.storage import WalletStorage
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QApplication,
QMainWindow, QFileDialog,
QVBoxLayout, QGroupBox,
QHBoxLayout, QHBoxLayout,
QLabel, QLabel,
QLineEdit, QLineEdit,
QMainWindow,
QPushButton, QPushButton,
QWidget,
QFileDialog,
QGroupBox,
QTextEdit, QTextEdit,
QVBoxLayout,
QWidget,
) )
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
class WalletUtilityGUI(QMainWindow): class WalletUtilityGUI(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.initUI() self.init_ui()
def initUI(self): def init_ui(self):
self.setWindowTitle("BAL Wallet Utility") self.setWindowTitle("BAL Wallet Utility")
self.setFixedSize(500, 400) self.setFixedSize(500, 400)
@@ -190,14 +189,6 @@ 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()

194
will.py
View File

@@ -12,11 +12,7 @@ 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
@@ -28,7 +24,7 @@ _logger = get_logger(__name__)
class Will: class Will:
# return an array with the list of children @staticmethod
def get_children(will, willid): def get_children(will, willid):
out = [] out = []
for _id in will: for _id in will:
@@ -40,6 +36,7 @@ class Will:
return out return out
# build a tree with parent transactions # build a tree with parent transactions
@staticmethod
def add_willtree(will): def add_willtree(will):
for willid in will: for willid in will:
will[willid].children = Will.get_children(will, willid) will[willid].children = Will.get_children(will, willid)
@@ -48,14 +45,17 @@ class Will:
will[child[0]].father = willid will[child[0]].father = willid
# return a list of will sorted by locktime # return a list of will sorted by locktime
@staticmethod
def get_sorted_will(will): def get_sorted_will(will):
return sorted(will.items(), key=lambda x: x[1]["tx"].locktime) return sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
@staticmethod
def only_valid(will): def only_valid(will):
for k, v in will.items(): for k, v in will.items():
if v.get_status("VALID"): if v.get_status("VALID"):
yield k yield k
@staticmethod
def search_equal_tx(will, tx, wid): def search_equal_tx(will, tx, wid):
for w in will: for w in will:
if w != wid and not tx.to_json() != will[w]["tx"].to_json(): if w != wid and not tx.to_json() != will[w]["tx"].to_json():
@@ -64,6 +64,7 @@ class Will:
return will[w]["tx"] return will[w]["tx"]
return False return False
@staticmethod
def get_tx_from_any(x): def get_tx_from_any(x):
try: try:
a = str(x) a = str(x)
@@ -74,6 +75,7 @@ class Will:
return x return x
@staticmethod
def add_info_from_will(will, wid, wallet): def add_info_from_will(will, wid, wallet):
if isinstance(will[wid].tx, str): if isinstance(will[wid].tx, str):
will[wid].tx = Will.get_tx_from_any(will[wid].tx) will[wid].tx = Will.get_tx_from_any(will[wid].tx)
@@ -82,13 +84,11 @@ class Will:
for txin in will[wid].tx.inputs(): for txin in will[wid].tx.inputs():
txid = txin.prevout.txid.hex() txid = txin.prevout.txid.hex()
if txid in will: if txid in will:
# print(will[txid].tx.outputs())
# print(txin.prevout.out_idx)
change = will[txid].tx.outputs()[txin.prevout.out_idx] change = will[txid].tx.outputs()[txin.prevout.out_idx]
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: except Exception:
pass pass
txin.is_mine = True txin.is_mine = True
txin._TxInput__address = change.address txin._TxInput__address = change.address
@@ -96,7 +96,9 @@ class Will:
txin._TxInput__value_sats = change.value txin._TxInput__value_sats = change.value
txin._trusted_value_sats = change.value txin._trusted_value_sats = change.value
def normalize_will(will, wallet=None, others_inputs={}): @staticmethod
def normalize_will(will, wallet=None, others_inputs=None):
others_input = others_inputs if others_inputs is not None else {}
to_delete = [] to_delete = []
to_add = {} to_add = {}
# add info from wallet # add info from wallet
@@ -135,8 +137,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
@@ -145,6 +147,7 @@ class Will:
if wid in will: if wid in will:
del will[wid] del will[wid]
@staticmethod
def new_input(txid, idx, change): def new_input(txid, idx, change):
prevout = TxOutpoint(txid=bfh(txid), out_idx=idx) prevout = TxOutpoint(txid=bfh(txid), out_idx=idx)
inp = PartialTxInput(prevout=prevout) inp = PartialTxInput(prevout=prevout)
@@ -155,6 +158,7 @@ class Will:
inp._TxInput__value_sats = change.value inp._TxInput__value_sats = change.value
return inp return inp
@staticmethod
def check_anticipate(ow: "WillItem", nw: "WillItem"): def check_anticipate(ow: "WillItem", nw: "WillItem"):
anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1) anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1)
if int(nw.tx.locktime) >= int(anticipate): if int(nw.tx.locktime) >= int(anticipate):
@@ -184,6 +188,7 @@ class Will:
return anticipate return anticipate
return 4294967295 + 1 return 4294967295 + 1
@staticmethod
def change_input(will, otxid, idx, change, others_inputs, to_delete, to_append): def change_input(will, otxid, idx, change, others_inputs, to_delete, to_append):
ow = will[otxid] ow = will[otxid]
ntxid = ow.tx.txid() ntxid = ow.tx.txid()
@@ -194,7 +199,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
@@ -205,7 +210,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 == True: if found:
pass pass
new_txid = will[wid].tx.txid() new_txid = will[wid].tx.txid()
@@ -224,6 +229,7 @@ class Will:
to_append, to_append,
) )
@staticmethod
def get_all_inputs(will, only_valid=False): def get_all_inputs(will, only_valid=False):
all_inputs = {} all_inputs = {}
for w, wi in will.items(): for w, wi in will.items():
@@ -232,12 +238,13 @@ 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 not prevout_str in all_inputs: if prevout_str not 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)
return all_inputs return all_inputs
@staticmethod
def get_all_inputs_min_locktime(all_inputs): def get_all_inputs_min_locktime(all_inputs):
all_inputs_min_locktime = {} all_inputs_min_locktime = {}
@@ -245,20 +252,21 @@ 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 not i in all_inputs_min_locktime: if i not 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)
return all_inputs_min_locktime return all_inputs_min_locktime
@staticmethod
def search_anticipate_rec(will, old_inputs): def search_anticipate_rec(will, old_inputs):
redo = False redo = False
to_delete = [] to_delete = []
to_append = {} to_append = {}
new_inputs = Will.get_all_inputs(will, only_valid=True) new_inputs = Will.get_all_inputs(will, only_valid=True)
for nid, nwi in will.items(): 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(): if nid != nwi.tx.txid():
redo = True redo = True
to_delete.append(nid) to_delete.append(nid)
@@ -268,25 +276,37 @@ class Will:
Will.change_input( Will.change_input(
will, nid, i, outputs[i], new_inputs, to_delete, to_append 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: for w in to_delete:
try: try:
del will[w] del will[w]
except: except Exception:
pass pass
for k, w in to_append.items(): for k, w in to_append.items():
will[k] = w will[k] = w
if redo: if redo:
Will.search_anticipate_rec(will, old_inputs) Will.search_anticipate_rec(will, old_inputs)
@staticmethod
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)
other_inputs = Will.get_all_inputs(old_will, {}) other_inputs = Will.get_all_inputs(old_will, {})
try: try:
Will.normalize_will(new_will, others_inputs=other_inputs) Will.normalize_will(new_will, others_inputs=other_inputs)
@@ -306,6 +326,7 @@ class Will:
else: else:
continue continue
@staticmethod
def get_higher_input_for_tx(will): def get_higher_input_for_tx(will):
out = {} out = {}
for wid in will: for wid in will:
@@ -319,21 +340,25 @@ class Will:
out[inp.prevout.to_str()] = inp out[inp.prevout.to_str()] = inp
return out return out
@staticmethod
def invalidate_will(will, wallet, fees_per_byte): def invalidate_will(will, wallet, fees_per_byte):
will_only_valid = Will.only_valid_list(will) will_only_valid = Will.only_valid_list(will)
inputs = Will.get_all_inputs(will_only_valid) inputs = Will.get_all_inputs(will_only_valid)
utxos = wallet.get_utxos() utxos = wallet.get_utxos()
filtered_inputs = [] filtered_inputs = []
prevout_to_spend = [] prevout_to_spend = []
current_height = Util.get_current_height(wallet.network)
for prevout_str, ws in inputs.items(): for prevout_str, ws in inputs.items():
for w in ws: for w in ws:
if not w[0] in filtered_inputs: if w[0] not in filtered_inputs:
filtered_inputs.append(w[0]) 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) prevout_to_spend.append(prevout_str)
balance = 0 balance = 0
utxo_to_spend = [] utxo_to_spend = []
for utxo in utxos: for utxo in utxos:
if utxo.is_coinbase_output() and utxo.block_height < current_height+100:
continue
utxo_str = utxo.prevout.to_str() utxo_str = utxo.prevout.to_str()
if utxo_str in prevout_to_spend: if utxo_str in prevout_to_spend:
balance += inputs[utxo_str][0][2].value_sats() balance += inputs[utxo_str][0][2].value_sats()
@@ -342,7 +367,7 @@ class Will:
change_addresses = wallet.get_change_addresses_for_new_transaction() change_addresses = wallet.get_change_addresses_for_new_transaction()
out = PartialTxOutput.from_address_and_value(change_addresses[0], balance) out = PartialTxOutput.from_address_and_value(change_addresses[0], balance)
out.is_change = True out.is_change = True
locktime = Util.get_current_height(wallet.network) locktime = current_height
tx = PartialTransaction.from_io( tx = PartialTransaction.from_io(
utxo_to_spend, [out], locktime=locktime, version=2 utxo_to_spend, [out], locktime=locktime, version=2
) )
@@ -367,13 +392,15 @@ class Will:
_logger.debug("len utxo_to_spend <=0") _logger.debug("len utxo_to_spend <=0")
pass pass
@staticmethod
def is_new(will): def is_new(will):
for wid, w in will.items(): for wid, w in will.items():
if w.get_status("VALID") and not w.get_status("COMPLETE"): if w.get_status("VALID") and not w.get_status("COMPLETE"):
return True return True
@staticmethod
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:
@@ -405,26 +432,35 @@ class Will:
else: else:
pass pass
@staticmethod
def utxos_strs(utxos): def utxos_strs(utxos):
return [Util.utxo_to_str(u) for u in utxos] return [Util.utxo_to_str(u) for u in utxos]
def set_invalidate(wid, will=[]): @staticmethod
def set_invalidate(wid, will=None):
will = will if will is not None else {}
will[wid].set_status("INVALIDATED", True) will[wid].set_status("INVALIDATED", True)
if will[wid].children: if will[wid].children:
for c in self.children.items(): for c in will[wid].children.items():
Will.set_invalidate(c[0], will) Will.set_invalidate(c[0], will)
@staticmethod
def check_tx_height(tx, wallet): def check_tx_height(tx, wallet):
info = wallet.get_tx_info(tx) info = wallet.get_tx_info(tx)
return info.tx_mined_status.height() return info.tx_mined_status.height()
# check if transactions are stil valid tecnically valid # check if transactions are stil valid tecnically valid
@staticmethod
def check_invalidated(willtree, utxos_list, wallet): def check_invalidated(willtree, utxos_list, wallet):
for wid, w in willtree.items(): for wid, w in willtree.items():
if not 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(): for inp in w.tx.inputs():
inp_str = Util.utxo_to_str(inp) inp_str = Util.utxo_to_str(inp)
if not inp_str in utxos_list: if inp_str not 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:
@@ -434,21 +470,22 @@ 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)
@staticmethod
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_heirs, fixed_amount, perc_heirs, perc_amount, fixed_amount_with_dust = (
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
@@ -470,6 +507,7 @@ class Will:
f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}" f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}"
) )
@staticmethod
def check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check): def check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check):
Will.add_willtree(will) Will.add_willtree(will)
utxos_list = Will.utxos_strs(all_utxos) utxos_list = Will.utxos_strs(all_utxos)
@@ -486,18 +524,28 @@ class Will:
Will.search_rai(all_inputs, all_utxos, will, wallet) Will.search_rai(all_inputs, all_utxos, will, wallet)
@staticmethod
def get_min_locktime(will,default_value=None):
return min((v.tx.locktime for v in will.values() if v.get_status('VALID')), default=default_value)
@staticmethod
def is_will_valid( def is_will_valid(
will, will,
block_to_check, block_to_check,
timestamp_to_check, timestamp_to_check,
tx_fees, tx_fees,
all_utxos, all_utxos,
heirs={}, heirs=None,
willexecutors={}, willexecutors=None,
self_willexecutor=False, self_willexecutor=False,
wallet=False, wallet=False,
callback_not_valid_tx=None, callback_not_valid_tx=None,
): ):
heirs = heirs if heirs is not None else {}
willexecutors= willexecutors if willexecutors is not None else {}
Will.check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check) Will.check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check)
if heirs: if heirs:
if not Will.check_willexecutors_and_heirs( if not Will.check_willexecutors_and_heirs(
@@ -526,6 +574,7 @@ class Will:
_logger.info("will ok") _logger.info("will ok")
return True return True
@staticmethod
def check_will_expired(all_inputs_min_locktime, block_to_check, timestamp_to_check): def check_will_expired(all_inputs_min_locktime, block_to_check, timestamp_to_check):
_logger.info("check if some transaction is expired") _logger.info("check if some transaction is expired")
for prevout_str, wid in all_inputs_min_locktime.items(): for prevout_str, wid in all_inputs_min_locktime.items():
@@ -542,18 +591,22 @@ class Will:
raise WillExpiredException( raise WillExpiredException(
f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}" f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}"
) )
else:
from datetime import datetime
_logger.debug(f"Will Not Expired {wid[0][0]}: {datetime.fromtimestamp(locktime).isoformat()} > {datetime.fromtimestamp(timestamp_to_check).isoformat()}")
def check_all_input_spent_are_in_wallet(): # 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)
@staticmethod
def only_valid_list(will): def only_valid_list(will):
out = {} out = {}
for wid, w in will.items(): for wid, w in will.items():
@@ -561,6 +614,7 @@ class Will:
out[wid] = w out[wid] = w
return out return out
@staticmethod
def only_valid_or_replaced_list(will): def only_valid_or_replaced_list(will):
out = [] out = []
for wid, w in will.items(): for wid, w in will.items():
@@ -569,6 +623,7 @@ class Will:
out.append(wid) out.append(wid)
return out return out
@staticmethod
def check_willexecutors_and_heirs( def check_willexecutors_and_heirs(
will, heirs, willexecutors, self_willexecutor, check_date, tx_fees will, heirs, willexecutors, self_willexecutor, check_date, tx_fees
): ):
@@ -582,7 +637,7 @@ class Will:
for wid in Will.only_valid_list(will): for wid in Will.only_valid_list(will):
w = will[wid] w = will[wid]
if w.tx_fees != tx_fees: if w.tx_fees != tx_fees:
raise TxFeesChangedException(f"{tx_fees}:", w.tx_fees) raise TxFeesChangedException(f"{tx_fees}: {w.tx_fees}")
for wheir in w.heirs: for wheir in w.heirs:
if not 'w!ll3x3c"' == wheir[:9]: if not 'w!ll3x3c"' == wheir[:9]:
their = will[wid].heirs[wheir] their = will[wid].heirs[wheir]
@@ -598,9 +653,9 @@ class Will:
heirs_found[wheir] = count + 1 heirs_found[wheir] = count + 1
else: else:
_logger.debug( _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: 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(
@@ -612,27 +667,27 @@ 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 not h in heirs_found: if h not 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 not url in willexecutors_found: if url not 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")
return True return True
class WillItem(Logger):
class WillItem(Logger):
STATUS_DEFAULT = { STATUS_DEFAULT = {
"ANTICIPATED": ["Anticipated", False], "ANTICIPATED": ["Anticipated", False],
"BROADCASTED": ["Broadcasted", False], "BROADCASTED": ["Broadcasted", False],
@@ -654,15 +709,15 @@ class WillItem(Logger):
} }
def set_status(self, status, value=True): def set_status(self, status, value=True):
#_logger.trace( # _logger.trace(
# "set status {} - {} {} -> {}".format( # "set status {} - {} {} -> {}".format(
# self._id, status, self.STATUS[status][1], value # self._id, status, self.STATUS[status][1], value
# ) # )
#) # )
if self.STATUS[status][1] == bool(value): if self.STATUS[status][1] == bool(value):
return None 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) self.STATUS[status][1] = bool(value)
if value: if value:
if status in ["INVALIDATED", "REPLACED", "CONFIRMED", "PENDING"]: if status in ["INVALIDATED", "REPLACED", "CONFIRMED", "PENDING"]:
@@ -776,9 +831,9 @@ class WillItem(Logger):
iw = inp[1] iw = inp[1]
self.set_anticipate(iw) self.set_anticipate(iw)
def check_willexecutor(self): def set_check_willexecutor(self,resp):
try: try:
if resp := Willexecutors.check_transaction(self._id, self.we["url"]): if resp :
if "tx" in resp and resp["tx"] == str(self.tx): if "tx" in resp and resp["tx"] == str(self.tx):
self.set_status("PUSHED") self.set_status("PUSHED")
self.set_status("CHECKED") self.set_status("CHECKED")
@@ -818,7 +873,12 @@ class WillItem(Logger):
class WillException(Exception): class WillException(Exception):
pass def __init__(self,msg="WillException"):
self.msg=msg
Exception.__init__(self)
def __str__(self):
return self.msg
class WillExpiredException(WillException): class WillExpiredException(WillException):
@@ -855,8 +915,6 @@ class WillExecutorNotPresent(NotCompleteWillException):
class NoHeirsException(WillException): class NoHeirsException(WillException):
pass pass
class AmountException(WillException): class AmountException(WillException):
pass pass

View File

@@ -1,33 +1,38 @@
import json import json
import time
from datetime import datetime from datetime import datetime
from functools import partial
from aiohttp import ClientResponse from aiohttp import ClientResponse
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 = BalPlugin.chainname
class Willexecutors: class Willexecutors:
@staticmethod
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[constants.net.NET_NAME] = willexecutors aw[chainname] = willexecutors
bal_plugin.WILLEXECUTORS.set(aw) bal_plugin.WILLEXECUTORS.set(aw)
_logger.debug(f"saved: {aw}")
# bal_plugin.WILLEXECUTORS.set(willexecutors)
@staticmethod
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(constants.net.NET_NAME, {}) willexecutors = willexecutors.get(chainname, {})
to_del = [] to_del = []
for w in willexecutors: for w in willexecutors:
if not isinstance(willexecutors[w], dict): if not isinstance(willexecutors[w], dict):
@@ -35,34 +40,38 @@ class Willexecutors:
continue continue
Willexecutors.initialize_willexecutor(willexecutors[w], w) Willexecutors.initialize_willexecutor(willexecutors[w], w)
for w in to_del: 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] 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(): 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") _logger.debug(f"force add {bal_url} willexecutor")
willexecutors[bal_url] = bal_executor willexecutors[bal_url] = bal_executor
if update: # if update:
found = False # found = False
for url, we in willexecutors.items(): # for url, we in willexecutors.items():
if Willexecutors.is_selected(we): # if Willexecutors.is_selected(we):
found = True # found = True
if found or force: # if found or force:
if bal_plugin.PING_WILLEXECUTORS.get() or force: # if bal_plugin.PING_WILLEXECUTORS.get() or force:
ping_willexecutors = True # ping_willexecutors = True
if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force: # if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force:
if bal_window: # if bal_window:
ping_willexecutors = bal_window.window.question( # ping_willexecutors = bal_window.window.question(
_( # _(
"Contact willexecutors servers to update payment informations?" # "Contact willexecutors servers to update payment informations?"
) # )
) # )
if ping_willexecutors: # if ping_willexecutors:
if task: # if task:
bal_window.ping_willexecutors(willexecutors, task) # bal_window.ping_willexecutors(willexecutors, task)
else: # else:
bal_window.ping_willexecutors_task(willexecutors) # bal_window.ping_willexecutors_task(willexecutors)
w_sorted = dict( w_sorted = dict(
sorted( sorted(
willexecutors.items(), key=lambda w: w[1].get("sort", 0), reverse=True willexecutors.items(), key=lambda w: w[1].get("sort", 0), reverse=True
@@ -70,17 +79,19 @@ class Willexecutors:
) )
return w_sorted return w_sorted
@staticmethod
def is_selected(willexecutor, value=None): def is_selected(willexecutor, value=None):
if not willexecutor: if not willexecutor:
return False return False
if not value is None: if value is not None:
willexecutor["selected"] = value willexecutor["selected"] = value
try: try:
return willexecutor["selected"] return willexecutor["selected"]
except: except Exception:
willexecutor["selected"] = False willexecutor["selected"] = False
return False return False
@staticmethod
def get_willexecutor_transactions(will, force=False): def get_willexecutor_transactions(will, force=False):
willexecutors = {} willexecutors = {}
for wid, willitem in will.items(): for wid, willitem in will.items():
@@ -90,7 +101,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 not url in willexecutors: if url not in willexecutors:
willexecutor["txs"] = "" willexecutor["txs"] = ""
willexecutor["txsids"] = [] willexecutor["txsids"] = []
willexecutor["broadcast_status"] = _("Waiting...") willexecutor["broadcast_status"] = _("Waiting...")
@@ -100,31 +111,35 @@ 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(willexecutor): # if Willexecutors.is_selected(url):
out[url] = v # out[url] = v
def push_transactions_to_willexecutors(will): # def push_transactions_to_willexecutors(will):
willexecutors = get_transactions_to_be_pushed() # willexecutors = 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(method, url, data=None, *, timeout=10): @staticmethod
def send_request(
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 ErrorConnectingServer("You are offline.") raise Exception("You are offline.")
_logger.debug(f"<-- {method} {url} {data}") _logger.debug(f"<-- {method} {url} {data}")
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(
@@ -132,7 +147,7 @@ class Willexecutors:
url, url,
params=data, params=data,
headers=headers, headers=headers,
on_finish=Willexecutors.handle_response, on_finish=handle_response,
timeout=timeout, timeout=timeout,
) )
elif method == "post": elif method == "post":
@@ -141,40 +156,64 @@ class Willexecutors:
url, url,
body=data, body=data,
headers=headers, headers=headers,
on_finish=Willexecutors.handle_response, on_finish=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
@staticmethod
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)
@staticmethod
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)
r["status"] = resp.status # url = Willexecutors.get_we_url_from_response(resp)
r["selected"] = Willexecutors.is_selected(willexecutor) # r["url"]= url
r["url"] = url # r["status"]=resp.status
except: except Exception as e:
_logger.debug(f"error handling response:{e}")
pass pass
return r return r
@staticmethod
class AlreadyPresentException(Exception): class AlreadyPresentException(Exception):
pass pass
@staticmethod
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['txs']")
if w := Willexecutors.send_request( if w := Willexecutors.send_request(
"post", "post",
willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs", willexecutor["url"] + "/" + chainname + "/pushtxs",
data=willexecutor["txs"].encode("ascii"), data=willexecutor["txs"].encode("ascii"),
): ):
willexecutor["broadcast_status"] = _("Success") willexecutor["broadcast_status"] = _("Success")
@@ -193,27 +232,25 @@ class Willexecutors:
return out return out
@staticmethod
def ping_servers(willexecutors): def ping_servers(willexecutors):
for url, we in willexecutors.items(): for url, we in willexecutors.items():
Willexecutors.get_info_task(url, we) Willexecutors.get_info_task(url, we)
@staticmethod
def get_info_task(url, willexecutor): def get_info_task(url, willexecutor):
w = None w = None
try: try:
_logger.info("GETINFO_WILLEXECUTOR") _logger.info("GETINFO_WILLEXECUTOR")
_logger.debug(url) _logger.debug(url)
netname = "bitcoin" w = Willexecutors.send_request("get", url + "/" + chainname + "/info")
if constants.net.NET_NAME != "mainnet": if isinstance(w, dict):
netname = constants.net.NET_NAME willexecutor["url"] = url
w = Willexecutors.send_request("get", url + "/" + netname + "/info") willexecutor["status"] = 200
willexecutor["base_fee"] = w["base_fee"]
willexecutor["url"] = url willexecutor["address"] = w["address"]
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['address']}") _logger.debug(f"response_data {w}")
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"
@@ -221,30 +258,44 @@ 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, selected=None): @staticmethod
def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor=None):
old_willexecutor=old_willexecutor if old_willexecutor is not None else {}
willexecutor["url"] = url willexecutor["url"] = url
if not status is None: if status is not None:
willexecutor["status"] = status 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):
@staticmethod
def download_list(old_willexecutors,welist_server):
try: try:
l = Willexecutors.send_request( welist_server = welist_server if welist_server[-1] == '/' else welist_server+'/'
"get", "https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100" willexecutors = Willexecutors.send_request(
"get",
f"{welist_server}data/{chainname}?page=0&limit=100",
) )
del l["status"] # del willexecutors["status"]
for w in l: for w in willexecutors:
willexecutor = l[w] if w not in ("status", "url"):
Willexecutors.initialize_willexecutor(willexecutor, w, "New", False) Willexecutors.initialize_willexecutor(
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 l return willexecutors
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}")
return {} return {}
def get_willexecutors_list_from_json(bal_plugin): @staticmethod
def get_willexecutors_list_from_json():
try: try:
with open("willexecutors.json") as f: with open("willexecutors.json") as f:
willexecutors = json.load(f) willexecutors = json.load(f)
@@ -252,12 +303,13 @@ 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 h return willexecutors
except Exception as e: except Exception as e:
_logger.error(f"error opening willexecutors json: {e}") _logger.error(f"error opening willexecutors json: {e}")
return {} return {}
@staticmethod
def check_transaction(txid, url): def check_transaction(txid, url):
_logger.debug(f"{url}:{txid}") _logger.debug(f"{url}:{txid}")
try: try:
@@ -269,14 +321,54 @@ 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
@staticmethod
def compute_id(willexecutor):
return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain"))
class WillExecutor:
def __init__(self, url, base_fee, chain, info, version):
self.url = url
self.base_fee = base_fee
self.chain = chain
self.info = info
self.version = version
def from_dict(d): #class WillExecutor:
we = WillExecutor(d["url"], d["base_fee"], d["chain"], d["info"], d["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):
# 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}"