Compare commits

...

No commits in common. "main" and "c0d64201a802ae373415fce17cde5334950a16da" have entirely different histories.

13 changed files with 1512 additions and 3727 deletions

View File

@ -1 +0,0 @@
0.2.2a

View File

@ -1 +1,20 @@
from electrum.i18n import _
import subprocess
from . import bal_resources
BUILD_NUMBER = 1
REVISION_NUMBER = 1
VERSION_NUMBER = 0
def _version():
return f'{VERSION_NUMBER}.{REVISION_NUMBER}-{BUILD_NUMBER}'
version = _version()
author = "Bal Enterprise inc."
fullname = _('B.A.L.')
description = ''.join([
"<img src='",bal_resources.icon_path('bal16x16.png'),"'>", _("Bitcoin After Life"), '<br/>',
_("For more information, visit"),
" <a href=\"https://bitcoin-after.life/\">https://bitcoin-after.life/</a><br/>",
"<p style='font-size:8pt;vertialAlign:bottom'>Version: ", _version(),"</p>"
])
#available_for = ['qt', 'cmdline', 'qml']
available_for = ['qt']

160
bal.py
View File

@ -1,128 +1,126 @@
import random import random
import os import os
import zipfile as zipfile_lib from hashlib import sha256
from typing import NamedTuple, Optional, Dict, Tuple
from electrum.plugin import BasePlugin from electrum.plugin import BasePlugin
from electrum.util import to_bytes, bfh
from electrum import json_db from electrum import json_db
from electrum.transaction import tx_from_any from electrum.transaction import tx_from_any
from . import util as Util
from . import willexecutors as Willexecutors
import os import os
json_db.register_dict('heirs', tuple, None) json_db.register_dict('heirs', tuple, None)
json_db.register_dict('will', lambda x: get_will(x), None) json_db.register_dict('will', lambda x: get_will(x), None)
json_db.register_dict('will_settings', lambda x:x, None) json_db.register_dict('will_settings', lambda x:x, None)
from electrum.logging import get_logger from electrum.logging import get_logger
def get_will(x): def get_will(x):
try: try:
#print("______________________________________________________________________________________________________")
#print(x)
x['tx']=tx_from_any(x['tx']) x['tx']=tx_from_any(x['tx'])
except Exception as e: except Exception as e:
#Util.print_var(x)
raise e raise e
return x return x
class BalConfig():
def __init__(self, config, name, default):
self.config = config
self.name = name
self.default = default
def get(self,default=None):
v = self.config.get(self.name, default)
if v is 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)
class BalPlugin(BasePlugin): class BalPlugin(BasePlugin):
LATEST_VERSION = '1' LOCKTIME_TIME = "bal_locktime_time"
KNOWN_VERSIONS = ('0', '1') LOCKTIME_BLOCKS = "bal_locktime_blocks"
assert LATEST_VERSION in KNOWN_VERSIONS LOCKTIMEDELTA_TIME = "bal_locktimedelta_time"
def version(): LOCKTIMEDELTA_BLOCKS = "bal_locktimedelta_blocks"
try: TX_FEES = "bal_tx_fees"
f="" BROADCAST = "bal_broadcast"
with open("VERSION","r") as f: ASK_BROADCAST = "bal_ask_broadcast"
f = str(f.readline()) INVALIDATE = "bal_invalidate"
return f ASK_INVALIDATE = "bal_ask_invalidate"
except: PREVIEW = "bal_preview"
return "unknown" SAVE_TXS = "bal_save_txs"
SIZE = (159, 97) WILLEXECUTORS = "bal_willexecutors"
PING_WILLEXECUTORS = "bal_ping_willexecutors"
ASK_PING_WILLEXECUTORS = "bal_ask_ping_willexecutors"
NO_WILLEXECUTOR = "bal_no_willexecutor"
HIDE_REPLACED = "bal_hide_replaced"
HIDE_INVALIDATED = "bal_hide_invalidated"
ALLOW_REPUSH = "bal_allow_repush"
def __init__(self, parent, config, name):
self.logger = get_logger(__name__)
BasePlugin.__init__(self, parent, config, name)
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])
#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) DEFAULT_SETTINGS={
self.LOCKTIME_TIME = BalConfig(config, "bal_locktime_time", 90) LOCKTIME_TIME: 90,
self.LOCKTIME_BLOCKS = BalConfig(config, "bal_locktime_blocks", 144*90) LOCKTIME_BLOCKS: 144*90,
self.LOCKTIMEDELTA_TIME = BalConfig(config, "bal_locktimedelta_time", 7) LOCKTIMEDELTA_TIME: 7,
self.LOCKTIMEDELTA_BLOCKS = BalConfig(config, "bal_locktimedelta_blocks", 144*7) LOCKTIMEDELTA_BLOCKS:144*7,
self.ENABLE_MULTIVERSE = BalConfig(config, "bal_enable_multiverse", False) TX_FEES: 100,
self.TX_FEES = BalConfig(config, "bal_tx_fees", 100) BROADCAST: True,
self.INVALIDATE = BalConfig(config, "bal_invalidate", True) ASK_BROADCAST: True,
self.ASK_INVALIDATE = BalConfig(config, "bal_ask_invalidate", True) INVALIDATE: True,
self.PREVIEW = BalConfig(config, "bal_preview", True) ASK_INVALIDATE: True,
self.SAVE_TXS = BalConfig(config, "bal_save_txs", True) PREVIEW: True,
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", True) SAVE_TXS: True,
self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True) PING_WILLEXECUTORS: False,
self.ASK_PING_WILLEXECUTORS = BalConfig(config, "bal_ask_ping_willexecutors", True) ASK_PING_WILLEXECUTORS: False,
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True) NO_WILLEXECUTOR: False,
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True) HIDE_REPLACED:True,
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True) HIDE_INVALIDATED:True,
self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True) ALLOW_REPUSH: False,
self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True) WILLEXECUTORS: {
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", { 'http://bitcoin-after.life:9137': {
"mainnet": {
'https://we.bitcoin-after.life': {
"base_fee": 100000, "base_fee": 100000,
"status": "New", "status": "New",
"info":"Bitcoin After Life Will Executor", "info":"Bitcoin After Life Will Executor",
"address":"bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", "address":"bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7",
"selected":True "selected":True
} }
},
} }
})
self.WILL_SETTINGS = BalConfig(config, "bal_will_settings", {
'tx_fees':100,
'threshold':'180d',
'locktime':'1y',
})
LATEST_VERSION = '1'
KNOWN_VERSIONS = ('0', '1')
assert LATEST_VERSION in KNOWN_VERSIONS
self._hide_invalidated= self.HIDE_INVALIDATED.get() SIZE = (159, 97)
self._hide_replaced= self.HIDE_REPLACED.get()
def __init__(self, parent, config, name):
self.logger= get_logger(__name__)
BasePlugin.__init__(self, parent, config, name)
self.base_dir = os.path.join(config.electrum_path(), 'bal')
self.logger.info(self.base_dir)
self.parent = parent
self.config = config
self.name = name
self._hide_invalidated= self.config_get(self.HIDE_INVALIDATED)
self._hide_replaced= self.config_get(self.HIDE_REPLACED)
self.plugin_dir = os.path.split(os.path.realpath(__file__))[0]
def resource_path(self,*parts): def resource_path(self,*parts):
return os.path.join(self.plugin_dir, *parts) return os.path.join(self.plugin_dir, *parts)
def config_get(self,key):
v = self.config.get(key,None)
if v is None:
self.config.set_key(key,self.DEFAULT_SETTINGS[key],save=True)
v = self.DEFAULT_SETTINGS[key]
return v
def hide_invalidated(self): def hide_invalidated(self):
self._hide_invalidated = not self._hide_invalidated self._hide_invalidated = not self._hide_invalidated
self.HIDE_INVALIDATED.set(self._hide_invalidated) self.config.set_key(BalPlugin.HIDE_INVALIDATED,self.hide_invalidated,save=True)
def hide_replaced(self): def hide_replaced(self):
self._hide_replaced = not self._hide_replaced self._hide_replaced = not self._hide_replaced
self.HIDE_REPLACED.set(self._hide_replaced) self.config.set_key(BalPlugin.HIDE_REPLACED,self.hide_invalidated,save=True)
def default_will_settings(self):
return {
'tx_fees':100,
'threshold':'180d',
'locktime':'1y',
}
def validate_will_settings(self,will_settings): def validate_will_settings(self,will_settings):
if int(will_settings.get('tx_fees',1))<1: if int(will_settings.get('tx_fees',1))<1:
will_settings['tx_fees']=1 will_settings['tx_fees']=1

