4 Commits

Author SHA1 Message Date
eea2497340 version 2026-01-04 16:29:03 -04:00
436c4a1348 version 2026-01-04 16:24:24 -04:00
f083d6a8f6 version 2026-01-04 16:02:30 -04:00
da937f2c1b refactor-022b.diff 2026-01-04 10:01:37 -04:00
15 changed files with 1407 additions and 1635 deletions

View File

@@ -1 +1 @@
0.2.6
0.2.2c

191
bal.py
View File

@@ -1,142 +1,125 @@
import random
import os
import zipfile as zipfile_lib
# import random
# import zipfile as zipfile_lib
from electrum import json_db
from electrum.logging import get_logger
from electrum.plugin import BasePlugin
from electrum import json_db
from electrum.transaction import tx_from_any
import os
def get_will_settings(x):
# print(x)
pass
json_db.register_dict("heirs", tuple, None)
json_db.register_dict("will", dict, None)
json_db.register_dict("will_settings", lambda x: x, None)
print(x)
json_db.register_dict('heirs', tuple, None)
json_db.register_dict('will', dict,None)
json_db.register_dict('will_settings', lambda x:x,None)
#{'rubiconda': ['bcrt1qgv0wu4v6kjzef5mnxfh2m9z6y7mez0ja0tt8mu', '45%', '1y'], 'veronica': ['bcrt1q6vxuvwrt8x5c9u9u29y5uq7frscr0vgc2dy60j', '15%', '1y']}
from electrum.logging import get_logger
def get_will_settings(x):
print(x)
def get_will(x):
try:
x["tx"] = tx_from_any(x["tx"])
x['tx']=tx_from_any(x['tx'])
except Exception as e:
raise e
return x
class BalConfig:
class BalConfig():
def __init__(self, config, name, default):
print("init bal_config")
self.config = config
self.name = name
self.default = default
def get(self, default=None):
def get(self,default=None):
v = self.config.get(self.name, default)
if v is None:
if default is not None:
if not default is None:
v = default
else:
v = self.default
return v
def set(self, value, save=True):
self.config.set_key(self.name, value, save=save)
def set(self,value,save=True):
self.config.set_key(self.name,value,save=save)
class BalPlugin(BasePlugin):
LATEST_VERSION = "1"
KNOWN_VERSIONS = ("0", "1")
LATEST_VERSION = '1'
KNOWN_VERSIONS = ('0', '1')
assert LATEST_VERSION in KNOWN_VERSIONS
def version():
try:
f = ""
with open("VERSION", "r") as fi:
f = str(fi.readline())
f=""
with open("VERSION","r") as f:
f = str(f.readline())
return f
except Exception:
except:
return "unknown"
SIZE = (159, 97)
def __init__(self, parent, config, name):
print("init bal_plugin")
self.logger = get_logger(__name__)
BasePlugin.__init__(self, parent, config, name)
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]
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
sys.path.insert(0, zipfile)
#print("sono state listate?")
self.parent = parent
self.config = config
self.name = name
self.ASK_BROADCAST = BalConfig(config, "bal_ask_broadcast", True)
self.BROADCAST = BalConfig(config, "bal_broadcast", True)
self.LOCKTIME_TIME = BalConfig(config, "bal_locktime_time", 90)
self.LOCKTIME_BLOCKS = BalConfig(config, "bal_locktime_blocks", 144 * 90)
self.LOCKTIMEDELTA_TIME = BalConfig(config, "bal_locktimedelta_time", 7)
self.LOCKTIMEDELTA_BLOCKS = BalConfig(
config, "bal_locktimedelta_blocks", 144 * 7
)
self.ENABLE_MULTIVERSE = BalConfig(config, "bal_enable_multiverse", False)
self.TX_FEES = BalConfig(config, "bal_tx_fees", 100)
self.INVALIDATE = BalConfig(config, "bal_invalidate", True)
self.ASK_INVALIDATE = BalConfig(config, "bal_ask_invalidate", True)
self.PREVIEW = BalConfig(config, "bal_preview", True)
self.SAVE_TXS = BalConfig(config, "bal_save_txs", True)
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", True)
# self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True)
# self.ASK_PING_WILLEXECUTORS = BalConfig(
# config, "bal_ask_ping_willexecutors", True
# )
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True)
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True)
self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True)
self.WILLEXECUTORS = BalConfig(
config,
"bal_willexecutors",
{
"mainnet": {
"https://we.bitcoin-after.life": {
"base_fee": 100000,
"status": "New",
"info": "Bitcoin After Life Will Executor",
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected": True,
}
},
"testnet": {
"https://we.bitcoin-after.life": {
"base_fee": 100000,
"status": "New",
"info": "Bitcoin After Life Will Executor",
"address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected": True,
}
},
},
)
self.WILL_SETTINGS = BalConfig(
config,
"bal_will_settings",
{
"baltx_fees": 100,
"threshold": "180d",
"locktime": "1y",
},
)
self.ASK_BROADCAST = BalConfig(config, "bal_ask_broadcast", True)
self.BROADCAST = BalConfig(config, "bal_broadcast", True)
self.LOCKTIME_TIME = BalConfig(config, "bal_locktime_time", 90)
self.LOCKTIME_BLOCKS = BalConfig(config, "bal_locktime_blocks", 144*90)
self.LOCKTIMEDELTA_TIME = BalConfig(config, "bal_locktimedelta_time", 7)
self.LOCKTIMEDELTA_BLOCKS = BalConfig(config, "bal_locktimedelta_blocks", 144*7)
self.ENABLE_MULTIVERSE = BalConfig(config, "bal_enable_multiverse", False)
self.TX_FEES = BalConfig(config, "bal_tx_fees", 100)
self.INVALIDATE = BalConfig(config, "bal_invalidate", True)
self.ASK_INVALIDATE = BalConfig(config, "bal_ask_invalidate", True)
self.PREVIEW = BalConfig(config, "bal_preview", True)
self.SAVE_TXS = BalConfig(config, "bal_save_txs", True)
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", True)
self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True)
self.ASK_PING_WILLEXECUTORS = BalConfig(config, "bal_ask_ping_willexecutors", True)
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True)
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True)
self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True)
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", {
"mainnet": {
'https://we.bitcoin-after.life': {
"base_fee": 100000,
"status": "New",
"info":"Bitcoin After Life Will Executor",
"address":"bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected":True
}
}
})
self.WILL_SETTINGS = BalConfig(config, "bal_will_settings", {
'baltx_fees':100,
'threshold':'180d',
'locktime':'1y',
})
self._hide_invalidated = self.HIDE_INVALIDATED.get()
self._hide_replaced = self.HIDE_REPLACED.get()
def resource_path(self, *parts):
self._hide_invalidated= self.HIDE_INVALIDATED.get()
self._hide_replaced= self.HIDE_REPLACED.get()
def resource_path(self,*parts):
return os.path.join(self.plugin_dir, *parts)
def hide_invalidated(self):
@@ -147,14 +130,20 @@ class BalPlugin(BasePlugin):
self._hide_replaced = not self._hide_replaced
self.HIDE_REPLACED.set(self._hide_replaced)
def validate_will_settings(self, will_settings):
if int(will_settings.get("baltx_fees", 1)) < 1:
will_settings["baltx_fees"] = 1
if not will_settings.get("threshold"):
will_settings["threshold"] = "180d"
if not will_settings.get("locktime"):
will_settings["locktime"] = "1y"
def validate_will_settings(self,will_settings):
print(type(will_settings))
print(will_settings.get('baltx_fees',1),1)
if int(will_settings.get('baltx_fees',1))<1:
will_settings['baltx_fees']=1
if not will_settings.get('threshold'):
will_settings['threshold']='180d'
if not will_settings.get('locktime')=='':
will_settings['locktime']='1y'
return will_settings
def default_will_settings(self):
return {"baltx_fees": 100, "threshold": "180d", "locktime": "1y"}
return {
'baltx_fees':100,
'threshold':'180d',
'locktime':'1y'
}

View File

@@ -1,14 +1,15 @@
import os
PLUGIN_DIR = os.path.split(os.path.realpath(__file__))[0]
DEFAULT_ICON = "bal32x32.png"
DEFAULT_ICON_PATH = "icons"
DEFAULT_ICON = 'bal32x32.png'
DEFAULT_ICON_PATH = ''
def icon_path(icon_basename: str = DEFAULT_ICON):
path = resource_path(DEFAULT_ICON_PATH, icon_basename)
path = resource_path(DEFAULT_ICON_PATH,icon_basename)
return path
def resource_path(*parts):
return os.path.join(PLUGIN_DIR, *parts)

61
bal_wallet_utils.py Executable file
View File

@@ -0,0 +1,61 @@
#!env/bin/python3
#same as qt but for command line, useful if you are going to fix various wallet
#also easier to read the code
from electrum.storage import WalletStorage
from electrum.util import MyEncoder
import json
import sys
import getpass
import os
default_fees= 100
def fix_will_settings_tx_fees(json_wallet):
tx_fees = json_wallet.get('will_settings',{}).get('tx_fees',False)
if tx_fees:
json_wallet['will_settings']['baltx_fees']=json_wallet.get('will_settings',{}).get('tx_fees',default_fees)
del json_wallet['will_settings']['tx_fees']
return True
return False
def uninstall_bal(json_wallet):
del json_wallet['will_settings']
del json_wallet['will']
del json_wallet['heirs']
return True
def save(json_wallet,storage):
human_readable=not storage.is_encrypted()
storage.write(json.dumps(
json_wallet,
indent=4 if human_readable else None,
sort_keys=bool(human_readable),
cls=MyEncoder,
))
if __name__ == '__main__':
if len(sys.argv) <3:
print("usage: ./bal_wallet_utils <command> <wallet path>")
print("available commands: uninstall, fix")
exit(1)
if not os.path.exists(sys.argv[2]):
print("Error: wallet not found")
exit(1)
command = sys.argv[1]
path = sys.argv[2]
storage=WalletStorage(path)
if storage.is_encrypted():
password = getpass.getpass("Enter wallet password: ", stream = None)
storage.decrypt(password)
data=storage.read()
json_wallet=json.loads(data)
have_to_save=False
if command == 'fix':
have_to_save = fix_will_settings_tx_fees(json_wallet)
if command == 'uninstall':
have_to_save = uninstall_bal(json_wallet)
if have_to_save:
save(json_wallet,storage)
else:
print("nothing to do")

View File