View File

@ -2,7 +2,7 @@ import os
PLUGIN_DIR = os.path.split(os.path.realpath(__file__))[0] PLUGIN_DIR = os.path.split(os.path.realpath(__file__))[0]
DEFAULT_ICON = 'bal32x32.png' DEFAULT_ICON = 'bal32x32.png'
DEFAULT_ICON_PATH = '' DEFAULT_ICON_PATH = 'icons'
def icon_path(icon_basename: str = DEFAULT_ICON): def icon_path(icon_basename: str = DEFAULT_ICON):

View File

@ -95,8 +95,25 @@ class bal_checkbox(QCheckBox):
def __init__(self, plugin,variable,window=None): def __init__(self, plugin,variable,window=None):
QCheckBox.__init__(self) QCheckBox.__init__(self)
self.setChecked(plugin.config_get(variable)) self.setChecked(plugin.config_get(variable))
window=window
def on_check(v): def on_check(v):
plugin.config.set_key(variable, v == 2) plugin.config.set_key(variable, v == Qt.CheckState.Checked, save=True)
plugin.config_get(variable) if window:
plugin._hide_invalidated= plugin.config_get(plugin.HIDE_INVALIDATED)
plugin._hide_replaced= plugin.config_get(plugin.HIDE_REPLACED)
window.update_all()
self.stateChanged.connect(on_check) self.stateChanged.connect(on_check)
#TODO IMPLEMENT PREVIEW DIALOG
#tx list display txid, willexecutor, qrcode, button to sign
# :def preview_dialog(self, txs):
def preview_dialog(self, txs):
w=PreviewDialog(self,txs)
w.exec()
return w
def add_info_from_will(self,tx):
for input in tx.inputs():
pass

View File

@ -225,7 +225,7 @@ class BalCloseDialog(BalDialog):
#self._stopping=True #self._stopping=True
#self.on_success_phase2() #self.on_success_phase2()
# return # return
_logger.debug("have to sign {}".format(self.have_to_sign)) _logger.debug("have to sign",self.have_to_sign)
password=None password=None
if self.have_to_sign is None: if self.have_to_sign is None:
self.msg_set_invalidating() self.msg_set_invalidating()

View File

@ -199,6 +199,7 @@ class WillExecutorDialog(BalDialog,MessageBoxMixin):
def __init__(self, bal_window): def __init__(self, bal_window):
BalDialog.__init__(self,bal_window.window) BalDialog.__init__(self,bal_window.window)
self.bal_plugin = bal_window.bal_plugin self.bal_plugin = bal_window.bal_plugin
self.gui_object = self.bal_plugin.gui_object
self.config = self.bal_plugin.config self.config = self.bal_plugin.config
self.window = bal_window.window self.window = bal_window.window
self.bal_window = bal_window self.bal_window = bal_window

View File

@ -14,8 +14,9 @@ from electrum.transaction import PartialTxInput, PartialTxOutput,TxOutpoint,Part
import datetime import datetime
import urllib.request import urllib.request
import urllib.parse import urllib.parse
from .util import Util from .bal import BalPlugin
from .willexecutors import Willexecutors from . import util as Util
from . import willexecutors as Willexecutors
if TYPE_CHECKING: if TYPE_CHECKING:
from .wallet_db import WalletDB from .wallet_db import WalletDB
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
@ -37,7 +38,6 @@ def reduce_outputs(in_amount, out_amount, fee, outputs):
for output in outputs: for output in outputs:
output.value = math.floor((in_amount-fee)/out_amount * output.value) output.value = math.floor((in_amount-fee)/out_amount * output.value)
"""
#TODO: put this method inside wallet.db to replace or complete get_locktime_for_new_transaction #TODO: put this method inside wallet.db to replace or complete get_locktime_for_new_transaction
def get_current_height(network:'Network'): def get_current_height(network:'Network'):
#if no network or not up to date, just set locktime to zero #if no network or not up to date, just set locktime to zero
@ -57,7 +57,7 @@ def get_current_height(network:'Network'):
# discourage "fee sniping" # discourage "fee sniping"
height = min(chain_height, server_height) height = min(chain_height, server_height)
return height return height
"""
@ -386,7 +386,7 @@ class Heirs(dict, Logger):
utxos = wallet.get_utxos() utxos = wallet.get_utxos()
willexecutors = Willexecutors.get_willexecutors(bal_plugin) or {} willexecutors = Willexecutors.get_willexecutors(bal_plugin) or {}
self.decimal_point=bal_plugin.config.get_decimal_point() self.decimal_point=bal_plugin.config.get_decimal_point()
no_willexecutors = bal_plugin.NO_WILLEXECUTOR.get() no_willexecutors = bal_plugin.config_get(BalPlugin.NO_WILLEXECUTOR)
for utxo in utxos: for utxo in utxos:
if utxo.value_sats()> 0*tx_fees: if utxo.value_sats()> 0*tx_fees:
balance += utxo.value_sats() balance += utxo.value_sats()
@ -563,7 +563,10 @@ class Heirs(dict, Logger):
def validate_amount(amount): def validate_amount(amount):
try: try:
famount = float(amount[:-1]) if Util.is_perc(amount) else float(amount) if Util.is_perc(amount):
famount = float(amount[:-1])
else:
famount = float(amount)
if famount <= 0.00000001: if famount <= 0.00000001:
raise AmountNotValid(f"amount have to be positive {famount} < 0") raise AmountNotValid(f"amount have to be positive {famount} < 0")
except Exception as e: except Exception as e:
@ -572,8 +575,9 @@ class Heirs(dict, Logger):
def validate_locktime(locktime,timestamp_to_check=False): def validate_locktime(locktime,timestamp_to_check=False):
try: try:
locktime = Util.parse_locktime_string(locktime,None)
if timestamp_to_check: if timestamp_to_check:
if Util.parse_locktime_string(locktime,None) < timestamp_to_check: if locktime < timestamp_to_check:
raise HeirExpiredException() raise HeirExpiredException()
except Exception as e: except Exception as e:
raise LocktimeNotValid(f"locktime string not properly formatted, {e}") raise LocktimeNotValid(f"locktime string not properly formatted, {e}")