@@ -1,57 +1,55 @@
#!/usr/bin/env python3
#this script will help to fix tx_fees wallet bug
#also added an uninstall button to remove any bal data from wallet
#still very work in progress.
#
#have to be executed from electrum source code directory in example /home/user/projects/electrum/
#
import sys
import os
import json
from PyQt6.QtWidgets import (
QApplication,
QMainWindow,
QVBoxLayout,
QHBoxLayout,
QLabel,
QLineEdit,
QPushButton,
QWidget,
QFileDialog,
QGroupBox,
QTextEdit,
)
from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QWidget, QFileDialog,
QGroupBox, QTextEdit)
from PyQt6.QtCore import Qt
from electrum.storage import WalletStorage
from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, save
from electrum.util import MyEncoder
default_fees = 100
class WalletUtilityGUI(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("BAL Wallet Utility")
self.setWindowTitle('BAL Wallet Utility')
self.setFixedSize(500, 400)
# Central widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Main layout
layout = QVBoxLayout(central_widget)
# Wallet input group
wallet_group = QGroupBox("Wallet Settings")
wallet_layout = QVBoxLayout(wallet_group)
# Wallet path
wallet_path_layout = QHBoxLayout()
wallet_path_layout.addWidget(QLabel("Wallet Path:"))
self.wallet_path_edit = QLineEdit()
self.wallet_path_edit.setPlaceholderText("Select wallet path...")
wallet_path_layout.addWidget(self.wallet_path_edit)
self.browse_btn = QPushButton("Browse...")
self.browse_btn.clicked.connect(self.browse_wallet)
wallet_path_layout.addWidget(self.browse_btn)
wallet_layout.addLayout(wallet_path_layout)
# Password
password_layout = QHBoxLayout()
password_layout.addWidget(QLabel("Password:"))
@@ -59,140 +57,169 @@ class WalletUtilityGUI(QMainWindow):
self.password_edit.setEchoMode(QLineEdit.EchoMode.Password)
self.password_edit.setPlaceholderText("Enter password (if encrypted)")
password_layout.addWidget(self.password_edit)
wallet_layout.addLayout(password_layout)
layout.addWidget(wallet_group)
# Output area
output_group = QGroupBox("Output")
output_layout = QVBoxLayout(output_group)
self.output_text = QTextEdit()
self.output_text.setReadOnly(True)
output_layout.addWidget(self.output_text)
layout.addWidget(output_group)
# Action buttons
buttons_layout = QHBoxLayout()
self.fix_btn = QPushButton("Fix")
self.fix_btn.clicked.connect(self.fix_wallet)
self.fix_btn.setEnabled(False)
buttons_layout.addWidget(self.fix_btn)
self.uninstall_btn = QPushButton("Uninstall")
self.uninstall_btn.clicked.connect(self.uninstall_wallet)
self.uninstall_btn.setEnabled(False)
buttons_layout.addWidget(self.uninstall_btn)
layout.addLayout(buttons_layout)
# Connections to enable buttons when path is entered
self.wallet_path_edit.textChanged.connect(self.check_inputs)
def browse_wallet(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "Select Wallet", "*", "Electrum Wallet (*)"
self,
"Select Wallet",
"",
"Electrum Wallet (*.dat)"
)
if file_path:
self.wallet_path_edit.setText(file_path)
def check_inputs(self):
wallet_path = self.wallet_path_edit.text().strip()
has_path = bool(wallet_path) and os.path.exists(wallet_path)
self.fix_btn.setEnabled(has_path)
self.uninstall_btn.setEnabled(has_path)
def log_message(self, message):
self.output_text.append(message)
def fix_will_settings_tx_fees(self, json_wallet):
tx_fees = json_wallet.get('will_settings',{}).get('tx_fees',False)
if tx_fees:
json_wallet['will_settings']['baltx_fees'] = json_wallet.get('will_settings',{}).get('tx_fees', default_fees)
del json_wallet['will_settings']['tx_fees']
return True
return False
def uninstall_bal(self, json_wallet):
if 'will_settings' in json_wallet:
del json_wallet['will_settings']
if 'will' in json_wallet:
del json_wallet['will']
if 'heirs' in json_wallet:
del json_wallet['heirs']
return True
def save_wallet(self, json_wallet, storage):
try:
human_readable = not storage.is_encrypted()
storage.write(json.dumps(
json_wallet,
indent=4 if human_readable else None,
sort_keys=bool(human_readable),
cls=MyEncoder,
))
return True
except Exception as e:
self.log_message(f"Save error: {str(e)}")
return False
def fix_wallet(self):
self.process_wallet("fix")
self.process_wallet('fix')
def uninstall_wallet(self):
self.log_message(
"WARNING: This will remove all BAL settings. This operation cannot be undone."
)
self.process_wallet("uninstall")
self.log_message("WARNING: This will remove all BAL settings. This operation cannot be undone.")
self.process_wallet('uninstall')
def process_wallet(self, command):
wallet_path = self.wallet_path_edit.text().strip()
password = self.password_edit.text()
if not wallet_path:
self.log_message("ERROR: Please enter wallet path")
return
if not os.path.exists(wallet_path):
self.log_message("ERROR: Wallet not found")
return
try:
self.log_message(f"Processing wallet: {wallet_path}")
storage = WalletStorage(wallet_path)
# Decrypt if necessary
if storage.is_encrypted():
if not password:
self.log_message(
"ERROR: Wallet is encrypted, please enter password"
)
self.log_message("ERROR: Wallet is encrypted, please enter password")
return
try:
storage.decrypt(password)
self.log_message("Wallet decrypted successfully")
except Exception as e:
self.log_message(f"ERROR: Wrong password: {str(e)}")
return
# Read wallet
data = storage.read()
json_wallet = json.loads("[" + data + "]")[0]
json_wallet = json.loads(data)
have_to_save = False
message = ""
if command == "fix":
have_to_save = fix_will_settings_tx_fees(json_wallet)
message = (
"Fix applied successfully" if have_to_save else "No fix needed"
)
elif command == "uninstall":
have_to_save = uninstall_bal(json_wallet)
message = (
"BAL uninstalled successfully"
if have_to_save
else "No BAL settings found to uninstall"
)
if command == 'fix':
have_to_save = self.fix_will_settings_tx_fees(json_wallet)
message = "Fix applied successfully" if have_to_save else "No fix needed"
elif command == 'uninstall':
have_to_save = self.uninstall_bal(json_wallet)
message = "BAL uninstalled successfully" if have_to_save else "No BAL settings found to uninstall"
if have_to_save:
try:
save(json_wallet, storage)
if self.save_wallet(json_wallet, storage):
self.log_message(f"SUCCESS: {message}")
except Exception as e:
self.log_message(f"Save error: {str(e)}")
else:
self.log_message("ERROR: Failed to save wallet")
else:
self.log_message(f"INFO: {message}")
except Exception as e:
error_msg = f"ERROR: Processing failed: {str(e)}"
self.log_message(error_msg)
def main():
app = QApplication(sys.argv)
# Check if dependencies are available
try:
from electrum.storage import WalletStorage
from electrum.util import MyEncoder
except ImportError as e:
print(f"ERROR: Cannot import Electrum dependencies: {str(e)}")
return 1
window = WalletUtilityGUI()
window.show()
return app.exec()
if __name__ == "__main__":
if __name__ == '__main__':
sys.exit(main())

293
heirs.py
View File

@@ -1,37 +1,24 @@
# import datetime
# import json
import math
# import datetime
# import urllib.request
# import urllib.parse
import random
import re
import threading
# import urllib.parse
# import urllib.request
from typing import (
TYPE_CHECKING,
Any,
Dict,
# List,
Optional,
# Sequence,
Tuple,
)
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple
import dns
from dns.exception import DNSException
from electrum import (
bitcoin,
constants,
# descriptor,
dnssec,
)
from electrum import bitcoin, constants, descriptor, dnssec
from electrum.logging import Logger, get_logger
from electrum.transaction import (
PartialTransaction,
PartialTxInput,
PartialTxOutput,
TxOutpoint,
# TxOutput,
TxOutput,
)
from electrum.util import (
bfh,
@@ -41,14 +28,12 @@ from electrum.util import (
write_json_file,
)
from .util import Util
from .util import *
from .willexecutors import Willexecutors
from electrum.util import BitcoinException
if TYPE_CHECKING:
from .simple_config import SimpleConfig
# from .wallet_db import WalletDB
from .wallet_db import WalletDB
_logger = get_logger(__name__)
@@ -57,7 +42,6 @@ HEIR_ADDRESS = 0
HEIR_AMOUNT = 1
HEIR_LOCKTIME = 2
HEIR_REAL_AMOUNT = 3
HEIR_DUST_AMOUNT = 4
TRANSACTION_LABEL = "inheritance transaction"
@@ -101,45 +85,38 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
x.value_sats(), x.prevout.txid, x.prevout.out_idx
),
)
# total_used_utxos = []
total_used_utxos = []
txsout = {}
locktime, _ = Util.get_lowest_locktimes(locktimes)
locktime, _ = get_lowest_locktimes(locktimes)
if not locktime:
_logger.info("prepare transactions, no locktime")
return
locktime = locktime[0]
heirs = locktimes[locktime]
true = True
while true:
true = False
vero = True
while vero:
vero = False
fee = fees.get(locktime, 0)
out_amount = fee
description = ""
outputs = []
paid_heirs = {}
for name, heir in heirs.items():
if len(heir) > HEIR_REAL_AMOUNT and "DUST" not in str(
heir[HEIR_REAL_AMOUNT]
):
try:
try:
if len(heir) > HEIR_REAL_AMOUNT:
real_amount = heir[HEIR_REAL_AMOUNT]
out_amount += real_amount
description += f"{name}\n"
paid_heirs[name] = heir
outputs.append(
PartialTxOutput.from_address_and_value(
heir[HEIR_ADDRESS], real_amount
)
)
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}")
else:
pass
paid_heirs[name] = heir
except Exception as e:
pass
in_amount = 0.0
used_utxos = []
@@ -148,29 +125,24 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
value = utxo.value_sats()
in_amount += value
used_utxos.append(utxo)
if in_amount >= out_amount:
if in_amount > out_amount:
break
except IndexError as e:
_logger.error(
f"error preparing transactions index error {e} {in_amount}, {out_amount}"
)
pass
if int(in_amount) < int(out_amount):
_logger.error(
"error preparing transactions in_amount < out_amount ({} < {}) "
)
continue
break
heirsvalue = out_amount
change = get_change_output(wallet, in_amount, out_amount, fee)
if change:
outputs.append(change)
for i in range(0, 100):
random.shuffle(outputs)
print(outputs)
tx = PartialTransaction.from_io(
used_utxos,
outputs,
locktime=Util.parse_locktime_string(locktime, wallet),
locktime=parse_locktime_string(locktime, wallet),
version=2,
)
if len(description) > 0:
@@ -214,7 +186,7 @@ def get_utxos_from_inputs(tx_inputs, tx, utxos):
# TODO calculate de minimum inputs to be invalidated
def invalidate_inheritance_transactions(wallet):
# listids = []
listids = []
utxos = {}
dtxs = {}
for k, v in wallet.get_all_labels().items():
@@ -243,7 +215,7 @@ def invalidate_inheritance_transactions(wallet):
for key, value in utxos:
for tx in value["txs"]:
txid = tx.txid()
if txid not in invalidated:
if not txid in invalidated:
invalidated.append(tx.txid())
remaining[key] = value
@@ -251,10 +223,10 @@ def invalidate_inheritance_transactions(wallet):
def print_transaction(heirs, tx, locktimes, tx_fees):
jtx = tx.to_json()
print(f"TX: {tx.txid()}\t-\tLocktime: {jtx['locktime']}")
print("---")
print(f"---")
for inp in jtx["inputs"]:
print(f"{inp['address']}: {inp['value_sats']}")
print("---")
print(f"---")
for out in jtx["outputs"]:
heirname = ""
for key in heirs.keys():
@@ -276,7 +248,7 @@ def print_transaction(heirs, tx, locktimes, tx_fees):
print()
try:
print(tx.serialize_to_network())
except Exception:
except:
print("impossible to serialize")
print()
@@ -291,15 +263,13 @@ def get_change_output(wallet, in_amount, out_amount, fee):
class Heirs(dict, Logger):
def __init__(self, wallet):
def __init__(self, db: "WalletDB"):
Logger.__init__(self)
self.db = wallet.db
self.wallet = wallet
self.db = db
d = self.db.get("heirs", {})
try:
self.update(d)
except Exception:
except e as Exception:
return
def invalidate_transactions(self, wallet):
@@ -330,10 +300,10 @@ class Heirs(dict, Logger):
def get_locktimes(self, from_locktime, a=False):
locktimes = {}
for key in self.keys():
locktime = Util.parse_locktime_string(self[key][HEIR_LOCKTIME])
locktime = parse_locktime_string(self[key][HEIR_LOCKTIME])
if locktime > from_locktime and not a or locktime <= from_locktime and a:
locktimes[int(locktime)] = None
return list(locktimes.keys())
return locktimes.keys()
def check_locktime(self):
return False
@@ -347,8 +317,6 @@ class Heirs(dict, Logger):
column = HEIR_AMOUNT
if real:
column = HEIR_REAL_AMOUNT
if "DUST" in str(v[column]):
column = HEIR_DUST_AMOUNT
value = int(
math.floor(
total_balance
@@ -359,10 +327,6 @@ class Heirs(dict, Logger):
if value > wallet.dust_threshold():
heir_list[key].insert(HEIR_REAL_AMOUNT, value)
amount += value
else:
heir_list[key].insert(HEIR_REAL_AMOUNT, f"DUST: {value}")
heir_list[key].insert(HEIR_DUST_AMOUNT, value)
_logger.info(f"{key}, {value} is dust will be ignored")
except Exception as e:
raise e
@@ -371,10 +335,10 @@ class Heirs(dict, Logger):
def amount_to_float(self, amount):
try:
return float(amount)
except Exception:
except:
try:
return float(amount[:-1])
except Exception:
except:
return 0.0
def fixed_percent_lists_amount(self, from_locktime, dust_threshold, reverse=False):
@@ -382,44 +346,25 @@ class Heirs(dict, Logger):
fixed_amount = 0.0
percent_heirs = {}
percent_amount = 0.0
fixed_amount_with_dust = 0.0
for key in self.keys():
try:
cmp = (
Util.parse_locktime_string(self[key][HEIR_LOCKTIME]) - from_locktime
)
cmp = parse_locktime_string(self[key][HEIR_LOCKTIME]) - from_locktime
if cmp <= 0:
_logger.debug(
"cmp < 0 {} {} {} {}".format(
cmp, key, self[key][HEIR_LOCKTIME], from_locktime
)
)
continue
if Util.is_perc(self[key][HEIR_AMOUNT]):
if is_perc(self[key][HEIR_AMOUNT]):
percent_amount += float(self[key][HEIR_AMOUNT][:-1])
percent_heirs[key] = list(self[key])
else:
heir_amount = int(math.floor(float(self[key][HEIR_AMOUNT])))
fixed_amount_with_dust += heir_amount
fixed_heirs[key] = list(self[key])
if heir_amount > dust_threshold:
fixed_amount += heir_amount
fixed_heirs[key] = list(self[key])
fixed_heirs[key].insert(HEIR_REAL_AMOUNT, heir_amount)
else:
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)
pass
except Exception as e:
_logger.error(e)
return (
fixed_heirs,
fixed_amount,
percent_heirs,
percent_amount,
fixed_amount_with_dust,
)
return fixed_heirs, fixed_amount, percent_heirs, percent_amount
def prepare_lists(
self, balance, total_fees, wallet, willexecutor=False, from_locktime=0
@@ -432,7 +377,7 @@ class Heirs(dict, Logger):
locktimes = self.get_locktimes(from_locktime)
if willexecutor:
for locktime in locktimes:
if int(Util.int_locktime(locktime)) > int(from_locktime):
if int(int_locktime(locktime)) > int(from_locktime):
try:
base_fee = int(willexecutor["base_fee"])
willexecutors_amount += base_fee
@@ -444,24 +389,19 @@ class Heirs(dict, Logger):
willexecutors[
'w!ll3x3c"' + willexecutor["url"] + '"' + str(locktime)
] = h
except Exception:
except Exception as e:
return [], False
else:
_logger.error(
f"heir excluded from will locktime({locktime}){Util.int_locktime(locktime)}<minimum{from_locktime}"
),
(
_logger.error(
f"heir excluded from will locktime({locktime}){int_locktime(locktime)}<minimum{from_locktime}"
),
)
heir_list.update(willexecutors)
newbalance -= willexecutors_amount
if newbalance < 0:
raise WillExecutorFeeException(willexecutor)
(
fixed_heirs,
fixed_amount,
percent_heirs,
percent_amount,
fixed_amount_with_dust,
) = self.fixed_percent_lists_amount(from_locktime, wallet.dust_threshold())
fixed_heirs, fixed_amount, percent_heirs, percent_amount = (
self.fixed_percent_lists_amount(from_locktime, wallet.dust_threshold())
)
if fixed_amount > newbalance:
fixed_amount = self.normalize_perc(
fixed_heirs, newbalance, fixed_amount, wallet
@@ -482,34 +422,33 @@ class Heirs(dict, Logger):
if newbalance > 0:
newbalance += fixed_amount
fixed_amount = self.normalize_perc(
fixed_heirs, newbalance, fixed_amount_with_dust, wallet, real=True
fixed_heirs, newbalance, fixed_amount, wallet, real=True
)
newbalance -= fixed_amount
heir_list.update(fixed_heirs)
heir_list = sorted(
heir_list.items(),
key=lambda item: Util.parse_locktime_string(item[1][HEIR_LOCKTIME], wallet),
key=lambda item: parse_locktime_string(item[1][HEIR_LOCKTIME], wallet),
)
locktimes = {}
for key, value in heir_list:
locktime = Util.parse_locktime_string(value[HEIR_LOCKTIME])
if locktime not in locktimes:
locktime = parse_locktime_string(value[HEIR_LOCKTIME])
if not locktime in locktimes:
locktimes[locktime] = {key: value}
else:
locktimes[locktime][key] = value
return locktimes, onlyfixed
def is_perc(self, key):
return Util.is_perc(self[key][HEIR_AMOUNT])
return is_perc(self[key][HEIR_AMOUNT])
def buildTransactions(
self, bal_plugin, wallet, tx_fees=None, utxos=None, from_locktime=0
):
Heirs._validate(self)
if len(self) <= 0:
_logger.info("while building transactions there was no heirs")
return
balance = 0.0
len_utxo_set = 0
@@ -525,7 +464,6 @@ class Heirs(dict, Logger):
len_utxo_set += 1
available_utxos.append(utxo)
if len_utxo_set == 0:
_logger.info("no usable utxos")
return
j = -2
willexecutorsitems = list(willexecutors.items())
@@ -549,66 +487,51 @@ class Heirs(dict, Logger):
break
fees = {}
i = 0
while i < 10:
while True:
txs = {}
redo = False
i += 1
total_fees = 0
for fee in fees:
total_fees += int(fees[fee])
# newbalance = balance
newbalance = balance
locktimes, onlyfixed = self.prepare_lists(
balance, total_fees, wallet, willexecutor, from_locktime
)
try:
locktimes, onlyfixed = self.prepare_lists(
balance, total_fees, wallet, willexecutor, from_locktime
txs = prepare_transactions(
locktimes, available_utxos[:], fees, wallet
)
except WillExecutorFeeException:
i = 10
continue
if locktimes:
if not txs:
return {}
except Exception as e:
try:
txs = prepare_transactions(
locktimes, available_utxos[:], fees, wallet
)
if not txs:
return {}
except Exception as e:
_logger.error(
f"build transactions: error preparing transactions: {e}"
)
try:
if "w!ll3x3c" in e.heirname:
Willexecutors.is_selected(
e.heirname[len("w!ll3x3c") :], False
)
break
except Exception:
raise e
total_fees = 0
total_fees_real = 0
total_in = 0
for txid, tx in txs.items():
tx.willexecutor = willexecutor
fee = tx.estimated_size() * tx_fees
txs[txid].tx_fees = tx_fees
total_fees += fee
total_fees_real += tx.get_fee()
total_in += tx.input_value()
rfee = tx.input_value() - tx.output_value()
if rfee < fee or rfee > fee + wallet.dust_threshold():
redo = True
# oldfees = fees.get(tx.my_locktime, 0)
fees[tx.my_locktime] = fee
if balance - total_in > wallet.dust_threshold():
if "w!ll3x3c" in e.heirname:
Willexecutors.is_selected(willexecutors[w], False)
break
except:
raise e
total_fees = 0
total_fees_real = 0
total_in = 0
for txid, tx in txs.items():
tx.willexecutor = willexecutor
fee = tx.estimated_size() * tx_fees
txs[txid].tx_fees = tx_fees
total_fees += fee
total_fees_real += tx.get_fee()
total_in += tx.input_value()
rfee = tx.input_value() - tx.output_value()
if rfee < fee or rfee > fee + wallet.dust_threshold():
redo = True
if not redo:
break
if i >= 10:
break
else:
_logger.info(
f"no locktimes for willexecutor {willexecutor} skipped"
)
oldfees = fees.get(tx.my_locktime, 0)
fees[tx.my_locktime] = fee
if balance - total_in > wallet.dust_threshold():
redo = True
if not redo:
break
if i >= 10:
break
alltxs.update(txs)
@@ -709,13 +632,13 @@ class Heirs(dict, Logger):
return None
def validate_address(address):
if not bitcoin.is_address(address, net=constants.net):
if not bitcoin.is_address(address):
raise NotAnAddress(f"not an address,{address}")
return address
def validate_amount(amount):
try:
famount = float(amount[:-1]) if Util.is_perc(amount) else float(amount)
famount = float(amount[:-1]) if is_perc(amount) else float(amount)
if famount <= 0.00000001:
raise AmountNotValid(f"amount have to be positive {famount} < 0")
except Exception as e:
@@ -725,7 +648,7 @@ class Heirs(dict, Logger):
def validate_locktime(locktime, timestamp_to_check=False):
try:
if timestamp_to_check:
if Util.parse_locktime_string(locktime, None) < timestamp_to_check:
if parse_locktime_string(locktime, None) < timestamp_to_check:
raise HeirExpiredException()
except Exception as e:
raise LocktimeNotValid(f"locktime string not properly formatted, {e}")
@@ -738,14 +661,12 @@ class Heirs(dict, Logger):
return (address, amount, locktime)
def _validate(data, timestamp_to_check=False):
for k, v in list(data.items()):
if k == "heirs":
return Heirs._validate(v, timestamp_to_check)
return Heirs._validate(v)
try:
Heirs.validate_heir(k, v, timestamp_to_check)
Heirs.validate_heir(k, v)
except Exception as e:
_logger.info(f"exception heir removed {e}")
data.pop(k)
return data
@@ -764,21 +685,3 @@ class LocktimeNotValid(ValueError):
class HeirExpiredException(LocktimeNotValid):
pass
class HeirAmountIsDustException(Exception):
pass
class NoHeirsException(Exception):
pass
class WillExecutorFeeException(Exception):
def __init__(self, willexecutor):
self.willexecutor = willexecutor
def __str__(self):
return "WillExecutorFeeException: {} fee:{}".format(
self.willexecutor["url"], self.willexecutor["base_fee"]
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

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

859
qt.py

File diff suppressed because it is too large Load Diff

942
util.py
View File

@@ -9,488 +9,504 @@ from electrum.util import FileExportFailed
LOCKTIME_THRESHOLD = 500000000
class Util:
def locktime_to_str(locktime):
try:
locktime = int(locktime)
if locktime > LOCKTIME_THRESHOLD:
dt = datetime.fromtimestamp(locktime).isoformat()
return dt
except Exception:
pass
return str(locktime)
def str_to_locktime(locktime):
try:
if locktime[-1] in ("y", "d", "b"):
return locktime
else:
return int(locktime)
except Exception:
pass
dt_object = datetime.fromisoformat(locktime)
timestamp = dt_object.timestamp()
return int(timestamp)
def parse_locktime_string(locktime, w=None):
try:
return int(locktime)
except Exception:
pass
try:
now = datetime.now()
if locktime[-1] == "y":
locktime = str(int(locktime[:-1]) * 365) + "d"
if locktime[-1] == "d":
return int(
(now + timedelta(days=int(locktime[:-1])))
.replace(hour=0, minute=0, second=0, microsecond=0)
.timestamp()
)
if locktime[-1] == "b":
locktime = int(locktime[:-1])
height = 0
if w:
height = Util.get_current_height(w.network)
locktime += int(height)
return int(locktime)
except Exception:
pass
return 0
def int_locktime(seconds=0, minutes=0, hours=0, days=0, blocks=0):
return int(
seconds
+ minutes * 60
+ hours * 60 * 60
+ days * 60 * 60 * 24
+ blocks * 600
)
def encode_amount(amount, decimal_point):
if Util.is_perc(amount):
return amount
else:
try:
return int(float(amount) * pow(10, decimal_point))
except Exception:
return 0
def decode_amount(amount, decimal_point):
if Util.is_perc(amount):
return amount
else:
basestr = "{{:0.{}f}}".format(decimal_point)
try:
return basestr.format(float(amount) / pow(10, decimal_point))
except Exception:
return str(amount)
def is_perc(value):
try:
return value[-1] == "%"
except Exception:
return False
def cmp_array(heira, heirb):
try:
if len(heira) != len(heirb):
return False
for h in range(0, len(heira)):
if heira[h] != heirb[h]:
return False
return True
except Exception:
return False
def cmp_heir(heira, heirb):
if heira[0] == heirb[0] and heira[1] == heirb[1]:
return True
return False
def cmp_willexecutor(willexecutora, willexecutorb):
if willexecutora == willexecutorb:
return True
try:
if (
willexecutora["url"] == willexecutorb["url"]
and willexecutora["address"] == willexecutorb["address"]
and willexecutora["base_fee"] == willexecutorb["base_fee"]
):
return True
except Exception:
return False
return False
def search_heir_by_values(heirs, heir, values):
for h, v in heirs.items():
found = False
for val in values:
if val in v and v[val] != heir[val]:
found = True
if not found:
return h
return False
def cmp_heir_by_values(heira, heirb, values):
for v in values:
if heira[v] != heirb[v]:
return False
return True
def cmp_heirs_by_values(
heirsa, heirsb, values, exclude_willexecutors=False, reverse=True
):
for heira in heirsa:
if (
exclude_willexecutors and 'w!ll3x3c"' not in heira
) or not exclude_willexecutors:
found = False
for heirb in heirsb:
if Util.cmp_heir_by_values(heirsa[heira], heirsb[heirb], values):
found = True
if not found:
return False
if reverse:
return Util.cmp_heirs_by_values(
heirsb,
heirsa,
values,
exclude_willexecutors=exclude_willexecutors,
reverse=False,
)
else:
return True
def cmp_heirs(
heirsa,
heirsb,
cmp_function=lambda x, y: x[0] == y[0] and x[3] == y[3],
reverse=True,
):
try:
for heir in heirsa:
if 'w!ll3x3c"' not in heir:
if heir not in heirsb or not cmp_function(
heirsa[heir], heirsb[heir]
):
if not Util.search_heir_by_values(heirsb, heirsa[heir], [0, 3]):
return False
if reverse:
return Util.cmp_heirs(heirsb, heirsa, cmp_function, False)
else:
return True
except Exception as e:
raise e
return False
def cmp_inputs(inputsa, inputsb):
if len(inputsa) != len(inputsb):
return False
for inputa in inputsa:
if not Util.in_utxo(inputa, inputsb):
return False
return True
def cmp_outputs(outputsa, outputsb, willexecutor_output=None):
if len(outputsa) != len(outputsb):
return False
for outputa in outputsa:
if not Util.cmp_output(outputa, willexecutor_output):
if not Util.in_output(outputa, outputsb):
return False
return True
def cmp_txs(txa, txb):
if not Util.cmp_inputs(txa.inputs(), txb.inputs()):
return False
if not Util.cmp_outputs(txa.outputs(), txb.outputs()):
return False
return True
def get_value_amount(txa, txb):
outputsa = txa.outputs()
# outputsb = txb.outputs()
value_amount = 0
for outa in outputsa:
same_amount, same_address = Util.in_output(outa, txb.outputs())
if not (same_amount or same_address):
return False
if same_amount and same_address:
value_amount += outa.value
if same_amount:
pass
if same_address:
pass
return value_amount
def chk_locktime(timestamp_to_check, block_height_to_check, locktime):
# TODO BUG: WHAT HAPPEN AT THRESHOLD?
def locktime_to_str(locktime):
try:
locktime = int(locktime)
if locktime > LOCKTIME_THRESHOLD and locktime > timestamp_to_check:
return True
elif locktime < LOCKTIME_THRESHOLD and locktime > block_height_to_check:
return True
else:
return False
def anticipate_locktime(locktime, blocks=0, hours=0, days=0):
locktime = int(locktime)
out = 0
if locktime > LOCKTIME_THRESHOLD:
seconds = blocks * 600 + hours * 3600 + days * 86400
dt = datetime.fromtimestamp(locktime)
dt -= timedelta(seconds=seconds)
out = dt.timestamp()
else:
blocks -= hours * 6 + days * 144
out = locktime + blocks
dt = datetime.fromtimestamp(locktime).isoformat()
return dt
if out < 1:
out = 1
except Exception:
pass
return str(locktime)
def str_to_locktime(locktime):
try:
if locktime[-1] in ("y", "d", "b"):
return locktime
else:
return int(locktime)
except Exception:
pass
dt_object = datetime.fromisoformat(locktime)
timestamp = dt_object.timestamp()
return int(timestamp)
def parse_locktime_string(locktime, w=None):
try:
return int(locktime)
except Exception:
pass
try:
now = datetime.now()
if locktime[-1] == "y":
locktime = str(int(locktime[:-1]) * 365) + "d"
if locktime[-1] == "d":
return int(
(now + timedelta(days=int(locktime[:-1])))
.replace(hour=0, minute=0, second=0, microsecond=0)
.timestamp()
)
if locktime[-1] == "b":
locktime = int(locktime[:-1])
height = 0
if w:
height = get_current_height(w.network)
locktime += int(height)
return int(locktime)
except Exception:
pass
return 0
def int_locktime(seconds=0, minutes=0, hours=0, days=0, blocks=0):
return int(
seconds + minutes * 60 + hours * 60 * 60 + days * 60 * 60 * 24 + blocks * 600
)
def encode_amount(amount, decimal_point):
if is_perc(amount):
return amount
else:
try:
return int(float(amount) * pow(10, decimal_point))
except:
return 0
def decode_amount(amount, decimal_point):
if is_perc(amount):
return amount
else:
num = 8 - decimal_point
basestr = "{{:0{}.{}f}}".format(num, num)
return "{:08.8f}".format(float(amount) / pow(10, decimal_point))
def is_perc(value):
try:
return value[-1] == "%"
except:
return False
def cmp_array(heira, heirb):
try:
if not len(heira) == len(heirb):
return False
for h in range(0, len(heira)):
if not heira[h] == heirb[h]:
return False
return True
except:
return False
def cmp_heir(heira, heirb):
if heira[0] == heirb[0] and heira[1] == heirb[1]:
return True
return False
def cmp_willexecutor(willexecutora, willexecutorb):
if willexecutora == willexecutorb:
return True
try:
if (
willexecutora["url"] == willexecutorb["url"]
and willexecutora["address"] == willexecutorb["address"]
and willexecutora["base_fee"] == willexecutorb["base_fee"]
):
return True
except:
return False
return False
def search_heir_by_values(heirs, heir, values):
for h, v in heirs.items():
found = False
for val in values:
if val in v and v[val] != heir[val]:
found = True
if not found:
return h
return False
def cmp_heir_by_values(heira, heirb, values):
for v in values:
if heira[v] != heirb[v]:
return False
return True
def cmp_heirs_by_values(
heirsa, heirsb, values, exclude_willexecutors=False, reverse=True
):
for heira in heirsa:
if (
exclude_willexecutors and 'w!ll3x3c"' not in heira
) or not exclude_willexecutors:
found = False
for heirb in heirsb:
if cmp_heir_by_values(heirsa[heira], heirsb[heirb], values):
found = True
if not found:
return False
if reverse:
return cmp_heirs_by_values(
heirsb,
heirsa,
values,
exclude_willexecutors=exclude_willexecutors,
reverse=False,
)
else:
return True
def cmp_heirs(
heirsa,
heirsb,
cmp_function=lambda x, y: x[0] == y[0] and x[3] == y[3],
reverse=True,
):
try:
for heir in heirsa:
if 'w!ll3x3c"' not in heir:
if heir not in heirsb or not cmp_function(heirsa[heir], heirsb[heir]):
if not search_heir_by_values(heirsb, heirsa[heir], [0, 3]):
return False
if reverse:
return cmp_heirs(heirsb, heirsa, cmp_function, False)
else:
return True
except Exception as e:
raise e
return False
def cmp_inputs(inputsa, inputsb):
if len(inputsa) != len(inputsb):
return False
for inputa in inputsa:
if not in_utxo(inputa, inputsb):
return False
return True
def cmp_outputs(outputsa, outputsb, willexecutor_output=None):
if len(outputsa) != len(outputsb):
return False
for outputa in outputsa:
if not cmp_output(outputa, willexecutor_output):
if not in_output(outputa, outputsb):
return False
return True
def cmp_txs(txa, txb):
if not cmp_inputs(txa.inputs(), txb.inputs()):
return False
if not cmp_outputs(txa.outputs(), txb.outputs()):
return False
return True
def get_value_amount(txa, txb):
outputsa = txa.outputs()
outputsb = txb.outputs()
value_amount = 0
for outa in outputsa:
same_amount, same_address = in_output(outa, txb.outputs())
if not (same_amount or same_address):
return False
if same_amount and same_address:
value_amount += outa.value
if same_amount:
pass
if same_address:
pass
return value_amount
def chk_locktime(timestamp_to_check, block_height_to_check, locktime):
# TODO BUG: WHAT HAPPEN AT THRESHOLD?
locktime = int(locktime)
if locktime > LOCKTIME_THRESHOLD and locktime > timestamp_to_check:
return True
elif locktime < LOCKTIME_THRESHOLD and locktime > block_height_to_check:
return True
else:
return False
def anticipate_locktime(locktime, blocks=0, hours=0, days=0):
locktime = int(locktime)
out = 0
if locktime > LOCKTIME_THRESHOLD:
seconds = blocks * 600 + hours * 3600 + days * 86400
dt = datetime.fromtimestamp(locktime)
dt -= timedelta(seconds=seconds)
out = dt.timestamp()
else:
blocks -= hours * 6 + days * 144
out = locktime + blocks
if out < 1:
out = 1
return out
def cmp_locktime(locktimea, locktimeb):
if locktimea == locktimeb:
return 0
strlocktimea = str(locktimea)
strlocktimeb = str(locktimeb)
intlocktimea = str_to_locktime(strlocktimea)
intlocktimeb = str_to_locktime(strlocktimeb)
if locktimea[-1] in "ydb":
if locktimeb[-1] == locktimea[-1]:
return int(strlocktimea[-1]) - int(strlocktimeb[-1])
else:
return int(locktimea) - (locktimeb)
def get_lowest_valid_tx(available_utxos, will):
will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
for txid, willitem in will.items():
pass
def get_locktimes(will):
locktimes = {}
for txid, willitem in will.items():
locktimes[willitem["tx"].locktime] = True
return locktimes.keys()
def get_lowest_locktimes(locktimes):
sorted_timestamp = []
sorted_block = []
for l in locktimes:
l = parse_locktime_string(l)
if l < LOCKTIME_THRESHOLD:
bisect.insort(sorted_block, l)
else:
bisect.insort(sorted_timestamp, l)
return sorted(sorted_timestamp), sorted(sorted_block)
def get_lowest_locktimes_from_will(will):
return get_lowest_locktimes(get_locktimes(will))
def search_willtx_per_io(will, tx):
for wid, w in will.items():
if cmp_txs(w["tx"], tx["tx"]):
return wid, w
return None, None
def invalidate_will(will):
raise Exception("not implemented")
def get_will_spent_utxos(will):
utxos = []
for txid, willitem in will.items():
utxos += willitem["tx"].inputs()
return utxos
def utxo_to_str(utxo):
try:
return utxo.to_str()
except Exception:
pass
try:
return utxo.prevout.to_str()
except Exception:
pass
return str(utxo)
def cmp_utxo(utxoa, utxob):
utxoa = utxo_to_str(utxoa)
utxob = utxo_to_str(utxob)
if utxoa == utxob:
return True
else:
return False
def in_utxo(utxo, utxos):
for s_u in utxos:
if cmp_utxo(s_u, utxo):
return True
return False
def txid_in_utxo(txid, utxos):
for s_u in utxos:
if s_u.prevout.txid == txid:
return True
return False
def cmp_output(outputa, outputb):
return outputa.address == outputb.address and outputa.value == outputb.value
def in_output(output, outputs):
for s_o in outputs:
if cmp_output(s_o, output):
return True
return False
# check all output with the same amount if none have the same address it can be a change
# return true true same address same amount
# return true false same amount different address
# return false false different amount, different address not found
def din_output(out, outputs):
same_amount = []
for s_o in outputs:
if int(out.value) == int(s_o.value):
same_amount.append(s_o)
if out.address == s_o.address:
return True, True
else:
pass
if len(same_amount) > 0:
return True, False
else:
return False, False
def get_change_output(wallet, in_amount, out_amount, fee):
change_amount = int(in_amount - out_amount - fee)
if change_amount > wallet.dust_threshold():
change_addresses = wallet.get_change_addresses_for_new_transaction()
out = PartialTxOutput.from_address_and_value(change_addresses[0], change_amount)
out.is_change = True
return out
def cmp_locktime(locktimea, locktimeb):
if locktimea == locktimeb:
return 0
strlocktimea = str(locktimea)
strlocktimeb = str(locktimeb)
# intlocktimea = Util.str_to_locktime(strlocktimea)
# intlocktimeb = Util.str_to_locktime(strlocktimeb)
if locktimea[-1] in "ydb":
if locktimeb[-1] == locktimea[-1]:
return int(strlocktimea[-1]) - int(strlocktimeb[-1])
else:
return int(locktimea) - (locktimeb)
def get_lowest_valid_tx(available_utxos, will):
will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime)
for txid, willitem in will.items():
pass
def get_current_height(network: "Network"):
# if no network or not up to date, just set locktime to zero
if not network:
return 0
chain = network.blockchain()
if chain.is_tip_stale():
return 0
# figure out current block height
chain_height = chain.height() # learnt from all connected servers, SPV-checked
server_height = (
network.get_server_height()
) # height claimed by main server, unverified
# note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork)
# - if it's lagging too much, it is the network's job to switch away
if server_height < chain_height - 10:
# the diff is suspiciously large... give up and use something non-fingerprintable
return 0
# discourage "fee sniping"
height = min(chain_height, server_height)
return height
def get_locktimes(will):
locktimes = {}
for txid, willitem in will.items():
locktimes[willitem["tx"].locktime] = True
return locktimes.keys()
def get_lowest_locktimes(locktimes):
sorted_timestamp = []
sorted_block = []
for locktime in locktimes:
locktime = Util.parse_locktime_string(locktime)
if locktime < LOCKTIME_THRESHOLD:
bisect.insort(sorted_block, locktime)
else:
bisect.insort(sorted_timestamp, locktime)
return sorted(sorted_timestamp), sorted(sorted_block)
def get_lowest_locktimes_from_will(will):
return Util.get_lowest_locktimes(Util.get_locktimes(will))
def search_willtx_per_io(will, tx):
for wid, w in will.items():
if Util.cmp_txs(w["tx"], tx["tx"]):
return wid, w
return None, None
def invalidate_will(will):
raise Exception("not implemented")
def get_will_spent_utxos(will):
utxos = []
for txid, willitem in will.items():
utxos += willitem["tx"].inputs()
return utxos
def utxo_to_str(utxo):
def print_var(var, name="", veryverbose=False):
print(f"---{name}---")
if var is not None:
try:
return utxo.to_str()
except Exception:
print("doc:", doc(var))
except:
pass
try:
return utxo.prevout.to_str()
except Exception:
print("str:", str(var))
except:
pass
try:
print("repr", repr(var))
except:
pass
try:
print("dict", dict(var))
except:
pass
try:
print("dir", dir(var))
except:
pass
try:
print("type", type(var))
except:
pass
try:
print("to_json", var.to_json())
except:
pass
try:
print("__slotnames__", var.__slotnames__)
except:
pass
return str(utxo)
def cmp_utxo(utxoa, utxob):
utxoa = Util.utxo_to_str(utxoa)
utxob = Util.utxo_to_str(utxob)
if utxoa == utxob:
return True
else:
return False
print(f"---end {name}---")
def in_utxo(utxo, utxos):
for s_u in utxos:
if Util.cmp_utxo(s_u, utxo):
return True
return False
def txid_in_utxo(txid, utxos):
for s_u in utxos:
if s_u.prevout.txid == txid:
return True
return False
def print_utxo(utxo, name=""):
print(f"---utxo-{name}---")
print_var(utxo, name)
print_prevout(utxo.prevout, name)
print_var(utxo.script_sig, f"{name}-script-sig")
print_var(utxo.witness, f"{name}-witness")
print("_TxInput__address:", utxo._TxInput__address)
print("_TxInput__scriptpubkey:", utxo._TxInput__scriptpubkey)
print("_TxInput__value_sats:", utxo._TxInput__value_sats)
print(f"---utxo-end {name}---")
def cmp_output(outputa, outputb):
return outputa.address == outputb.address and outputa.value == outputb.value
def in_output(output, outputs):
for s_o in outputs:
if Util.cmp_output(s_o, output):
return True
return False
def print_prevout(prevout, name=""):
print(f"---prevout-{name}---")
print_var(prevout, f"{name}-prevout")
print_var(prevout._asdict())
print(f"---prevout-end {name}---")
# check all output with the same amount if none have the same address it can be a change
# return true true same address same amount
# return true false same amount different address
# return false false different amount, different address not found
def din_output(out, outputs):
same_amount = []
for s_o in outputs:
if int(out.value) == int(s_o.value):
same_amount.append(s_o)
if out.address == s_o.address:
return True, True
else:
pass
if len(same_amount) > 0:
return True, False
else:
return False, False
def get_change_output(wallet, in_amount, out_amount, fee):
change_amount = int(in_amount - out_amount - fee)
if change_amount > wallet.dust_threshold():
change_addresses = wallet.get_change_addresses_for_new_transaction()
out = PartialTxOutput.from_address_and_value(
change_addresses[0], change_amount
)
out.is_change = True
return out
def get_current_height(network):
# if no network or not up to date, just set locktime to zero
if not network:
return 0
chain = network.blockchain()
if chain.is_tip_stale():
return 0
# figure out current block height
chain_height = chain.height() # learnt from all connected servers, SPV-checked
server_height = (
network.get_server_height()
) # height claimed by main server, unverified
# note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork)
# - if it's lagging too much, it is the network's job to switch away
if server_height < chain_height - 10:
# the diff is suspiciously large... give up and use something non-fingerprintable
return 0
# discourage "fee sniping"
height = min(chain_height, server_height)
return height
def print_var(var, name="", veryverbose=False):
print(f"---{name}---")
if var is not None:
try:
print("str:", str(var))
except Exception:
pass
try:
print("repr", repr(var))
except Exception:
pass
try:
print("dict", dict(var))
except Exception:
pass
try:
print("dir", dir(var))
except Exception:
pass
try:
print("type", type(var))
except Exception:
pass
try:
print("to_json", var.to_json())
except Exception:
pass
try:
print("__slotnames__", var.__slotnames__)
except Exception:
pass
print(f"---end {name}---")
def print_utxo(utxo, name=""):
print(f"---utxo-{name}---")
Util.print_var(utxo, name)
Util.print_prevout(utxo.prevout, name)
Util.print_var(utxo.script_sig, f"{name}-script-sig")
Util.print_var(utxo.witness, f"{name}-witness")
print("_TxInput__address:", utxo._TxInput__address)
print("_TxInput__scriptpubkey:", utxo._TxInput__scriptpubkey)
print("_TxInput__value_sats:", utxo._TxInput__value_sats)
print(f"---utxo-end {name}---")
def print_prevout(prevout, name=""):
print(f"---prevout-{name}---")
Util.print_var(prevout, f"{name}-prevout")
Util.print_var(prevout._asdict())
print(f"---prevout-end {name}---")
def export_meta_gui(electrum_window, 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,
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))
)
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)))
)
def copy(dicto, dictfrom):
for k, v in dictfrom.items():
dicto[k] = v
def fix_will_settings_tx_fees(will_settings):
tx_fees = will_settings.get("tx_fees", False)
have_to_update = False
if tx_fees:
will_settings["baltx_fees"] = tx_fees
del will_settings["tx_fees"]
have_to_update = True
return have_to_update
def fix_will_tx_fees(will):
have_to_update = False
for txid, willitem in will.items():
tx_fees = willitem.get("tx_fees", False)
if tx_fees:
will[txid]["baltx_fees"] = tx_fees
del will[txid]["tx_fees"]
have_to_update = True
return have_to_update
def copy(dicto, dictfrom):
for k, v in dictfrom.items():
dicto[k] = v