View File

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

2674
qt.py

File diff suppressed because it is too large Load Diff

173
util.py
View File

@ -8,8 +8,7 @@ import urllib.parse
from electrum.util import write_json_file,FileImportFailed,FileExportFailed from electrum.util import write_json_file,FileImportFailed,FileExportFailed
LOCKTIME_THRESHOLD = 500000000 LOCKTIME_THRESHOLD = 500000000
class Util: def locktime_to_str(locktime):
def locktime_to_str(locktime):
try: try:
locktime=int(locktime) locktime=int(locktime)
if locktime > LOCKTIME_THRESHOLD: if locktime > LOCKTIME_THRESHOLD:
@ -17,26 +16,29 @@ class Util:
return dt return dt
except Exception as e: except Exception as e:
#print(e)
pass pass
return str(locktime) return str(locktime)
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: return int(locktime) else: return int(locktime)
except Exception as e: except Exception as e:
pass pass
#print(e)
dt_object = datetime.fromisoformat(locktime) dt_object = datetime.fromisoformat(locktime)
timestamp = dt_object.timestamp() timestamp = dt_object.timestamp()
return int(timestamp) return int(timestamp)
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 as e:
pass pass
#print("parse_locktime_string",e)
try: try:
now = datetime.now() now = datetime.now()
if locktime[-1] == 'y': if locktime[-1] == 'y':
@ -47,19 +49,20 @@ class Util:
locktime = int(locktime[:-1]) locktime = int(locktime[:-1])
height = 0 height = 0
if w: if w:
height = Util.get_current_height(w.network) height = get_current_height(w.network)
locktime+=int(height) locktime+=int(height)
return int(locktime) return int(locktime)
except Exception as e: except Exception as e:
pass print("parse_locktime_string",e)
#raise e
return 0 return 0
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(seconds + minutes*60 + hours*60*60 + days*60*60*24 + blocks * 600) return int(seconds + minutes*60 + hours*60*60 + days*60*60*24 + blocks * 600)
def encode_amount(amount, decimal_point): def encode_amount(amount, decimal_point):
if Util.is_perc(amount): if is_perc(amount):
return amount return amount
else: else:
try: try:
@ -67,21 +70,21 @@ class Util:
except: except:
return 0 return 0
def decode_amount(amount,decimal_point): def decode_amount(amount,decimal_point):
if Util.is_perc(amount): if is_perc(amount):
return amount return amount
else: else:
num=8-decimal_point num=8-decimal_point
basestr="{{:0{}.{}f}}".format(num,num) basestr="{{:0{}.{}f}}".format(num,num)
return "{:08.8f}".format(float(amount)/pow(10,decimal_point)) return "{:08.8f}".format(float(amount)/pow(10,decimal_point))
def is_perc(value): def is_perc(value):
try: try:
return value[-1] == '%' return value[-1] == '%'
except: except:
return False return False
def cmp_array(heira,heirb): def cmp_array(heira,heirb):
try: try:
if not len(heira) == len(heirb): if not len(heira) == len(heirb):
return False return False
@ -92,12 +95,12 @@ class Util:
except: except:
return False return False
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
def cmp_willexecutor(willexecutora,willexecutorb): def cmp_willexecutor(willexecutora,willexecutorb):
if willexecutora == willexecutorb: if willexecutora == willexecutorb:
return True return True
try: try:
@ -107,7 +110,8 @@ class Util:
return False return False
return False return False
def search_heir_by_values(heirs,heir,values): def search_heir_by_values(heirs,heir,values):
#print()
for h,v in heirs.items(): for h,v in heirs.items():
found = False found = False
for val in values: for val in values:
@ -118,86 +122,98 @@ class Util:
return h return h
return False return False
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
def cmp_heirs_by_values(heirsa,heirsb,values,exclude_willexecutors=False,reverse = True): def cmp_heirs_by_values(heirsa,heirsb,values,exclude_willexecutors=False,reverse = True):
for heira in heirsa: for heira in heirsa:
if (exclude_willexecutors and not "w!ll3x3c\"" in heira) or not exclude_willexecutors: if (exclude_willexecutors and not "w!ll3x3c\"" in heira) or not exclude_willexecutors:
found = False found = False
for heirb in heirsb: for heirb in heirsb:
if Util.cmp_heir_by_values(heirsa[heira],heirsb[heirb],values): if cmp_heir_by_values(heirsa[heira],heirsb[heirb],values):
found=True found=True
if not found: if not found:
#print(f"not_found {heira}--{heirsa[heira]}")
return False return False
if reverse: if reverse:
return Util.cmp_heirs_by_values(heirsb,heirsa,values,exclude_willexecutors=exclude_willexecutors,reverse=False) return cmp_heirs_by_values(heirsb,heirsa,values,exclude_willexecutors=exclude_willexecutors,reverse=False)
else: else:
return True return True
def cmp_heirs(heirsa,heirsb,cmp_function = lambda x,y: x[0]==y[0] and x[3]==y[3],reverse=True): def cmp_heirs(heirsa,heirsb,cmp_function = lambda x,y: x[0]==y[0] and x[3]==y[3],reverse=True):
try: try:
for heir in heirsa: for heir in heirsa:
if not "w!ll3x3c\"" in heir: if not "w!ll3x3c\"" in heir:
if not heir in heirsb or not cmp_function(heirsa[heir],heirsb[heir]): if not heir in heirsb or not cmp_function(heirsa[heir],heirsb[heir]):
if not Util.search_heir_by_values(heirsb,heirsa[heir],[0,3]): if not search_heir_by_values(heirsb,heirsa[heir],[0,3]):
return False return False
if reverse: if reverse:
return Util.cmp_heirs(heirsb,heirsa,cmp_function,False) return cmp_heirs(heirsb,heirsa,cmp_function,False)
else: else:
return True return True
except Exception as e: except Exception as e:
raise e raise e
return False return False
def cmp_inputs(inputsa,inputsb): def cmp_inputs(inputsa,inputsb):
if len(inputsa) != len(inputsb): if len(inputsa) != len(inputsb):
return False return False
for inputa in inputsa: for inputa in inputsa:
if not Util.in_utxo(inputa,inputsb): if not in_utxo(inputa,inputsb):
return False return False
return True return True
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
for outputa in outputsa: for outputa in outputsa:
if not Util.cmp_output(outputa,willexecutor_output): if not cmp_output(outputa,willexecutor_output):
if not Util.in_output(outputa,outputsb): if not in_output(outputa,outputsb):
return False return False
return True return True
def cmp_txs(txa,txb): def cmp_txs(txa,txb):
if not Util.cmp_inputs(txa.inputs(),txb.inputs()): if not cmp_inputs(txa.inputs(),txb.inputs()):
return False return False
if not Util.cmp_outputs(txa.outputs(),txb.outputs()): if not cmp_outputs(txa.outputs(),txb.outputs()):
return False return False
return True return True
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
#if len(outputsa) != len(outputsb):
# print("outputlen is different")
# return False
for outa in outputsa: for outa in outputsa:
same_amount,same_address = Util.in_output(outa,txb.outputs()) same_amount,same_address = in_output(outa,txb.outputs())
if not (same_amount or same_address): if not (same_amount or same_address):
#print("outa notin txb", same_amount,same_address)
return False return False
if same_amount and same_address: if same_amount and same_address:
value_amount+=outa.value value_amount+=outa.value
if same_amount: if same_amount:
pass pass
#print("same amount")
if same_address: if same_address:
pass pass
#print("same address")
return value_amount return value_amount
#not needed
#for outb in outputsb:
# if not in_output(outb,txa.outputs()):
# print("outb notin txb")
# return False
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)
if locktime > LOCKTIME_THRESHOLD and locktime > timestamp_to_check: if locktime > LOCKTIME_THRESHOLD and locktime > timestamp_to_check:
@ -207,7 +223,7 @@ class Util:
else: else:
return False return False
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
if locktime> LOCKTIME_THRESHOLD: if locktime> LOCKTIME_THRESHOLD:
@ -223,13 +239,13 @@ class Util:
out = 1 out = 1
return out return out
def cmp_locktime(locktimea,locktimeb): def cmp_locktime(locktimea,locktimeb):
if locktimea==locktimeb: if locktimea==locktimeb:
return 0 return 0
strlocktime = str(locktimea) strlocktime = str(locktimea)
strlocktimeb = str(locktimeb) strlocktimeb = str(locktimeb)
intlocktimea = Util.str_to_locktime(strlocktimea) intlocktimea = str_to_locktime(strlocktimea)
intlocktimeb = Util.str_to_locktime(strlocktimeb) intlocktimeb = 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])
@ -237,22 +253,23 @@ class Util:
return int(locktimea)-(locktimeb) return int(locktimea)-(locktimeb)
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
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()
def get_lowest_locktimes(locktimes): def get_lowest_locktimes(locktimes):
sorted_timestamp=[] sorted_timestamp=[]
sorted_block=[] sorted_block=[]
for l in locktimes: for l in locktimes:
l=Util.parse_locktime_string(l) #print("locktime:",parse_locktime_string(l))
l=parse_locktime_string(l)
if l < LOCKTIME_THRESHOLD: if l < LOCKTIME_THRESHOLD:
bisect.insort(sorted_block,l) bisect.insort(sorted_block,l)
else: else:
@ -260,83 +277,86 @@ class Util:
return sorted(sorted_timestamp), sorted(sorted_block) return sorted(sorted_timestamp), sorted(sorted_block)
def get_lowest_locktimes_from_will(will): def get_lowest_locktimes_from_will(will):
return Util.get_lowest_locktimes(Util.get_locktimes(will)) return get_lowest_locktimes(get_locktimes(will))
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 cmp_txs(w['tx'],tx['tx']):
return wid,w return wid,w
return None, None return None, None
def invalidate_will(will): def invalidate_will(will):
raise Exception("not implemented") raise Exception("not implemented")
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():
utxos+=willitem['tx'].inputs() utxos+=willitem['tx'].inputs()
return utxos return utxos
def utxo_to_str(utxo): def utxo_to_str(utxo):
try: return utxo.to_str() try: return utxo.to_str()
except Exception as e: pass except Exception as e: pass
try: return utxo.prevout.to_str() try: return utxo.prevout.to_str()
except Exception as e: pass except Exception as e: pass
return str(utxo) return str(utxo)
def cmp_utxo(utxoa,utxob): def cmp_utxo(utxoa,utxob):
utxoa=Util.utxo_to_str(utxoa) utxoa=utxo_to_str(utxoa)
utxob=Util.utxo_to_str(utxob) utxob=utxo_to_str(utxob)
if utxoa == utxob: if utxoa == utxob:
#if utxoa.prevout.txid==utxob.prevout.txid and utxoa.prevout.out_idx == utxob.prevout.out_idx:
return True return True
else: else:
return False return False
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 cmp_utxo(s_u,utxo):
return True return True
return False return False
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
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
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 cmp_output(s_o,output):
return True return True
return False return False
#check all output with the same amount if none have the same address it can be a change #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 true same address same amount
#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
def din_output(out,outputs): def din_output(out,outputs):
same_amount=[] same_amount=[]
for s_o in outputs: for s_o in outputs:
if int(out.value) == int(s_o.value): if int(out.value) == int(s_o.value):
same_amount.append(s_o) same_amount.append(s_o)
if out.address==s_o.address: if out.address==s_o.address:
#print("SAME_:",out.address,s_o.address)
return True, True return True, True
else: else:
pass pass
#print("NOT SAME_:",out.address,s_o.address)
if len(same_amount)>0: if len(same_amount)>0:
return True, False return True, False
else:return False, False else:return False, False
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():
change_addresses = wallet.get_change_addresses_for_new_transaction() change_addresses = wallet.get_change_addresses_for_new_transaction()
@ -345,7 +365,7 @@ class Util:
return out return out
def get_current_height(network:'Network'): def get_current_height(network:'Network'):
#if no network or not up to date, just set locktime to zero #if no network or not up to date, just set locktime to zero
if not network: if not network:
return 0 return 0
@ -365,7 +385,7 @@ class Util:
return height return height
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 not var is None:
try: try:
@ -395,24 +415,25 @@ class Util:
print(f"---end {name}---") print(f"---end {name}---")
def print_utxo(utxo, name = ""): def print_utxo(utxo, name = ""):
print(f"---utxo-{name}---") print(f"---utxo-{name}---")
Util.print_var(utxo,name) print_var(utxo,name)
Util.print_prevout(utxo.prevout,name) print_prevout(utxo.prevout,name)
Util.print_var(utxo.script_sig,f"{name}-script-sig") print_var(utxo.script_sig,f"{name}-script-sig")
Util.print_var(utxo.witness,f"{name}-witness") print_var(utxo.witness,f"{name}-witness")
#print("madonnamaiala_TXInput__scriptpubkey:",utxo._TXInput__scriptpubkey)
print("_TxInput__address:",utxo._TxInput__address) print("_TxInput__address:",utxo._TxInput__address)
print("_TxInput__scriptpubkey:",utxo._TxInput__scriptpubkey) print("_TxInput__scriptpubkey:",utxo._TxInput__scriptpubkey)
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}---")
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") print_var(prevout,f"{name}-prevout")
Util.print_var(prevout._asdict()) print_var(prevout._asdict())
print(f"---prevout-end {name}---") print(f"---prevout-end {name}---")
def export_meta_gui(electrum_window: 'ElectrumWindow', title, exporter): def export_meta_gui(electrum_window: 'ElectrumWindow', title, exporter):
filter_ = "All files (*)" filter_ = "All files (*)"
filename = getSaveFileName( filename = getSaveFileName(
parent=electrum_window, parent=electrum_window,
@ -432,6 +453,6 @@ class Util:
.format(title, str(filename))) .format(title, str(filename)))
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

206
will.py
View File

@ -1,7 +1,7 @@
import copy import copy
from .willexecutors import Willexecutors from . import willexecutors as Willexecutors
from .util import Util from . import util as Util
from electrum.i18n import _ from electrum.i18n import _
@ -15,9 +15,8 @@ MIN_LOCKTIME = 1
MIN_BLOCK = 1 MIN_BLOCK = 1
_logger = get_logger(__name__) _logger = get_logger(__name__)
class Will: #return an array with the list of children
#return an array with the list of children def get_children(will,willid):
def get_children(will,willid):
out = [] out = []
for _id in will: for _id in will:
inputs = will[_id].tx.inputs() inputs = will[_id].tx.inputs()
@ -27,26 +26,26 @@ class Will:
out.append([_id,idi,_input.prevout.out_idx]) out.append([_id,idi,_input.prevout.out_idx])
return out return out
#build a tree with parent transactions #build a tree with parent transactions
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 = get_children(will,willid)
for child in will[willid].children: for child in will[willid].children:
if not will[child[0]].father: if not will[child[0]].father:
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
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)
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
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():
if will[w]['tx'].txid() != tx.txid(): if will[w]['tx'].txid() != tx.txid():
@ -54,7 +53,7 @@ class Will:
return will[w]['tx'] return will[w]['tx']
return False return False
def get_tx_from_any(x): def get_tx_from_any(x):
try: try:
a=str(x) a=str(x)
return tx_from_any(a) return tx_from_any(a)
@ -64,16 +63,14 @@ class Will:
return x return x
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 = get_tx_from_any(will[wid].tx)
if wallet: if wallet:
will[wid].tx.add_info_from_wallet(wallet) will[wid].tx.add_info_from_wallet(wallet)
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:
@ -86,15 +83,12 @@ 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 = {}): def normalize_will(will,wallet = None,others_inputs = {}):
to_delete = [] to_delete = []
to_add = {} to_add = {}
#add info from wallet #add info from wallet
willitems={}
for wid in will: for wid in will:
Will.add_info_from_will(will,wid,wallet) add_info_from_will(will,wid,wallet)
willitems[wid]=WillItem(will[wid])
will=willitems
errors ={} errors ={}
for wid in will: for wid in will:
@ -115,10 +109,10 @@ class Will:
outputs = will[wid].tx.outputs() outputs = will[wid].tx.outputs()
ow=will[wid] ow=will[wid]
ow.normalize_locktime(others_inputs) ow.normalize_locktime(others_inputs)
will[wid]=WillItem(ow.to_dict()) will[wid]=ow.to_dict()
for i in range(0,len(outputs)): for i in range(0,len(outputs)):
Will.change_input(will,wid,i,outputs[i],others_inputs,to_delete,to_add) change_input(will,wid,i,outputs[i],others_inputs,to_delete,to_add)
to_delete.append(wid) to_delete.append(wid)
to_add[ow.tx.txid()]=ow.to_dict() to_add[ow.tx.txid()]=ow.to_dict()
@ -133,7 +127,7 @@ class Will:
if wid in will: if wid in will:
del will[wid] del will[wid]
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)
inp._trusted_value_sats = change.value inp._trusted_value_sats = change.value
@ -143,20 +137,27 @@ class Will:
inp._TxInput__value_sats = change.value inp._TxInput__value_sats = change.value
return inp return inp
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):
if Util.cmp_heirs_by_values(ow.heirs,nw.heirs,[0,1],exclude_willexecutors = True): if Util.cmp_heirs_by_values(ow.heirs,nw.heirs,[0,1],exclude_willexecutors = True):
print("same heirs",ow._id,nw._id)
if nw.we and ow.we: if nw.we and ow.we:
if ow.we['url'] == nw.we['url']: if ow.we['url'] == nw.we['url']:
print("same willexecutors", ow.we['url'],nw.we['url'])
if int(ow.we['base_fee'])>int(nw.we['base_fee']): if int(ow.we['base_fee'])>int(nw.we['base_fee']):
print("anticipate")
return anticipate return anticipate
else: else:
if int(ow.tx_fees) != int(nw.tx_fees): if int(ow.tx_fees) != int(nw.tx_fees):
return anticipate return anticipate
else: else:
print("keep the same")
#_logger.debug("ow,base fee > nw.base_fee")
ow.tx.locktime ow.tx.locktime
else: else:
#_logger.debug("ow.we['url']({ow.we['url']}) == nw.we['url']({nw.we['url']})")
print("keep the same")
ow.tx.locktime ow.tx.locktime
else: else:
if nw.we == ow.we: if nw.we == ow.we:
@ -171,7 +172,7 @@ class Will:
return 4294967295+1 return 4294967295+1
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()
if otxid != ntxid: if otxid != ntxid:
@ -187,7 +188,7 @@ class Will:
if isinstance(w.tx,Transaction): if isinstance(w.tx,Transaction):
will[wid].tx = PartialTransaction.from_tx(w.tx) will[wid].tx = PartialTransaction.from_tx(w.tx)
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]=new_input(wid,idx,change)
found = True found = True
if found == True: if found == True:
pass pass
@ -198,9 +199,9 @@ class Will:
to_append[new_txid]=will[wid] to_append[new_txid]=will[wid]
outputs = will[wid].tx.outputs() outputs = will[wid].tx.outputs()
for i in range(0,len(outputs)): for i in range(0,len(outputs)):
Will.change_input(will, wid, i, outputs[i],others_inputs,to_delete,to_append) change_input(will, wid, i, outputs[i],others_inputs,to_delete,to_append)
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():
if not only_valid or wi.get_status('VALID'): if not only_valid or wi.get_status('VALID'):
@ -214,7 +215,7 @@ class Will:
all_inputs[prevout_str].append(inp) all_inputs[prevout_str].append(inp)
return all_inputs return all_inputs
def get_all_inputs_min_locktime(all_inputs): def get_all_inputs_min_locktime(all_inputs):
all_inputs_min_locktime = {} all_inputs_min_locktime = {}
for i,values in all_inputs.items(): for i,values in all_inputs.items():
@ -229,11 +230,11 @@ class Will:
return all_inputs_min_locktime return all_inputs_min_locktime
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 = 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) or nwi.search_anticipate(old_inputs):
if nid != nwi.tx.txid(): if nid != nwi.tx.txid():
@ -242,7 +243,7 @@ class Will:
to_append[nwi.tx.txid()] = nwi to_append[nwi.tx.txid()] = nwi
outputs = nwi.tx.outputs() outputs = nwi.tx.outputs()
for i in range(0,len(outputs)): for i in range(0,len(outputs)):
Will.change_input(will,nid,i,outputs[i],new_inputs,to_delete,to_append) change_input(will,nid,i,outputs[i],new_inputs,to_delete,to_append)
for w in to_delete: for w in to_delete:
@ -253,25 +254,25 @@ class Will:
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) search_anticipate_rec(will,old_inputs)
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 = 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 = get_all_inputs_min_locktime(all_old_inputs)
all_new_inputs = Will.get_all_inputs(new_will) all_new_inputs = 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) search_anticipate_rec(new_will,all_old_inputs)
other_inputs = Will.get_all_inputs(old_will,{}) other_inputs = get_all_inputs(old_will,{})
try: try:
Will.normalize_will(new_will,others_inputs=other_inputs) normalize_will(new_will,others_inputs=other_inputs)
except Exception as e: except Exception as e:
raise e raise e
for oid in Will.only_valid(old_will): for oid in only_valid(old_will):
if oid in new_will: if oid in new_will:
new_heirs = new_will[oid].heirs new_heirs = new_will[oid].heirs
new_we = new_will[oid].we new_we = new_will[oid].we
@ -279,12 +280,14 @@ class Will:
new_will[oid]=old_will[oid] new_will[oid]=old_will[oid]
new_will[oid].heirs = new_heirs new_will[oid].heirs = new_heirs
new_will[oid].we = new_we new_will[oid].we = new_we
print(f"found {oid}")
continue continue
else: else:
print(f"not found {oid}")
continue continue
def get_higher_input_for_tx(will): def get_higher_input_for_tx(will):
out = {} out = {}
for wid in will: for wid in will:
wtx = will[wid].tx wtx = will[wid].tx
@ -297,9 +300,9 @@ class Will:
out[inp.prevout.to_str()] = inp out[inp.prevout.to_str()] = inp
return out return out
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 = only_valid_list(will)
inputs = Will.get_all_inputs(will_only_valid) inputs = get_all_inputs(will_only_valid)
utxos = wallet.get_utxos() utxos = wallet.get_utxos()
filtered_inputs = [] filtered_inputs = []
prevout_to_spend = [] prevout_to_spend = []
@ -334,20 +337,20 @@ class Will:
return tx return tx
else: else:
_logger.debug(f"balance({balance}) - fee({fee}) <=0") _logger.debug("balance - fee <=0")
pass pass
else: else:
_logger.debug("len utxo_to_spend <=0") _logger.debug("len utxo_to_spend <=0")
pass pass
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
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 = 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:
@ -367,6 +370,16 @@ class Will:
wi.set_status('CONFIRMED',True) wi.set_status('CONFIRMED',True)
else: else:
wi.set_status('INVALIDATED',True) wi.set_status('INVALIDATED',True)
#else:
# if prevout_id in will:
# wo = will[prevout_id]
# ttx= wallet.db.get_transaction(prevout_id)
# if ttx:
# _logger.error("transaction in wallet should be early detected")
# #wi.set_status('CONFIRMED',True)
# #else:
# # _logger.error("transaction not in will or utxo")
# # wi.set_status('INVALIDATED',True)
for child in wi.search(all_inputs): for child in wi.search(all_inputs):
if child.tx.locktime < wi.tx.locktime: if child.tx.locktime < wi.tx.locktime:
@ -375,38 +388,44 @@ class Will:
else: else:
pass pass
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=[]): def set_invalidate(wid,will=[]):
will[wid].set_status("INVALIDATED",True) will[wid].set_status("INVALIDATED",True)
if will[wid].children: if will[wid].children:
for c in self.children.items(): for c in self.children.items():
Will.set_invalidate(c[0],will) set_invalidate(c[0],will)
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
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:
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)
#print(utxos_list)
#print(inp_str)
#print(inp_str in utxos_list)
#print("notin: ",not inp_str in utxos_list)
if not inp_str in utxos_list: if not inp_str in utxos_list:
#print("quindi qua non ci arrivo?")
if wallet: if wallet:
height= Will.check_tx_height(w.tx,wallet) height= check_tx_height(w.tx,wallet)
if height < 0: if height < 0:
Will.set_invalidate(wid,willtree) #_logger.debug(f"heigth {height}")
set_invalidate(wid,willtree)
elif height == 0: elif height == 0:
w.set_status("PENDING",True) w.set_status("PENDING",True)
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:
@ -417,9 +436,9 @@ class Will:
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) reflect_to_children(wc)
def check_amounts(heirs,willexecutors,all_utxos,timestamp_to_check,dust): def check_amounts(heirs,willexecutors,all_utxos,timestamp_to_check,dust):
fixed_heirs,fixed_amount,perc_heirs,perc_amount = heirs.fixed_percent_lists_amount(timestamp_to_check,dust,reverse=True) fixed_heirs,fixed_amount,perc_heirs,perc_amount = heirs.fixed_percent_lists_amount(timestamp_to_check,dust,reverse=True)
wallet_balance = 0 wallet_balance = 0
for utxo in all_utxos: for utxo in all_utxos:
@ -437,33 +456,36 @@ class Will:
raise FixedAmountException(f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}") raise FixedAmountException(f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}")
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) add_willtree(will)
utxos_list= Will.utxos_strs(all_utxos) utxos_list= utxos_strs(all_utxos)
Will.check_invalidated(will,utxos_list,wallet) check_invalidated(will,utxos_list,wallet)
#from pprint import pprint
#for wid,w in will.items():
# pprint(w.to_dict())
all_inputs=Will.get_all_inputs(will,only_valid = True) all_inputs=get_all_inputs(will,only_valid = True)
all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_inputs) all_inputs_min_locktime = get_all_inputs_min_locktime(all_inputs)
Will.check_will_expired(all_inputs_min_locktime,block_to_check,timestamp_to_check) check_will_expired(all_inputs_min_locktime,block_to_check,timestamp_to_check)
all_inputs=Will.get_all_inputs(will,only_valid = True) all_inputs=get_all_inputs(will,only_valid = True)
Will.search_rai(all_inputs,all_utxos,will,wallet) search_rai(all_inputs,all_utxos,will,wallet)
def is_will_valid(will, block_to_check, timestamp_to_check, tx_fees, all_utxos,heirs={},willexecutors={},self_willexecutor=False, wallet=False, callback_not_valid_tx=None): def is_will_valid(will, block_to_check, timestamp_to_check, tx_fees, all_utxos,heirs={},willexecutors={},self_willexecutor=False, wallet=False, callback_not_valid_tx=None):
Will.check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check) check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check)
if heirs: if heirs:
if not Will.check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,timestamp_to_check,tx_fees): if not check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,timestamp_to_check,tx_fees):
raise NotCompleteWillException() raise NotCompleteWillException()
all_inputs=Will.get_all_inputs(will,only_valid = True) all_inputs=get_all_inputs(will,only_valid = True)
_logger.info('check all utxo in wallet are spent') _logger.info('check all utxo in wallet are spent')
if all_inputs: if all_inputs:
@ -477,7 +499,7 @@ class Will:
_logger.info('will ok') _logger.info('will ok')
return True return True
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():
for w in wid: for w in wid:
@ -490,7 +512,7 @@ class Will:
if locktime < int(timestamp_to_check): if locktime < int(timestamp_to_check):
raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}") raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}")
def check_all_input_spent_are_in_wallet(): def check_all_input_spent_are_in_wallet():
_logger.info("check all input spent are in wallet or valid txs") _logger.info("check all input spent are in wallet or valid txs")
for inp,ws in all_inputs.items(): for inp,ws in all_inputs.items():
if not Util.in_utxo(inp,all_utxos): if not Util.in_utxo(inp,all_utxos):
@ -502,14 +524,14 @@ class Will:
w[1].set_status('INVALIDATED',True) w[1].set_status('INVALIDATED',True)
def only_valid_list(will): def only_valid_list(will):
out={} out={}
for wid,w in will.items(): for wid,w in will.items():
if w.get_status('VALID'): if w.get_status('VALID'):
out[wid]=w out[wid]=w
return out return out
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():
wi = w wi = w
@ -517,17 +539,18 @@ class Will:
out.append(wid) out.append(wid)
return out return out
def check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,check_date,tx_fees): def check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,check_date,tx_fees):
_logger.debug("check willexecutors heirs") _logger.debug("check willexecutors heirs")
no_willexecutor = 0 no_willexecutor = 0
willexecutors_found = {} willexecutors_found = {}
heirs_found = {} heirs_found = {}
will_only_valid = Will.only_valid_list(will) will_only_valid = only_valid_list(will)
if len(will_only_valid)<1: if len(will_only_valid)<1:
return False return False
for wid in Will.only_valid_list(will): for wid in only_valid_list(will):
w = will[wid] w = will[wid]
if w.tx_fees != tx_fees: if w.tx_fees != tx_fees:
#w.set_status('VALID',False)
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]:
@ -607,6 +630,8 @@ class WillItem(Logger):
self.STATUS['PUSH_FAIL'][1] = False self.STATUS['PUSH_FAIL'][1] = False
self.STATUS['CHECK_FAIL'][1] = False self.STATUS['CHECK_FAIL'][1] = False
#if status in ['CHECK_FAIL']:
# self.STATUS['PUSHED'][1] = False
if status in ['CHECKED']: if status in ['CHECKED']:
self.STATUS['PUSHED'][1] = True self.STATUS['PUSHED'][1] = True
@ -621,7 +646,7 @@ class WillItem(Logger):
if isinstance(w,WillItem,): if isinstance(w,WillItem,):
self.__dict__ = w.__dict__.copy() self.__dict__ = w.__dict__.copy()
else: else:
self.tx = Will.get_tx_from_any(w['tx']) self.tx = get_tx_from_any(w['tx'])
self.heirs = w.get('heirs',None) self.heirs = w.get('heirs',None)
self.we = w.get('willexecutor',None) self.we = w.get('willexecutor',None)
self.status = w.get('status',None) self.status = w.get('status',None)
@ -675,11 +700,13 @@ class WillItem(Logger):
return str(self.to_dict()) return str(self.to_dict())
def set_anticipate(self, ow:'WillItem'): def set_anticipate(self, ow:'WillItem'):
nl = min(ow.tx.locktime,Will.check_anticipate(ow,self)) nl = min(ow.tx.locktime,check_anticipate(ow,self))
if int(nl) < self.tx.locktime: if int(nl) < self.tx.locktime:
#_logger.debug("actually anticipating")
self.tx.locktime = int(nl) self.tx.locktime = int(nl)
return True return True
else: else:
#_logger.debug("keeping the same locktime")
return False return False
@ -748,11 +775,10 @@ class WillItem(Logger):
else: else:
return "#ffffff" return "#ffffff"
class WillException(Exception):
class WillExpiredException(Exception):
pass pass
class WillExpiredException(WillException): class NotCompleteWillException(Exception):
pass
class NotCompleteWillException(WillException):
pass pass
class HeirChangeException(NotCompleteWillException): class HeirChangeException(NotCompleteWillException):
pass pass
@ -766,9 +792,9 @@ class NoWillExecutorNotPresent(NotCompleteWillException):
pass pass
class WillExecutorNotPresent(NotCompleteWillException): class WillExecutorNotPresent(NotCompleteWillException):
pass pass
class NoHeirsException(WillException): class NoHeirsException(Exception):
pass pass
class AmountException(WillException): class AmountException(Exception):
pass pass
class PercAmountException(AmountException): class PercAmountException(AmountException):
pass pass