View File

@@ -1,77 +0,0 @@
#!env/bin/python3
from electrum.storage import WalletStorage
import json
from electrum.util import MyEncoder
import sys
import getpass
import os
default_fees = 100
def fix_will_settings_tx_fees(json_wallet):
tx_fees = json_wallet.get("will_settings", {}).get("tx_fees", False)
have_to_update = False
if tx_fees:
json_wallet["will_settings"]["baltx_fees"] = tx_fees
del json_wallet["will_settings"]["tx_fees"]
have_to_update = True
for txid, willitem in json_wallet["will"].items():
tx_fees = willitem.get("tx_fees", False)
if tx_fees:
json_wallet["will"][txid]["baltx_fees"] = tx_fees
del json_wallet["will"][txid]["tx_fees"]
have_to_update = True
return have_to_update
def uninstall_bal(json_wallet):
del json_wallet["will_settings"]
del json_wallet["will"]
del json_wallet["heirs"]
return True
def save(json_wallet, storage):
human_readable = not storage.is_encrypted()
storage.write(
json.dumps(
json_wallet,
indent=4 if human_readable else None,
sort_keys=bool(human_readable),
cls=MyEncoder,
)
)
def read_wallet(path, password=False):
storage = WalletStorage(path)
if storage.is_encrypted():
if not password:
password = getpass.getpass("Enter wallet password: ", stream=None)
storage.decrypt(password)
data = storage.read()
json_wallet = json.loads("[" + data + "]")[0]
return json_wallet, storage
if __name__ == "__main__":
if len(sys.argv) < 3:
print("usage: ./bal_wallet_utils <command> <wallet path>")
print("available commands: uninstall, fix")
exit(1)
if not os.path.exists(sys.argv[2]):
print("Error: wallet not found")
exit(1)
command = sys.argv[1]
path = sys.argv[2]
json_wallet, storage = read_wallet(path)
have_to_save = False
if command == "fix":
have_to_save = fix_will_settings_tx_fees(json_wallet)
if command == "uninstall":
have_to_save = uninstall_bal(json_wallet)
if have_to_save:
save(json_wallet, storage)
else:
print("nothing to do")