View File

@ -8,55 +8,41 @@ from electrum import constants
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.gui.qt.util import WaitingDialog from electrum.gui.qt.util import WaitingDialog
from electrum.i18n import _ from electrum.i18n import _
from .bal import BalPlugin
from .util import Util from .balqt.baldialog import BalWaitingDialog
from . import util as Util
DEFAULT_TIMEOUT = 5 DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__) _logger = get_logger(__name__)
class Willexecutors:
def save(bal_plugin, willexecutors): def get_willexecutors(bal_plugin, update = False,bal_window=False,force=False,task=True):
aw=bal_plugin.WILLEXECUTORS.get() willexecutors = bal_plugin.config_get(bal_plugin.WILLEXECUTORS)
aw[constants.net.NET_NAME]=willexecutors
bal_plugin.WILLEXECUTORS.set(aw)
def get_willexecutors(bal_plugin, update = False,bal_window=False,force=False,task=True):
willexecutors = bal_plugin.WILLEXECUTORS.get()
willexecutors=willexecutors.get(constants.net.NET_NAME,{})
to_del=[]
for w in willexecutors: for w in willexecutors:
if not isinstance(willexecutors[w],dict): initialize_willexecutor(willexecutors[w],w)
to_del.append(w)
continue bal=bal_plugin.DEFAULT_SETTINGS[bal_plugin.WILLEXECUTORS]
Willexecutors.initialize_willexecutor(willexecutors[w],w)
for w in to_del:
print("ERROR: WILLEXECUTOR TO DELETE:", w)
del willexecutors[w]
bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME,{})
for bal_url,bal_executor in bal.items(): for bal_url,bal_executor in bal.items():
if not bal_url in willexecutors: if not bal_url in willexecutors:
_logger.debug(f"force add {bal_url} willexecutor") _logger.debug("replace bal")
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 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.config_get(bal_plugin.PING_WILLEXECUTORS) or force:
ping_willexecutors = True ping_willexecutors = True
if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force: if bal_plugin.config_get(bal_plugin.ASK_PING_WILLEXECUTORS) and not force:
if bal_window:
ping_willexecutors = bal_window.window.question(_("Contact willexecutors servers to update payment informations?")) ping_willexecutors = bal_window.window.question(_("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)
else: else:
bal_window.ping_willexecutors_task(willexecutors) bal_window.ping_willexecutors_task(willexecutors)
w_sorted = dict(sorted(willexecutors.items(), key=lambda w:w[1].get('sort',0),reverse=True)) return willexecutors
return w_sorted
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 not value is None:
@ -67,7 +53,7 @@ class Willexecutors:
willexecutor['selected']=False willexecutor['selected']=False
return False return False
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():
if willitem.get_status('VALID'): if willitem.get_status('VALID'):
@ -75,7 +61,7 @@ class Willexecutors:
if not willitem.get_status('PUSHED') or force: if not willitem.get_status('PUSHED') or force:
if willexecutor := willitem.we: if willexecutor := willitem.we:
url=willexecutor['url'] url=willexecutor['url']
if willexecutor and Willexecutors.is_selected(willexecutor): if willexecutor and is_selected(willexecutor):
if not url in willexecutors: if not url in willexecutors:
willexecutor['txs']="" willexecutor['txs']=""
willexecutor['txsids']=[] willexecutor['txsids']=[]
@ -86,26 +72,26 @@ 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 willexectors.items():
if Willexecutors.is_selected(willexecutor): if is_selected(willexecutor):
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 = 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 is_selected(willexecutor):
if 'txs' in willexecutor: if 'txs' in willexecutor:
Willexecutors.push_transactions_to_willexecutor(willexecutors[url]['txs'],url) push_transactions_to_willexecutor(willexecutors[url]['txs'],url)
def send_request(method, url, data=None, *, timeout=10): def send_request(method, url, data=None, *, timeout=10):
network = Network.get_instance() network = Network.get_instance()
if not network: if not network:
raise ErrorConnectingServer('You are offline.') raise ErrorConnectingServer('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'] = 'BalPlugin'
headers['Content-Type']='text/plain' headers['Content-Type']='text/plain'
try: try:
@ -113,13 +99,13 @@ class Willexecutors:
response = Network.send_http_on_proxy(method, url, response = Network.send_http_on_proxy(method, 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':
response = Network.send_http_on_proxy(method, url, response = Network.send_http_on_proxy(method, 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}")
@ -129,26 +115,25 @@ class Willexecutors:
else: else:
_logger.debug(f'--> {response}') _logger.debug(f'--> {response}')
return response return response
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 r['status'] = resp.status
r['selected']=Willexecutors.is_selected(willexecutor) r['selected']=is_selected(willexecutor)
r['url']=url r['url']=url
except: except:
pass pass
return r return r
class AlreadyPresentException(Exception): class AlreadyPresentException(Exception):
pass pass
def push_transactions_to_willexecutor(willexecutor): def push_transactions_to_willexecutor(willexecutor):
out=True out=True
try: try:
_logger.debug(f"willexecutor['txs']") _logger.debug(f"willexecutor['txs']")
if w:=Willexecutors.send_request('post', willexecutor['url']+"/"+constants.net.NET_NAME+"/pushtxs", data=willexecutor['txs'].encode('ascii')): if w:=send_request('post', willexecutor['url']+"/"+constants.net.NET_NAME+"/pushtxs", data=willexecutor['txs'].encode('ascii')):
willexecutor['broadcast_status'] = _("Success") willexecutor['broadcast_stauts'] = _("Success")
_logger.debug(f"pushed: {w}") _logger.debug(f"pushed: {w}")
if w !='thx': if w !='thx':
_logger.debug(f"error: {w}") _logger.debug(f"error: {w}")
@ -158,27 +143,23 @@ class Willexecutors:
except Exception as e: except Exception as e:
_logger.debug(f"error:{e}") _logger.debug(f"error:{e}")
if str(e) == "already present": if str(e) == "already present":
raise Willexecutors.AlreadyPresentException() raise AlreadyPresentException()
out=False out=False
willexecutor['broadcast_status'] = _("Failed") willexecutor['broadcast_stauts'] = _("Failed")
return out return out
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) get_info_task(url,we)
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 = send_request('get',url+"/"+constants.net.NET_NAME+"/info")
if constants.net.NET_NAME!="mainnet":
netname=constants.net.NET_NAME
w = Willexecutors.send_request('get',url+"/"+netname+"/info")
willexecutor['url']=url willexecutor['url']=url
willexecutor['status'] = w['status'] willexecutor['status'] = w['status']
willexecutor['base_fee'] = w['base_fee'] willexecutor['base_fee'] = w['base_fee']
@ -193,64 +174,30 @@ 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): def initialize_willexecutor(willexecutor,url,status=None,selected=None):
willexecutor['url']=url willexecutor['url']=url
if not status is None: if not status is None:
willexecutor['status'] = status willexecutor['status'] = status
willexecutor['selected'] = Willexecutors.is_selected(willexecutor,selected) willexecutor['selected'] = is_selected(willexecutor,selected)
def download_list(bal_plugin): def get_willexecutors_list_from_json(bal_plugin):
try:
l = Willexecutors.send_request('get',"https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100")
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 l
except Exception as e:
_logger.error(f"Failed to download willexecutors list: {e}")
return {}
def get_willexecutors_list_from_json(bal_plugin):
try: try:
with open("willexecutors.json") as f: with open("willexecutors.json") as f:
willexecutors = json.load(f) willexecutors = json.load(f)
for w in willexecutors: for w in 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.config.set_key(bal_plugin.WILLEXECUTORS,willexecutors,save=True)
return h return h
except Exception as e: except Exception as e:
_logger.error(f"error opening willexecutors json: {e}") _logger.error(f"errore aprendo willexecutors.json: {e}")
return {} return {}
def check_transaction(txid,url): def check_transaction(txid,url):
_logger.debug(f"{url}:{txid}") _logger.debug(f"{url}:{txid}")
try: try:
w = Willexecutors.send_request('post',url+"/searchtx",data=txid.encode('ascii')) w = send_request('post',url+"/searchtx",data=txid.encode('ascii'))
return w return w
except Exception as e: except Exception as e:
_logger.error(f"error contacting {url} for checking txs {e}") _logger.error(f"error contacting {url} for checking txs {e}")
raise e raise e
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):
we = WillExecutor(
d['url'],
d['base_fee'],
d['chain'],
d['info'],
d['version']
)