173
will.py
View File

@@ -12,10 +12,14 @@ from electrum.transaction import (
tx_from_any,
)
from electrum.util import (
FileImportFailed,
bfh,
decimal_point_to_base_unit_name,
read_json_file,
write_json_file,
)
from .util import Util
from .util import *
from .willexecutors import Willexecutors
MIN_LOCKTIME = 1
@@ -24,6 +28,7 @@ _logger = get_logger(__name__)
class Will:
# return an array with the list of children
def get_children(will, willid):
out = []
for _id in will:
@@ -55,7 +60,7 @@ class Will:
for w in will:
if w != wid and not tx.to_json() != will[w]["tx"].to_json():
if will[w]["tx"].txid() != tx.txid():
if Util.cmp_txs(will[w]["tx"], tx):
if cmp_txs(will[w]["tx"], tx):
return will[w]["tx"]
return False
@@ -77,11 +82,13 @@ class Will:
for txin in will[wid].tx.inputs():
txid = txin.prevout.txid.hex()
if txid in will:
# print(will[txid].tx.outputs())
# print(txin.prevout.out_idx)
change = will[txid].tx.outputs()[txin.prevout.out_idx]
txin._trusted_value_sats = change.value
try:
txin.script_descriptor = change.script_descriptor
except Exception:
except:
pass
txin.is_mine = True
txin._TxInput__address = change.address
@@ -100,7 +107,6 @@ class Will:
will = willitems
errors = {}
for wid in will:
txid = will[wid].tx.txid()
if txid is None:
@@ -128,8 +134,8 @@ class Will:
to_delete.append(wid)
to_add[ow.tx.txid()] = ow.to_dict()
# for eid, err in errors.items():
# new_txid = err.tx.txid()
for eid, err in errors.items():
new_txid = err.tx.txid()
for k, w in to_add.items():
will[k] = w
@@ -148,20 +154,10 @@ class Will:
inp._TxInput__value_sats = change.value
return inp
"""
in questa situazione sono presenti due transazioni con id differente(quindi transazioni differenti)
per prima cosa controllo il locktime
se il locktime della nuova transazione e' maggiore del locktime della vecchia transazione, allora
confronto gli eredi, per locktime se corrispondono controllo i willexecutor
se hanno la stessa url ma le fee vecchie sono superiori alle fee nuove, allora anticipare.
"""
def check_anticipate(ow: "WillItem", nw: "WillItem"):
anticipate = Util.anticipate_locktime(ow.tx.locktime, days=1)
anticipate = anticipate_locktime(ow.tx.locktime, days=1)
if int(nw.tx.locktime) >= int(anticipate):
if Util.cmp_heirs_by_values(
if cmp_heirs_by_values(
ow.heirs, nw.heirs, [0, 1], exclude_willexecutors=True
):
if nw.we and ow.we:
@@ -177,7 +173,7 @@ class Will:
ow.tx.locktime
else:
if nw.we == ow.we:
if not Util.cmp_heirs_by_values(ow.heirs, nw.heirs, [0, 3]):
if not cmp_heirs_by_values(ow.heirs, nw.heirs, [0, 3]):
return anticipate
else:
return ow.tx.locktime
@@ -197,7 +193,7 @@ class Will:
outputs = w.tx.outputs()
found = False
old_txid = w.tx.txid()
# ntx = None
ntx = None
for i in range(0, len(inputs)):
if (
inputs[i].prevout.txid.hex() == otxid
@@ -208,7 +204,7 @@ class Will:
will[wid].tx.set_rbf(True)
will[wid].tx._inputs[i] = Will.new_input(wid, idx, change)
found = True
if found:
if found == True:
pass
new_txid = will[wid].tx.txid()
@@ -235,7 +231,7 @@ class Will:
for i in inputs:
prevout_str = i.prevout.to_str()
inp = [w, will[w], i]
if prevout_str not in all_inputs:
if not prevout_str in all_inputs:
all_inputs[prevout_str] = [inp]
else:
all_inputs[prevout_str].append(inp)
@@ -248,7 +244,7 @@ class Will:
min_locktime = min(values, key=lambda x: x[1].tx.locktime)[1].tx.locktime
for w in values:
if w[1].tx.locktime == min_locktime:
if i not in all_inputs_min_locktime:
if not i in all_inputs_min_locktime:
all_inputs_min_locktime[i] = [w]
else:
all_inputs_min_locktime[i].append(w)
@@ -261,7 +257,7 @@ class Will:
to_append = {}
new_inputs = Will.get_all_inputs(will, only_valid=True)
for nid, nwi in will.items():
if nwi.search_anticipate(new_inputs):
if nwi.search_anticipate(new_inputs) or nwi.search_anticipate(old_inputs):
if nid != nwi.tx.txid():
redo = True
to_delete.append(nid)
@@ -271,36 +267,25 @@ class Will:
Will.change_input(
will, nid, i, outputs[i], new_inputs, to_delete, to_append
)
if nwi.search_anticipate(old_inputs):
if nid != nwi.tx.txid():
redo = True
to_delete.append(nid)
to_append[nwi.tx.txid()] = nwi
outputs = nwi.tx.outputs()
for i in range(0, len(outputs)):
Will.change_input(
will, nid, i, outputs[i], new_inputs, to_delete, to_append
)
for w in to_delete:
try:
del will[w]
except Exception:
except:
pass
for k, w in to_append.items():
will[k] = w
if redo:
Will.search_anticipate_rec(will, old_inputs)
def update_will(old_will, new_will):
all_old_inputs = Will.get_all_inputs(old_will, only_valid=True)
# all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
# all_new_inputs = Will.get_all_inputs(new_will)
all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
all_new_inputs = Will.get_all_inputs(new_will)
# check if the new input is already spent by other transaction
# if it is use the same locktime, or anticipate.
Will.search_anticipate_rec(new_will, all_old_inputs)
other_inputs = Will.get_all_inputs(old_will, {})
try:
Will.normalize_will(new_will, others_inputs=other_inputs)
@@ -341,9 +326,9 @@ class Will:
prevout_to_spend = []
for prevout_str, ws in inputs.items():
for w in ws:
if w[0] not in filtered_inputs:
if not w[0] in filtered_inputs:
filtered_inputs.append(w[0])
if prevout_str not in prevout_to_spend:
if not prevout_str in prevout_to_spend:
prevout_to_spend.append(prevout_str)
balance = 0
utxo_to_spend = []
@@ -352,11 +337,12 @@ class Will:
if utxo_str in prevout_to_spend:
balance += inputs[utxo_str][0][2].value_sats()
utxo_to_spend.append(utxo)
if len(utxo_to_spend) > 0:
change_addresses = wallet.get_change_addresses_for_new_transaction()
out = PartialTxOutput.from_address_and_value(change_addresses[0], balance)
out.is_change = True
locktime = Util.get_current_height(wallet.network)
locktime = get_current_height(wallet.network)
tx = PartialTransaction.from_io(
utxo_to_spend, [out], locktime=locktime, version=2
)
@@ -387,9 +373,9 @@ class Will:
return True
def search_rai(all_inputs, all_utxos, will, wallet):
# will_only_valid = Will.only_valid_or_replaced_list(will)
will_only_valid = Will.only_valid_or_replaced_list(will)
for inp, ws in all_inputs.items():
inutxo = Util.in_utxo(inp, all_utxos)
inutxo = in_utxo(inp, all_utxos)
for w in ws:
wi = w[1]
if (
@@ -420,31 +406,28 @@ class Will:
pass
def utxos_strs(utxos):
return [Util.utxo_to_str(u) for u in utxos]
return [utxo_to_str(u) for u in utxos]
def set_invalidate(wid, will=[]):
will[wid].set_status("INVALIDATED", True)
if will[wid].children:
for c in will[wid].children.items():
for c in self.children.items():
Will.set_invalidate(c[0], will)
def check_tx_height(tx, wallet):
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
def check_invalidated(willtree, utxos_list, wallet):
for wid, w in willtree.items():
if (
not w.father
or willtree[w.father].get_status("CONFIRMED")
or willtree[w.father].get_status("PENDING")
):
if not w.father:
for inp in w.tx.inputs():
inp_str = Util.utxo_to_str(inp)
if inp_str not in utxos_list:
inp_str = utxo_to_str(inp)
if not inp_str in utxos_list:
if wallet:
height = Will.check_tx_height(w.tx, wallet)
if height < 0:
Will.set_invalidate(wid, willtree)
elif height == 0:
@@ -452,21 +435,21 @@ class Will:
else:
w.set_status("CONFIRMED", True)
# def reflect_to_children(treeitem):
# if not treeitem.get_status("VALID"):
# _logger.debug(f"{tree:item._id} status not valid looking for children")
# for child in treeitem.children:
# wc = willtree[child]
# if wc.get_status("VALID"):
# if treeitem.get_status("INVALIDATED"):
# wc.set_status("INVALIDATED", True)
# if treeitem.get_status("REPLACED"):
# wc.set_status("REPLACED", True)
# if wc.children:
# Will.reflect_to_children(wc)
def reflect_to_children(treeitem):
if not treeitem.get_status("VALID"):
_logger.debug(f"{tree:item._id} status not valid looking for children")
for child in treeitem.children:
wc = willtree[child]
if wc.get_status("VALID"):
if treeitem.get_status("INVALIDATED"):
wc.set_status("INVALIDATED", True)
if treeitem.get_status("REPLACED"):
wc.set_status("REPLACED", True)
if wc.children:
Will.reflect_to_children(wc)
def check_amounts(heirs, willexecutors, all_utxos, timestamp_to_check, dust):
fixed_heirs, fixed_amount, perc_heirs, perc_amount, fixed_amount_with_dust = (
fixed_heirs, fixed_amount, perc_heirs, perc_amount = (
heirs.fixed_percent_lists_amount(timestamp_to_check, dust, reverse=True)
)
wallet_balance = 0
@@ -495,7 +478,9 @@ class Will:
Will.check_invalidated(will, utxos_list, wallet)
all_inputs = Will.get_all_inputs(will, only_valid=True)
all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_inputs)
Will.check_will_expired(
all_inputs_min_locktime, block_to_check, timestamp_to_check
)
@@ -517,6 +502,7 @@ class Will:
callback_not_valid_tx=None,
):
Will.check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check)
if heirs:
if not Will.check_willexecutors_and_heirs(
will,
@@ -534,7 +520,7 @@ class Will:
if all_inputs:
for utxo in all_utxos:
if utxo.value_sats() > 68 * tx_fees:
if not Util.in_utxo(utxo, all_inputs.keys()):
if not in_utxo(utxo, all_inputs.keys()):
_logger.info("utxo is not spent", utxo.to_json())
_logger.debug(all_inputs.keys())
raise NotCompleteWillException(
@@ -561,16 +547,16 @@ class Will:
f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}"
)
# def check_all_input_spent_are_in_wallet():
# _logger.info("check all input spent are in wallet or valid txs")
# for inp, ws in all_inputs.items():
# if not Util.in_utxo(inp, all_utxos):
# for w in ws:
# if w[1].get_status("VALID"):
# prevout_id = w[2].prevout.txid.hex()
# parentwill = will.get(prevout_id, False)
# if not parentwill or not parentwill.get_status("VALID"):
# w[1].set_status("INVALIDATED", True)
def check_all_input_spent_are_in_wallet():
_logger.info("check all input spent are in wallet or valid txs")
for inp, ws in all_inputs.items():
if not in_utxo(inp, all_utxos):
for w in ws:
if w[1].get_status("VALID"):
prevout_id = w[2].prevout.txid.hex()
parentwill = will.get(prevout_id, False)
if not parentwill or not parentwill.get_status("VALID"):
w[1].set_status("INVALIDATED", True)
def only_valid_list(will):
out = {}
@@ -605,23 +591,22 @@ class Will:
if not 'w!ll3x3c"' == wheir[:9]:
their = will[wid].heirs[wheir]
if heir := heirs.get(wheir, None):
if (
heir[0] == their[0]
and heir[1] == their[1]
and Util.parse_locktime_string(heir[2])
>= Util.parse_locktime_string(their[2])
and parse_locktime_string(heir[2])
>= parse_locktime_string(their[2])
):
count = heirs_found.get(wheir, 0)
heirs_found[wheir] = count + 1
else:
_logger.debug(
f"heir not present transaction is not valid:{wheir} {wid}, {w}"
"heir not present transaction is not valid:", wid, w
)
continue
if willexecutor := w.we:
count = willexecutors_found.get(willexecutor["url"], 0)
if Util.cmp_willexecutor(
if cmp_willexecutor(
willexecutor, willexecutors.get(willexecutor["url"], None)
):
willexecutors_found[willexecutor["url"]] = count + 1
@@ -630,19 +615,19 @@ class Will:
no_willexecutor += 1
count_heirs = 0
for h in heirs:
if Util.parse_locktime_string(heirs[h][2]) >= check_date:
if parse_locktime_string(heirs[h][2]) >= check_date:
count_heirs += 1
if h not in heirs_found:
if not h in heirs_found:
_logger.debug(f"heir: {h} not found")
raise HeirNotFoundException(h)
if not count_heirs:
raise NoHeirsException("there are not valid heirs")
if self_willexecutor and no_willexecutor == 0:
raise NoWillExecutorNotPresent("Backup tx")
for url, we in willexecutors.items():
if Willexecutors.is_selected(we):
if url not in willexecutors_found:
if not url in willexecutors_found:
_logger.debug(f"will-executor: {url} not fount")
raise WillExecutorNotPresent(url)
_logger.info("will is coherent with heirs and will-executors")
@@ -671,15 +656,15 @@ class WillItem(Logger):
}
def set_status(self, status, value=True):
# _logger.trace(
# "set status {} - {} {} -> {}".format(
# self._id, status, self.STATUS[status][1], value
# )
# )
_logger.debug(
"set status {} - {} {} -> {}".format(
self._id, status, self.STATUS[status][1], value
)
)
if self.STATUS[status][1] == bool(value):
return None
self.status += "." + (("NOT " if not value else "") + _(self.STATUS[status][0]))
self.status += "." + ("NOT " if not value else "" + _(self.STATUS[status][0]))
self.STATUS[status][1] = bool(value)
if value:
if status in ["INVALIDATED", "REPLACED", "CONFIRMED", "PENDING"]:

View File

@@ -1,37 +1,33 @@
import json
from datetime import datetime
import time
from functools import partial
from aiohttp import ClientResponse
from electrum import constants
from electrum.gui.qt.util import WaitingDialog
from electrum.i18n import _
from electrum.logging import get_logger
from electrum.network import Network
from .bal import BalPlugin
# from .util import *
DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__)
chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin"
class Willexecutors:
def save(bal_plugin, willexecutors):
_logger.debug(f"save {willexecutors},{chainname}")
aw = bal_plugin.WILLEXECUTORS.get()
aw[chainname] = willexecutors
aw[constants.net.NET_NAME] = willexecutors
bal_plugin.WILLEXECUTORS.set(aw)
_logger.debug(f"saved: {aw}")
# bal_plugin.WILLEXECUTORS.set(willexecutors)
def get_willexecutors(
bal_plugin, update=False, bal_window=False, force=False, task=True
):
willexecutors = bal_plugin.WILLEXECUTORS.get()
willexecutors = willexecutors.get(chainname, {})
willexecutors = willexecutors.get(constants.net.NET_NAME, {})
to_del = []
for w in willexecutors:
if not isinstance(willexecutors[w], dict):
@@ -39,38 +35,34 @@ class Willexecutors:
continue
Willexecutors.initialize_willexecutor(willexecutors[w], w)
for w in to_del:
_logger.error(
"error Willexecutor to delete type:{} {}".format(
type(willexecutors[w]), w
)
)
print("ERROR: WILLEXECUTOR TO DELETE:", w)
del willexecutors[w]
bal = bal_plugin.WILLEXECUTORS.default.get(chainname, {})
bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME, {})
for bal_url, bal_executor in bal.items():
if bal_url not in willexecutors:
if not bal_url in willexecutors:
_logger.debug(f"force add {bal_url} willexecutor")
willexecutors[bal_url] = bal_executor
# if update:
# found = False
# for url, we in willexecutors.items():
# if Willexecutors.is_selected(we):
# found = True
# if found or force:
# if bal_plugin.PING_WILLEXECUTORS.get() or force:
# ping_willexecutors = True
# if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force:
# if bal_window:
# ping_willexecutors = bal_window.window.question(
# _(
# "Contact willexecutors servers to update payment informations?"
# )
# )
if update:
found = False
for url, we in willexecutors.items():
if Willexecutors.is_selected(we):
found = True
if found or force:
if bal_plugin.PING_WILLEXECUTORS.get() or force:
ping_willexecutors = True
if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force:
if bal_window:
ping_willexecutors = bal_window.window.question(
_(
"Contact willexecutors servers to update payment informations?"
)
)
# if ping_willexecutors:
# if task:
# bal_window.ping_willexecutors(willexecutors, task)
# else:
# bal_window.ping_willexecutors_task(willexecutors)
if ping_willexecutors:
if task:
bal_window.ping_willexecutors(willexecutors, task)
else:
bal_window.ping_willexecutors_task(willexecutors)
w_sorted = dict(
sorted(
willexecutors.items(), key=lambda w: w[1].get("sort", 0), reverse=True
@@ -81,11 +73,11 @@ class Willexecutors:
def is_selected(willexecutor, value=None):
if not willexecutor:
return False
if value is not None:
if not value is None:
willexecutor["selected"] = value
try:
return willexecutor["selected"]
except Exception:
except:
willexecutor["selected"] = False
return False
@@ -98,7 +90,7 @@ class Willexecutors:
if willexecutor := willitem.we:
url = willexecutor["url"]
if willexecutor and Willexecutors.is_selected(willexecutor):
if url not in willexecutors:
if not url in willexecutors:
willexecutor["txs"] = ""
willexecutor["txsids"] = []
willexecutor["broadcast_status"] = _("Waiting...")
@@ -108,34 +100,31 @@ class Willexecutors:
return willexecutors
# def only_selected_list(willexecutors):
# out = {}
# for url, v in willexecutors.items():
# if Willexecutors.is_selected(url):
# out[url] = v
def only_selected_list(willexecutors):
out = {}
for url, v in willexecutors.items():
if Willexecutors.is_selected(willexecutor):
out[url] = v
# def push_transactions_to_willexecutors(will):
# willexecutors = Willexecutors.get_transactions_to_be_pushed()
# for url in willexecutors:
# willexecutor = willexecutors[url]
# if Willexecutors.is_selected(willexecutor):
# if "txs" in willexecutor:
# Willexecutors.push_transactions_to_willexecutor(
# willexecutors[url]["txs"], url
# )
def push_transactions_to_willexecutors(will):
willexecutors = get_transactions_to_be_pushed()
for url in willexecutors:
willexecutor = willexecutors[url]
if Willexecutors.is_selected(willexecutor):
if "txs" in willexecutor:
Willexecutors.push_transactions_to_willexecutor(
willexecutors[url]["txs"], url
)
def send_request(
method, url, data=None, *, timeout=10, handle_response=None, count_reply=0
):
def send_request(method, url, data=None, *, timeout=10):
network = Network.get_instance()
if not network:
raise Exception("You are offline.")
raise ErrorConnectingServer("You are offline.")
_logger.debug(f"<-- {method} {url} {data}")
headers = {}
headers["user-agent"] = f"BalPlugin v:{BalPlugin.version()}"
headers["Content-Type"] = "text/plain"
if not handle_response:
handle_response = Willexecutors.handle_response
try:
if method == "get":
response = Network.send_http_on_proxy(
@@ -143,7 +132,7 @@ class Willexecutors:
url,
params=data,
headers=headers,
on_finish=handle_response,
on_finish=Willexecutors.handle_response,
timeout=timeout,
)
elif method == "post":
@@ -152,47 +141,26 @@ class Willexecutors:
url,
body=data,
headers=headers,
on_finish=handle_response,
on_finish=Willexecutors.handle_response,
timeout=timeout,
)
else:
raise Exception(f"unexpected {method=!r}")
except TimeoutError:
if count_reply < 10:
_logger.debug(f"timeout({count_reply}) error: retry in 3 sec...")
time.sleep(3)
return Willexecutors.send_request(
method,
url,
data,
timeout=timeout,
handle_response=handle_response,
count_reply=count_reply + 1,
)
else:
_logger.debug(f"Too many timeouts: {count_reply}")
except Exception as e:
_logger.error(f"exception sending request {e}")
raise e
else:
_logger.debug(f"--> {response}")
return response
def get_we_url_from_response(resp):
url_slices = str(resp.url).split("/")
if len(url_slices) > 2:
url_slices = url_slices[:-2]
return "/".join(url_slices)
async def handle_response(resp: ClientResponse):
r = await resp.text()
try:
r = json.loads(r)
# url = Willexecutors.get_we_url_from_response(resp)
# r["url"]= url
# r["status"]=resp.status
except Exception as e:
_logger.debug(f"error handling response:{e}")
r["status"] = resp.status
r["selected"] = Willexecutors.is_selected(willexecutor)
r["url"] = url
except:
pass
return r
@@ -202,10 +170,10 @@ class Willexecutors:
def push_transactions_to_willexecutor(willexecutor):
out = True
try:
_logger.debug(f"{willexecutor['url']}: {willexecutor['txs']}")
_logger.debug(f"willexecutor['txs']")
if w := Willexecutors.send_request(
"post",
willexecutor["url"] + "/" + chainname + "/pushtxs",
willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs",
data=willexecutor["txs"].encode("ascii"),
):
willexecutor["broadcast_status"] = _("Success")
@@ -233,14 +201,18 @@ class Willexecutors:
try:
_logger.info("GETINFO_WILLEXECUTOR")
_logger.debug(url)
w = Willexecutors.send_request("get", url + "/" + chainname + "/info")
if isinstance(w, dict):
willexecutor["url"] = url
willexecutor["status"] = 200
willexecutor["base_fee"] = w["base_fee"]
willexecutor["address"] = w["address"]
netname = "bitcoin"
if constants.net.NET_NAME != "mainnet":
netname = constants.net.NET_NAME
w = Willexecutors.send_request("get", url + "/" + netname + "/info")
willexecutor["url"] = url
willexecutor["status"] = w["status"]
willexecutor["base_fee"] = w["base_fee"]
willexecutor["address"] = w["address"]
if not willexecutor["info"]:
willexecutor["info"] = w["info"]
_logger.debug(f"response_data {w}")
_logger.debug(f"response_data {w['address']}")
except Exception as e:
_logger.error(f"error {e} contacting {url}: {w}")
willexecutor["status"] = "KO"
@@ -248,33 +220,24 @@ class Willexecutors:
willexecutor["last_update"] = datetime.now().timestamp()
return willexecutor
def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor={}):
def initialize_willexecutor(willexecutor, url, status=None, selected=None):
willexecutor["url"] = url
if status is not None:
if not status is None:
willexecutor["status"] = status
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"))
willexecutor["selected"] = Willexecutors.is_selected(willexecutor, selected)
def download_list(bal_plugin,old_willexecutors):
def download_list(bal_plugin):
try:
willexecutors = Willexecutors.send_request(
"get",
f"https://welist.bitcoin-after.life/data/{chainname}?page=0&limit=100",
l = Willexecutors.send_request(
"get", "https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100"
)
# del willexecutors["status"]
for w in willexecutors:
if w not in ("status", "url"):
Willexecutors.initialize_willexecutor(
willexecutors[w], w, None, old_willexecutors.get(w,{})
)
del l["status"]
for w in l:
willexecutor = l[w]
Willexecutors.initialize_willexecutor(willexecutor, w, "New", False)
# bal_plugin.WILLEXECUTORS.set(l)
# bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True)
return willexecutors
return l
except Exception as e:
_logger.error(f"Failed to download willexecutors list: {e}")
@@ -288,7 +251,7 @@ class Willexecutors:
willexecutor = willexecutors[w]
Willexecutors.initialize_willexecutor(willexecutor, w, "New", False)
# bal_plugin.WILLEXECUTORS.set(willexecutors)
return willexecutors
return h
except Exception as e:
_logger.error(f"error opening willexecutors json: {e}")
@@ -305,53 +268,14 @@ class Willexecutors:
_logger.error(f"error contacting {url} for checking txs {e}")
raise e
def compute_id(willexecutor):
return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain"))
class WillExecutor:
def __init__(
self,
url,
base_fee,
chain,
info,
version,
status,
is_selected=False,
promo_code="",
):
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
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}"
we = WillExecutor(d["url"], d["base_fee"], d["chain"], d["info"], d["version"])