Compare commits
2 Commits
v0.2.3
...
56ebf760b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
56ebf760b3
|
|||
|
e5e342649f
|
96
bal.py
96
bal.py
@@ -1,33 +1,26 @@
|
|||||||
|
import random
|
||||||
import os
|
import os
|
||||||
#import random
|
import zipfile as zipfile_lib
|
||||||
#import zipfile as zipfile_lib
|
|
||||||
|
|
||||||
from electrum import json_db
|
|
||||||
from electrum.logging import get_logger
|
|
||||||
from electrum.plugin import BasePlugin
|
from electrum.plugin import BasePlugin
|
||||||
|
from electrum import json_db
|
||||||
from electrum.transaction import tx_from_any
|
from electrum.transaction import tx_from_any
|
||||||
|
|
||||||
|
import os
|
||||||
|
json_db.register_dict('heirs', tuple, None)
|
||||||
|
json_db.register_dict('will', lambda x: get_will(x), None)
|
||||||
|
json_db.register_dict('will_settings', lambda x: x, None)
|
||||||
|
|
||||||
def get_will_settings(x):
|
from electrum.logging import get_logger
|
||||||
#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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_will(x):
|
def get_will(x):
|
||||||
try:
|
try:
|
||||||
x["tx"] = tx_from_any(x["tx"])
|
x['tx']=tx_from_any(x['tx'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
class BalConfig():
|
||||||
class BalConfig:
|
|
||||||
def __init__(self, config, name, default):
|
def __init__(self, config, name, default):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.name = name
|
self.name = name
|
||||||
@@ -36,7 +29,7 @@ class BalConfig:
|
|||||||
def get(self,default=None):
|
def get(self,default=None):
|
||||||
v = self.config.get(self.name, default)
|
v = self.config.get(self.name, default)
|
||||||
if v is None:
|
if v is None:
|
||||||
if default is not None:
|
if not default is None:
|
||||||
v = default
|
v = default
|
||||||
else:
|
else:
|
||||||
v = self.default
|
v = self.default
|
||||||
@@ -45,27 +38,24 @@ class BalConfig:
|
|||||||
def set(self,value,save=True):
|
def set(self,value,save=True):
|
||||||
self.config.set_key(self.name,value,save=save)
|
self.config.set_key(self.name,value,save=save)
|
||||||
|
|
||||||
|
|
||||||
class BalPlugin(BasePlugin):
|
class BalPlugin(BasePlugin):
|
||||||
LATEST_VERSION = "1"
|
LATEST_VERSION = '1'
|
||||||
KNOWN_VERSIONS = ("0", "1")
|
KNOWN_VERSIONS = ('0', '1')
|
||||||
assert LATEST_VERSION in KNOWN_VERSIONS
|
assert LATEST_VERSION in KNOWN_VERSIONS
|
||||||
|
|
||||||
def version():
|
def version():
|
||||||
try:
|
try:
|
||||||
f=""
|
f=""
|
||||||
with open("VERSION", "r") as fi:
|
with open("VERSION","r") as f:
|
||||||
f = str(fi.readline())
|
f = str(f.readline())
|
||||||
return f
|
return f
|
||||||
except:
|
except:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
SIZE = (159, 97)
|
SIZE = (159, 97)
|
||||||
|
|
||||||
def __init__(self, parent, config, name):
|
def __init__(self, parent, config, name):
|
||||||
self.logger = get_logger(__name__)
|
self.logger = get_logger(__name__)
|
||||||
BasePlugin.__init__(self, parent, config, 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]
|
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__))
|
#print("real path",os.path.realpath(__file__))
|
||||||
@@ -74,7 +64,6 @@ class BalPlugin(BasePlugin):
|
|||||||
#print("suca:",zipfile)
|
#print("suca:",zipfile)
|
||||||
#print("plugin_dir:", self.plugin_dir)
|
#print("plugin_dir:", self.plugin_dir)
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, zipfile)
|
sys.path.insert(0, zipfile)
|
||||||
#print("sono state listate?")
|
#print("sono state listate?")
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
@@ -86,9 +75,7 @@ class BalPlugin(BasePlugin):
|
|||||||
self.LOCKTIME_TIME = BalConfig(config, "bal_locktime_time", 90)
|
self.LOCKTIME_TIME = BalConfig(config, "bal_locktime_time", 90)
|
||||||
self.LOCKTIME_BLOCKS = BalConfig(config, "bal_locktime_blocks", 144*90)
|
self.LOCKTIME_BLOCKS = BalConfig(config, "bal_locktime_blocks", 144*90)
|
||||||
self.LOCKTIMEDELTA_TIME = BalConfig(config, "bal_locktimedelta_time", 7)
|
self.LOCKTIMEDELTA_TIME = BalConfig(config, "bal_locktimedelta_time", 7)
|
||||||
self.LOCKTIMEDELTA_BLOCKS = BalConfig(
|
self.LOCKTIMEDELTA_BLOCKS = BalConfig(config, "bal_locktimedelta_blocks", 144*7)
|
||||||
config, "bal_locktimedelta_blocks", 144 * 7
|
|
||||||
)
|
|
||||||
self.ENABLE_MULTIVERSE = BalConfig(config, "bal_enable_multiverse", False)
|
self.ENABLE_MULTIVERSE = BalConfig(config, "bal_enable_multiverse", False)
|
||||||
self.TX_FEES = BalConfig(config, "bal_tx_fees", 100)
|
self.TX_FEES = BalConfig(config, "bal_tx_fees", 100)
|
||||||
self.INVALIDATE = BalConfig(config, "bal_invalidate", True)
|
self.INVALIDATE = BalConfig(config, "bal_invalidate", True)
|
||||||
@@ -97,42 +84,34 @@ class BalPlugin(BasePlugin):
|
|||||||
self.SAVE_TXS = BalConfig(config, "bal_save_txs", True)
|
self.SAVE_TXS = BalConfig(config, "bal_save_txs", True)
|
||||||
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", True)
|
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", True)
|
||||||
self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True)
|
self.PING_WILLEXECUTORS = BalConfig(config, "bal_ping_willexecutors", True)
|
||||||
self.ASK_PING_WILLEXECUTORS = BalConfig(
|
self.ASK_PING_WILLEXECUTORS = BalConfig(config, "bal_ask_ping_willexecutors", True)
|
||||||
config, "bal_ask_ping_willexecutors", True
|
|
||||||
)
|
|
||||||
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)
|
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)
|
||||||
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True)
|
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True)
|
||||||
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
|
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
|
||||||
self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True)
|
self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True)
|
||||||
self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True)
|
self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True)
|
||||||
self.WILLEXECUTORS = BalConfig(
|
self.WILLEXECUTORS = BalConfig(config, "bal_willexecutors", {
|
||||||
config,
|
|
||||||
"bal_willexecutors",
|
|
||||||
{
|
|
||||||
"mainnet": {
|
"mainnet": {
|
||||||
"https://we.bitcoin-after.life": {
|
'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", {
|
||||||
self.WILL_SETTINGS = BalConfig(
|
'tx_fees':100,
|
||||||
config,
|
'threshold':'180d',
|
||||||
"bal_will_settings",
|
'locktime':'1y',
|
||||||
{
|
})
|
||||||
"baltx_fees": 100,
|
|
||||||
"threshold": "180d",
|
|
||||||
"locktime": "1y",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
self._hide_invalidated= self.HIDE_INVALIDATED.get()
|
self._hide_invalidated= self.HIDE_INVALIDATED.get()
|
||||||
self._hide_replaced= self.HIDE_REPLACED.get()
|
self._hide_replaced= self.HIDE_REPLACED.get()
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@@ -145,16 +124,13 @@ class BalPlugin(BasePlugin):
|
|||||||
self.HIDE_REPLACED.set(self._hide_replaced)
|
self.HIDE_REPLACED.set(self._hide_replaced)
|
||||||
|
|
||||||
def validate_will_settings(self,will_settings):
|
def validate_will_settings(self,will_settings):
|
||||||
# print(type(will_settings))
|
if int(will_settings.get('tx_fees',1))<1:
|
||||||
# print(will_settings.get('baltx_fees',1),1)
|
will_settings['tx_fees']=1
|
||||||
if int(will_settings.get("baltx_fees", 1)) < 1:
|
if not will_settings.get('threshold'):
|
||||||
will_settings["baltx_fees"] = 1
|
will_settings['threshold']='180d'
|
||||||
if not will_settings.get("threshold"):
|
if not will_settings.get('locktime')=='':
|
||||||
will_settings["threshold"] = "180d"
|
will_settings['locktime']='1y'
|
||||||
if not will_settings.get("locktime") == "":
|
|
||||||
will_settings["locktime"] = "1y"
|
|
||||||
return will_settings
|
return will_settings
|
||||||
|
|
||||||
def default_will_settings(self):
|
def default_will_settings(self):
|
||||||
return {"baltx_fees": 100, "threshold": "180d", "locktime": "1y"}
|
return {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import os
|
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 = "icons"
|
DEFAULT_ICON_PATH = ''
|
||||||
|
|
||||||
|
|
||||||
def icon_path(icon_basename: str = DEFAULT_ICON):
|
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
|
return path
|
||||||
|
|
||||||
|
|
||||||
def resource_path(*parts):
|
def resource_path(*parts):
|
||||||
return os.path.join(PLUGIN_DIR, *parts)
|
return os.path.join(PLUGIN_DIR, *parts)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
89
balqt/amountedit.py
Normal file
89
balqt/amountedit.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from . import qt_resources
|
||||||
|
if qt_resources.QT_VERSION == 5:
|
||||||
|
from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy)
|
||||||
|
from PyQt5.QtGui import QPalette, QPainter
|
||||||
|
from PyQt5.QtCore import pyqtSignal, Qt, QSize
|
||||||
|
else:
|
||||||
|
from PyQt6.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy)
|
||||||
|
from PyQt6.QtGui import QPalette, QPainter
|
||||||
|
from PyQt6.QtCore import pyqtSignal, Qt, QSize
|
||||||
|
|
||||||
|
|
||||||
|
from electrum.util import (format_satoshis_plain, decimal_point_to_base_unit_name,
|
||||||
|
FEERATE_PRECISION, quantize_feerate, DECIMAL_POINT, UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE)
|
||||||
|
|
||||||
|
from electrum.gui.qt.amountedit import BTCAmountEdit, char_width_in_lineedit, ColorScheme
|
||||||
|
|
||||||
|
_NOT_GIVEN = object() # sentinel value
|
||||||
|
|
||||||
|
|
||||||
|
class PercAmountEdit(BTCAmountEdit):
|
||||||
|
def __init__(self, decimal_point, is_int=False, parent=None, *, max_amount=_NOT_GIVEN):
|
||||||
|
super().__init__(decimal_point, is_int, parent, max_amount=max_amount)
|
||||||
|
|
||||||
|
def numbify(self):
|
||||||
|
text = self.text().strip()
|
||||||
|
if text == '!':
|
||||||
|
self.shortcut.emit()
|
||||||
|
return
|
||||||
|
pos = self.cursorPosition()
|
||||||
|
chars = '0123456789%'
|
||||||
|
chars += DECIMAL_POINT
|
||||||
|
|
||||||
|
s = ''.join([i for i in text if i in chars])
|
||||||
|
|
||||||
|
if '%' in s:
|
||||||
|
self.is_perc=True
|
||||||
|
s=s.replace('%','')
|
||||||
|
else:
|
||||||
|
self.is_perc=False
|
||||||
|
|
||||||
|
if DECIMAL_POINT in s:
|
||||||
|
p = s.find(DECIMAL_POINT)
|
||||||
|
s = s.replace(DECIMAL_POINT, '')
|
||||||
|
s = s[:p] + DECIMAL_POINT + s[p:p+8]
|
||||||
|
if self.is_perc:
|
||||||
|
s+='%'
|
||||||
|
|
||||||
|
|
||||||
|
#if self.max_amount:
|
||||||
|
# if (amt := self._get_amount_from_text(s)) and amt >= self.max_amount:
|
||||||
|
# s = self._get_text_from_amount(self.max_amount)
|
||||||
|
self.setText(s)
|
||||||
|
# setText sets Modified to False. Instead we want to remember
|
||||||
|
# if updates were because of user modification.
|
||||||
|
self.setModified(self.hasFocus())
|
||||||
|
self.setCursorPosition(pos)
|
||||||
|
#if len(s>0)
|
||||||
|
# self.drawText("")
|
||||||
|
|
||||||
|
def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]:
|
||||||
|
try:
|
||||||
|
text = text.replace(DECIMAL_POINT, '.')
|
||||||
|
text = text.replace('%', '')
|
||||||
|
return (Decimal)(text)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_text_from_amount(self, amount):
|
||||||
|
out = super()._get_text_from_amount(amount)
|
||||||
|
if self.is_perc: out+='%'
|
||||||
|
return out
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
QLineEdit.paintEvent(self, event)
|
||||||
|
if self.base_unit:
|
||||||
|
panel = QStyleOptionFrame()
|
||||||
|
self.initStyleOption(panel)
|
||||||
|
textRect = self.style().subElementRect(QStyle.SubElement.SE_LineEditContents, panel, self)
|
||||||
|
textRect.adjust(2, 0, -10, 0)
|
||||||
|
painter = QPainter(self)
|
||||||
|
painter.setPen(ColorScheme.GRAY.as_color())
|
||||||
|
if len(self.text())==0:
|
||||||
|
painter.drawText(textRect, int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter), self.base_unit() + " or perc value")
|
||||||
|
|
||||||
102
balqt/baldialog.py
Normal file
102
balqt/baldialog.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
from typing import Callable,Any
|
||||||
|
|
||||||
|
from . import qt_resources
|
||||||
|
if qt_resources.QT_VERSION == 5:
|
||||||
|
from PyQt5.QtCore import Qt,pyqtSignal
|
||||||
|
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QCheckBox
|
||||||
|
else:
|
||||||
|
from PyQt6.QtCore import Qt,pyqtSignal
|
||||||
|
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QCheckBox
|
||||||
|
|
||||||
|
from electrum.gui.qt.util import WindowModalDialog, TaskThread
|
||||||
|
from electrum.i18n import _
|
||||||
|
from electrum.logging import get_logger
|
||||||
|
|
||||||
|
_logger = get_logger(__name__)
|
||||||
|
|
||||||
|
class BalDialog(WindowModalDialog):
|
||||||
|
|
||||||
|
def __init__(self,parent,title=None, icon = 'bal32x32.png'):
|
||||||
|
self.parent=parent
|
||||||
|
WindowModalDialog.__init__(self,self.parent,title)
|
||||||
|
self.setWindowIcon(qt_resources.read_QIcon(icon))
|
||||||
|
|
||||||
|
class BalWaitingDialog(BalDialog):
|
||||||
|
updatemessage=pyqtSignal([str], arguments=['message'])
|
||||||
|
def __init__(self, bal_window: 'BalWindow', message: str, task, on_success=None, on_error=None, on_cancel=None,exe=True):
|
||||||
|
assert bal_window
|
||||||
|
BalDialog.__init__(self, bal_window.window, _("Please wait"))
|
||||||
|
self.message_label = QLabel(message)
|
||||||
|
vbox = QVBoxLayout(self)
|
||||||
|
vbox.addWidget(self.message_label)
|
||||||
|
self.updatemessage.connect(self.update_message)
|
||||||
|
if on_cancel:
|
||||||
|
self.cancel_button = CancelButton(self)
|
||||||
|
self.cancel_button.clicked.connect(on_cancel)
|
||||||
|
vbox.addLayout(Buttons(self.cancel_button))
|
||||||
|
self.accepted.connect(self.on_accepted)
|
||||||
|
self.task=task
|
||||||
|
self.on_success = on_success
|
||||||
|
self.on_error = on_error
|
||||||
|
self.on_cancel = on_cancel
|
||||||
|
if exe:
|
||||||
|
self.exe()
|
||||||
|
|
||||||
|
def exe(self):
|
||||||
|
self.thread = TaskThread(self)
|
||||||
|
self.thread.finished.connect(self.deleteLater) # see #3956
|
||||||
|
self.thread.finished.connect(self.finished)
|
||||||
|
self.thread.add(self.task, self.on_success, self.accept, self.on_error)
|
||||||
|
self.exec()
|
||||||
|
|
||||||
|
def hello(self):
|
||||||
|
pass
|
||||||
|
def finished(self):
|
||||||
|
_logger.info("finished")
|
||||||
|
def wait(self):
|
||||||
|
self.thread.wait()
|
||||||
|
|
||||||
|
def on_accepted(self):
|
||||||
|
self.thread.stop()
|
||||||
|
def update_message(self,msg):
|
||||||
|
self.message_label.setText(msg)
|
||||||
|
|
||||||
|
def update(self, msg):
|
||||||
|
self.updatemessage.emit(msg)
|
||||||
|
|
||||||
|
def getText(self):
|
||||||
|
return self.message_label.text()
|
||||||
|
|
||||||
|
def closeEvent(self,event):
|
||||||
|
self.thread.stop()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BalBlockingWaitingDialog(BalDialog):
|
||||||
|
def __init__(self, bal_window: 'BalWindow', message: str, task: Callable[[], Any]):
|
||||||
|
BalDialog.__init__(self, bal_window, _("Please wait"))
|
||||||
|
self.message_label = QLabel(message)
|
||||||
|
vbox = QVBoxLayout(self)
|
||||||
|
vbox.addWidget(self.message_label)
|
||||||
|
self.finished.connect(self.deleteLater) # see #3956
|
||||||
|
# show popup
|
||||||
|
self.show()
|
||||||
|
# refresh GUI; needed for popup to appear and for message_label to get drawn
|
||||||
|
QCoreApplication.processEvents()
|
||||||
|
QCoreApplication.processEvents()
|
||||||
|
try:
|
||||||
|
# block and run given task
|
||||||
|
task()
|
||||||
|
finally:
|
||||||
|
# close popup
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
class bal_checkbox(QCheckBox):
|
||||||
|
def __init__(self, plugin,variable,window=None):
|
||||||
|
QCheckBox.__init__(self)
|
||||||
|
self.setChecked(plugin.config_get(variable))
|
||||||
|
def on_check(v):
|
||||||
|
plugin.config.set_key(variable, v == 2)
|
||||||
|
plugin.config_get(variable)
|
||||||
|
self.stateChanged.connect(on_check)
|
||||||
|
|
||||||
384
balqt/closedialog.py
Normal file
384
balqt/closedialog.py
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
from .baldialog import BalDialog
|
||||||
|
from . import qt_resources
|
||||||
|
|
||||||
|
if qt_resources.QT_VERSION == 5:
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QCheckBox,QWidget
|
||||||
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject,QEventLoop
|
||||||
|
else:
|
||||||
|
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject,QEventLoop
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QCheckBox, QWidget
|
||||||
|
import time
|
||||||
|
from electrum.i18n import _
|
||||||
|
from electrum.gui.qt.util import WindowModalDialog, TaskThread
|
||||||
|
from electrum.network import Network,TxBroadcastError, BestEffortRequestFailed
|
||||||
|
from electrum.logging import get_logger
|
||||||
|
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from .. import util as Util
|
||||||
|
from .. import will as Will
|
||||||
|
|
||||||
|
from .. import willexecutors as Willexecutors
|
||||||
|
_logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BalCloseDialog(BalDialog):
|
||||||
|
updatemessage=pyqtSignal()
|
||||||
|
def __init__(self,bal_window):
|
||||||
|
BalDialog.__init__(self,bal_window.window,"Closing BAL")
|
||||||
|
self.updatemessage.connect(self.update)
|
||||||
|
self.bal_window=bal_window
|
||||||
|
self.message_label = QLabel("Closing BAL:")
|
||||||
|
self.vbox = QVBoxLayout(self)
|
||||||
|
self.vbox.addWidget(self.message_label)
|
||||||
|
self.qwidget=QWidget()
|
||||||
|
self.vbox.addWidget(self.qwidget)
|
||||||
|
self.labels=[]
|
||||||
|
#self.checking.connect(self.msg_set_checking)
|
||||||
|
#self.invalidating.connect(self.msg_set_invalidating)
|
||||||
|
#self.building.connect(self.msg_set_building)
|
||||||
|
#self.signing.connect(self.msg_set_signing)
|
||||||
|
#self.pushing.connect(self.msg_set_pushing)
|
||||||
|
|
||||||
|
#self.askpassword.connect(self.ask_password)
|
||||||
|
#self.passworddone.connect(self.password_done)
|
||||||
|
self.check_row = None
|
||||||
|
self.inval_row = None
|
||||||
|
self.build_row = None
|
||||||
|
self.sign_row = None
|
||||||
|
self.push_row = None
|
||||||
|
self.network = Network.get_instance()
|
||||||
|
self._stopping = False
|
||||||
|
self.thread = TaskThread(self)
|
||||||
|
self.thread.finished.connect(self.task_finished) # see #3956
|
||||||
|
def task_finished(self):
|
||||||
|
pass
|
||||||
|
#_logger.trace("task finished")
|
||||||
|
|
||||||
|
def close_plugin_task(self):
|
||||||
|
_logger.debug("close task to be started")
|
||||||
|
self.thread.add(self.task_phase1,on_success=self.on_success_phase1,on_done=self.on_accept,on_error=self.on_error_phase1)
|
||||||
|
self.show()
|
||||||
|
self.exec()
|
||||||
|
|
||||||
|
def task_phase1(self):
|
||||||
|
_logger.debug("close plugin phase 1 started")
|
||||||
|
try:
|
||||||
|
self.bal_window.init_class_variables()
|
||||||
|
except Will.NoHeirsException:
|
||||||
|
return False, None
|
||||||
|
self.msg_set_status("checking variables","Waiting")
|
||||||
|
try:
|
||||||
|
Will.check_amounts(self.bal_window.heirs,self.bal_window.willexecutors,self.bal_window.window.wallet.get_utxos(),self.bal_window.date_to_check,self.bal_window.window.wallet.dust_threshold())
|
||||||
|
except Will.AmountException:
|
||||||
|
self.msg_edit_row('<font color="#ff0000">'+_("In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts"+"</font>"))
|
||||||
|
#self.bal_window.show_warning(_("In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts"),parent=self)
|
||||||
|
|
||||||
|
self.msg_set_checking()
|
||||||
|
have_to_build=False
|
||||||
|
try:
|
||||||
|
self.bal_window.check_will()
|
||||||
|
self.msg_set_checking('Ok')
|
||||||
|
except Will.WillExpiredException as e:
|
||||||
|
self.msg_set_checking("Expired")
|
||||||
|
fee_per_byte=self.bal_window.will_settings.get('tx_fees',1)
|
||||||
|
return None, Will.invalidate_will(self.bal_window.willitems,self.bal_window.wallet,fee_per_byte)
|
||||||
|
except Will.NoHeirsException:
|
||||||
|
self.msg_set_checking("No Heirs")
|
||||||
|
except Will.NotCompleteWillException as e:
|
||||||
|
message = False
|
||||||
|
have_to_build=True
|
||||||
|
if isinstance(e,Will.HeirChangeException):
|
||||||
|
message ="Heirs changed:"
|
||||||
|
elif isinstance(e,Will.WillExecutorNotPresent):
|
||||||
|
message = "Will-Executor not present"
|
||||||
|
elif isinstance(e,Will.WillexecutorChangeException):
|
||||||
|
message = "Will-Executor changed"
|
||||||
|
elif isinstance(e,Will.TxFeesChangedException):
|
||||||
|
message = "Txfees are changed"
|
||||||
|
elif isinstance(e,Will.HeirNotFoundException):
|
||||||
|
message = "Heir not found"
|
||||||
|
if message:
|
||||||
|
self.msg_set_checking(message)
|
||||||
|
else:
|
||||||
|
self.msg_set_checking("New")
|
||||||
|
|
||||||
|
if have_to_build:
|
||||||
|
self.msg_set_building()
|
||||||
|
try:
|
||||||
|
self.bal_window.build_will()
|
||||||
|
self.bal_window.check_will()
|
||||||
|
for wid in Will.only_valid(self.bal_window.willitems):
|
||||||
|
self.bal_window.wallet.set_label(wid,"BAL Transaction")
|
||||||
|
self.msg_set_building("Ok")
|
||||||
|
except Exception as e:
|
||||||
|
self.msg_set_building(self.msg_error(e))
|
||||||
|
return False,None
|
||||||
|
have_to_sign = False
|
||||||
|
for wid in Will.only_valid(self.bal_window.willitems):
|
||||||
|
if not self.bal_window.willitems[wid].get_status("COMPLETE"):
|
||||||
|
have_to_sign = True
|
||||||
|
break
|
||||||
|
return have_to_sign, None
|
||||||
|
|
||||||
|
def on_accept(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_accept_phase2(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_error_push(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def wait(self,secs):
|
||||||
|
wait_row=None
|
||||||
|
for i in range(secs,0,-1):
|
||||||
|
if self._stopping:
|
||||||
|
return
|
||||||
|
wait_row = self.msg_edit_row(f"Please wait {i}secs", wait_row)
|
||||||
|
time.sleep(1)
|
||||||
|
self.msg_del_row(wait_row)
|
||||||
|
|
||||||
|
def loop_broadcast_invalidating(self,tx):
|
||||||
|
self.msg_set_invalidating("Broadcasting")
|
||||||
|
try:
|
||||||
|
tx.add_info_from_wallet(self.bal_window.wallet)
|
||||||
|
self.network.run_from_another_thread(tx.add_info_from_network(self.network))
|
||||||
|
txid = self.network.run_from_another_thread(self.network.broadcast_transaction(tx,timeout=120),timeout=120)
|
||||||
|
self.msg_set_invalidating("Ok")
|
||||||
|
if not txid:
|
||||||
|
_logger.debug(f"should not be none txid: {txid}")
|
||||||
|
|
||||||
|
|
||||||
|
except TxBroadcastError as e:
|
||||||
|
_logger.error(e)
|
||||||
|
msg = e.get_message_for_gui()
|
||||||
|
self.msg_set_invalidating(self.msg_error(msg))
|
||||||
|
except BestEffortRequestFailed as e:
|
||||||
|
self.msg_set_invalidating(self.msg_error(e))
|
||||||
|
# self.loop_broadcast_invalidating(tx)
|
||||||
|
|
||||||
|
def loop_push(self):
|
||||||
|
self.msg_set_pushing("Broadcasting")
|
||||||
|
retry = False
|
||||||
|
try:
|
||||||
|
willexecutors=Willexecutors.get_willexecutor_transactions(self.bal_window.willitems)
|
||||||
|
for url,willexecutor in willexecutors.items():
|
||||||
|
try:
|
||||||
|
if Willexecutors.is_selected(self.bal_window.willexecutors.get(url)):
|
||||||
|
_logger.debug(f"{url}: {willexecutor}")
|
||||||
|
if not Willexecutors.push_transactions_to_willexecutor(willexecutor):
|
||||||
|
for wid in willexecutor['txsids']:
|
||||||
|
self.bal_window.willitems[wid].set_status('PUSH_FAIL',True)
|
||||||
|
retry=True
|
||||||
|
else:
|
||||||
|
for wid in willexecutor['txsids']:
|
||||||
|
self.bal_window.willitems[wid].set_status('PUSHED',True)
|
||||||
|
except Willexecutors.AlreadyPresentException:
|
||||||
|
for wid in willexecutor['txsids']:
|
||||||
|
row = self.msg_edit_row("checking {} - {} : {}".format(self.bal_window.willitems[wid].we['url'],wid, "Waiting"))
|
||||||
|
self.bal_window.willitems[wid].check_willexecutor()
|
||||||
|
row = self.msg_edit_row("checked {} - {} : {}".format(self.bal_window.willitems[wid].we['url'],wid,self.bal_window.willitems[wid].get_status("CHECKED" )),row)
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
_logger.error(e)
|
||||||
|
raise e
|
||||||
|
if retry:
|
||||||
|
raise Exception("retry")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.msg_set_pushing(self.msg_error(e))
|
||||||
|
self.wait(10)
|
||||||
|
if not self._stopping:
|
||||||
|
self.loop_push()
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_task(self,tx,password):
|
||||||
|
_logger.debug(f"invalidate tx: {tx}")
|
||||||
|
tx = self.bal_window.wallet.sign_transaction(tx,password)
|
||||||
|
try:
|
||||||
|
if tx:
|
||||||
|
if tx.is_complete():
|
||||||
|
self.loop_broadcast_invalidating(tx)
|
||||||
|
self.wait(5)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
self.msg_set_invalidating("Error")
|
||||||
|
raise Exception("Impossible to sign")
|
||||||
|
def on_success_invalidate(self,success):
|
||||||
|
self.thread.add(self.task_phase1,on_success=self.on_success_phase1,on_done=self.on_accept,on_error=self.on_error_phase1)
|
||||||
|
def on_error(self,error):
|
||||||
|
_logger.error(error)
|
||||||
|
pass
|
||||||
|
def on_success_phase1(self,result):
|
||||||
|
self.have_to_sign,tx = list(result)
|
||||||
|
#if have_to_sign is False and tx is None:
|
||||||
|
#self._stopping=True
|
||||||
|
#self.on_success_phase2()
|
||||||
|
# return
|
||||||
|
_logger.debug("have to sign {}".format(self.have_to_sign))
|
||||||
|
password=None
|
||||||
|
if self.have_to_sign is None:
|
||||||
|
self.msg_set_invalidating()
|
||||||
|
#need to sign invalidate and restart phase 1
|
||||||
|
|
||||||
|
password = self.bal_window.get_wallet_password("Invalidate your old will",parent=self.bal_window.window)
|
||||||
|
if password is False:
|
||||||
|
self.msg_set_invalidating("Aborted")
|
||||||
|
self.wait(3)
|
||||||
|
self.close()
|
||||||
|
return
|
||||||
|
self.thread.add(partial(self.invalidate_task,tx,password),on_success=self.on_success_invalidate, on_done=self.on_accept, on_error=self.on_error)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
elif self.have_to_sign:
|
||||||
|
password = self.bal_window.get_wallet_password("Sign your will",parent=self.bal_window.window)
|
||||||
|
if password is False:
|
||||||
|
self.msg_set_signing('Aborted')
|
||||||
|
else:
|
||||||
|
self.msg_set_signing('Nothing to do')
|
||||||
|
self.thread.add(partial(self.task_phase2,password),on_success=self.on_success_phase2,on_done=self.on_accept_phase2,on_error=self.on_error_phase2)
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_success_phase2(self,arg=False):
|
||||||
|
self.thread.stop()
|
||||||
|
self.bal_window.save_willitems()
|
||||||
|
self.msg_edit_row("Finished")
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def closeEvent(self,event):
|
||||||
|
self._stopping=True
|
||||||
|
self.thread.stop()
|
||||||
|
|
||||||
|
def task_phase2(self,password):
|
||||||
|
if self.have_to_sign:
|
||||||
|
try:
|
||||||
|
if txs:=self.bal_window.sign_transactions(password):
|
||||||
|
for txid,tx in txs.items():
|
||||||
|
self.bal_window.willitems[txid].tx = copy.deepcopy(tx)
|
||||||
|
self.bal_window.save_willitems()
|
||||||
|
self.msg_set_signing("Ok")
|
||||||
|
except Exception as e:
|
||||||
|
self.msg_set_signing(self.msg_error(e))
|
||||||
|
|
||||||
|
self.msg_set_pushing()
|
||||||
|
have_to_push = False
|
||||||
|
for wid in Will.only_valid(self.bal_window.willitems):
|
||||||
|
w=self.bal_window.willitems[wid]
|
||||||
|
if w.we and w.get_status("COMPLETE") and not w.get_status("PUSHED"):
|
||||||
|
have_to_push = True
|
||||||
|
if not have_to_push:
|
||||||
|
self.msg_set_pushing("Nothing to do")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.loop_push()
|
||||||
|
self.msg_set_pushing("Ok")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.msg_set_pushing(self.msg_error(e))
|
||||||
|
self.msg_edit_row("Ok")
|
||||||
|
self.wait(5)
|
||||||
|
|
||||||
|
def on_error_phase1(self,error):
|
||||||
|
_logger.error(f"error phase1: {error}")
|
||||||
|
|
||||||
|
def on_error_phase2(self,error):
|
||||||
|
_logger.error("error phase2: { error}")
|
||||||
|
|
||||||
|
|
||||||
|
def msg_set_checking(self, status = None, row = None):
|
||||||
|
row = self.check_row if row is None else row
|
||||||
|
self.check_row = self.msg_set_status("Checking your will", row, status)
|
||||||
|
|
||||||
|
def msg_set_invalidating(self, status = None, row = None):
|
||||||
|
row = self.inval_row if row is None else row
|
||||||
|
self.inval_row = self.msg_set_status("Invalidating old will", self.inval_row, status)
|
||||||
|
|
||||||
|
def msg_set_building(self, status = None, row = None):
|
||||||
|
row = self.build_row if row is None else row
|
||||||
|
self.build_row = self.msg_set_status("Building your will", self.build_row, status)
|
||||||
|
|
||||||
|
def msg_set_signing(self, status = None, row = None):
|
||||||
|
row = self.sign_row if row is None else row
|
||||||
|
self.sign_row = self.msg_set_status("Signing your will", self.sign_row, status)
|
||||||
|
|
||||||
|
def msg_set_pushing(self, status = None, row = None):
|
||||||
|
row = self.push_row if row is None else row
|
||||||
|
self.push_row = self.msg_set_status("Broadcasting your will to executors", self.push_row, status)
|
||||||
|
|
||||||
|
def msg_set_waiting(self, status = None, row = None):
|
||||||
|
row = self.wait_row if row is None else row
|
||||||
|
self.wait_row = self.msg_edit_row(f"Please wait {status}secs", self.wait_row)
|
||||||
|
|
||||||
|
def msg_error(self,e):
|
||||||
|
return "Error: {}".format(e)
|
||||||
|
|
||||||
|
def msg_set_status(self,msg,row,status=None):
|
||||||
|
status= "Wait" if status is None else status
|
||||||
|
line="{}:\t{}".format(_(msg), status)
|
||||||
|
return self.msg_edit_row(line,row)
|
||||||
|
|
||||||
|
#return v$msg_edit_row("{}:\t{}".format(_(msg), status), row)
|
||||||
|
|
||||||
|
def ask_password(self,msg=None):
|
||||||
|
self.password=self.bal_window.get_wallet_password(msg,parent=self)
|
||||||
|
|
||||||
|
def msg_edit_row(self,line,row=None):
|
||||||
|
_logger.debug(f"{row},{line}")
|
||||||
|
|
||||||
|
#msg=self.get_text()
|
||||||
|
#rows=msg.split("\n")
|
||||||
|
#try:
|
||||||
|
# rows[row]=line
|
||||||
|
#except Exception as e:
|
||||||
|
# rows.append(line)
|
||||||
|
#row=len(rows)-1
|
||||||
|
#self.update("\n".join(rows))
|
||||||
|
|
||||||
|
#return row
|
||||||
|
|
||||||
|
#def msg_edit_label(self,line,row=None):
|
||||||
|
#_logger.trace(f"{row},{line}")
|
||||||
|
|
||||||
|
#msg=self.get_text()
|
||||||
|
#rows=msg.split("\n")
|
||||||
|
try:
|
||||||
|
self.labels[row]=line
|
||||||
|
except Exception as e:
|
||||||
|
self.labels.append(line)
|
||||||
|
row=len(self.labels)-1
|
||||||
|
|
||||||
|
self.updatemessage.emit()
|
||||||
|
|
||||||
|
return row
|
||||||
|
|
||||||
|
def msg_del_row(self,row):
|
||||||
|
#_logger.trace(f"del row: {row}")
|
||||||
|
try:
|
||||||
|
del self.labels[row]
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
self.updatemessage.emit()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.vbox.removeWidget(self.qwidget)
|
||||||
|
self.qwidget=QWidget(self)
|
||||||
|
labelsbox = QVBoxLayout(self.qwidget)
|
||||||
|
for label in self.labels:
|
||||||
|
labelsbox.addWidget(QLabel(label))
|
||||||
|
self.vbox.addWidget(self.qwidget)
|
||||||
|
|
||||||
|
def get_text(self):
|
||||||
|
return self.message_label.text()
|
||||||
|
def ThreadStopped(Exception):
|
||||||
|
pass
|
||||||
283
balqt/heir_list.py
Normal file
283
balqt/heir_list.py
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Electrum - lightweight Bitcoin client
|
||||||
|
# Copyright (C) 2015 Thomas Voegtlin
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation files
|
||||||
|
# (the "Software"), to deal in the Software without restriction,
|
||||||
|
# including without limitation the rights to use, copy, modify, merge,
|
||||||
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
# and to permit persons to whom the Software is furnished to do so,
|
||||||
|
# subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
import enum
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from . import qt_resources
|
||||||
|
if qt_resources.QT_VERSION == 5:
|
||||||
|
from PyQt5.QtGui import QStandardItemModel, QStandardItem
|
||||||
|
from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex
|
||||||
|
from PyQt5.QtWidgets import (QAbstractItemView, QMenu,QWidget,QHBoxLayout,QLabel,QSpinBox,QPushButton)
|
||||||
|
else:
|
||||||
|
from PyQt6.QtGui import QStandardItemModel, QStandardItem
|
||||||
|
from PyQt6.QtCore import Qt, QPersistentModelIndex, QModelIndex
|
||||||
|
from PyQt6.QtWidgets import (QAbstractItemView, QMenu,QWidget,QHBoxLayout,QLabel,QSpinBox,QPushButton)
|
||||||
|
|
||||||
|
from electrum.i18n import _
|
||||||
|
from electrum.bitcoin import is_address
|
||||||
|
from electrum.util import block_explorer_URL
|
||||||
|
from electrum.plugin import run_hook
|
||||||
|
from electrum.gui.qt.util import webopen, MessageBoxMixin,HelpButton
|
||||||
|
from electrum.gui.qt.my_treeview import MyTreeView, MySortModel
|
||||||
|
|
||||||
|
from .. import util as Util
|
||||||
|
from .locktimeedit import HeirsLockTimeEdit
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from electrum.gui.qt.main_window import ElectrumWindow
|
||||||
|
|
||||||
|
|
||||||
|
class HeirList(MyTreeView,MessageBoxMixin):
|
||||||
|
|
||||||
|
class Columns(MyTreeView.BaseColumnsEnum):
|
||||||
|
NAME = enum.auto()
|
||||||
|
ADDRESS = enum.auto()
|
||||||
|
AMOUNT = enum.auto()
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
Columns.NAME: _('Name'),
|
||||||
|
Columns.ADDRESS: _('Address'),
|
||||||
|
Columns.AMOUNT: _('Amount'),
|
||||||
|
#Columns.LOCKTIME:_('LockTime'),
|
||||||
|
}
|
||||||
|
filter_columns = [Columns.NAME, Columns.ADDRESS]
|
||||||
|
|
||||||
|
ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1000
|
||||||
|
|
||||||
|
ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 1001
|
||||||
|
key_role = ROLE_HEIR_KEY
|
||||||
|
|
||||||
|
def __init__(self, bal_window: 'BalWindow'):
|
||||||
|
super().__init__(
|
||||||
|
parent=bal_window.window,
|
||||||
|
main_window=bal_window.window,
|
||||||
|
stretch_column=self.Columns.NAME,
|
||||||
|
editable_columns=[self.Columns.NAME,self.Columns.ADDRESS,self.Columns.AMOUNT],
|
||||||
|
)
|
||||||
|
self.decimal_point = bal_window.bal_plugin.config.get_decimal_point()
|
||||||
|
self.bal_window = bal_window
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.setModel(QStandardItemModel(self))
|
||||||
|
self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder)
|
||||||
|
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
#self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder)
|
||||||
|
#self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
|
|
||||||
|
self.setSortingEnabled(True)
|
||||||
|
self.std_model = self.model()
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
|
def on_edited(self, idx, edit_key, *, text):
|
||||||
|
original = prior_name = self.bal_window.heirs.get(edit_key)
|
||||||
|
if not prior_name:
|
||||||
|
return
|
||||||
|
col = idx.column()
|
||||||
|
try:
|
||||||
|
#if col == 3:
|
||||||
|
# try:
|
||||||
|
# text = Util.str_to_locktime(text)
|
||||||
|
# except:
|
||||||
|
# print("not a valid locktime")
|
||||||
|
# pass
|
||||||
|
if col == 2:
|
||||||
|
text = Util.encode_amount(text,self.decimal_point)
|
||||||
|
elif col == 0:
|
||||||
|
self.bal_window.delete_heirs([edit_key])
|
||||||
|
edit_key = text
|
||||||
|
prior_name[col-1] = text
|
||||||
|
prior_name.insert(0,edit_key)
|
||||||
|
prior_name = tuple(prior_name)
|
||||||
|
except Exception as e:
|
||||||
|
#print("eccezione tupla",e)
|
||||||
|
prior_name = (edit_key,)+prior_name[:col-1]+(text,)+prior_name[col:]
|
||||||
|
#print("prior_name",prior_name,original)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.bal_window.set_heir(prior_name)
|
||||||
|
#print("setheir")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
#print("heir non valido ripristino l'originale",e)
|
||||||
|
try:
|
||||||
|
#print("setup_original",(edit_key,)+original)
|
||||||
|
self.bal_window.set_heir((edit_key,)+original)
|
||||||
|
except Exception as e:
|
||||||
|
#print("errore nellimpostare original",e,original)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def create_menu(self, position):
|
||||||
|
menu = QMenu()
|
||||||
|
idx = self.indexAt(position)
|
||||||
|
column = idx.column() or self.Columns.NAME
|
||||||
|
selected_keys = []
|
||||||
|
for s_idx in self.selected_in_column(self.Columns.NAME):
|
||||||
|
#print(s_idx)
|
||||||
|
sel_key = self.model().itemFromIndex(s_idx).data(0)
|
||||||
|
selected_keys.append(sel_key)
|
||||||
|
if selected_keys and idx.isValid():
|
||||||
|
column_title = self.model().horizontalHeaderItem(column).text()
|
||||||
|
column_data = '\n'.join(self.model().itemFromIndex(s_idx).text()
|
||||||
|
for s_idx in self.selected_in_column(column))
|
||||||
|
menu.addAction(_("Copy {}").format(column_title), lambda: self.place_text_on_clipboard(column_data, title=column_title))
|
||||||
|
if column in self.editable_columns:
|
||||||
|
item = self.model().itemFromIndex(idx)
|
||||||
|
if item.isEditable():
|
||||||
|
# would not be editable if openalias
|
||||||
|
persistent = QPersistentModelIndex(idx)
|
||||||
|
menu.addAction(_("Edit {}").format(column_title), lambda p=persistent: self.edit(QModelIndex(p)))
|
||||||
|
menu.addAction(_("Delete"), lambda: self.bal_window.delete_heirs(selected_keys))
|
||||||
|
menu.exec(self.viewport().mapToGlobal(position))
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.maybe_defer_update():
|
||||||
|
return
|
||||||
|
current_key = self.get_role_data_for_current_item(col=self.Columns.NAME, role=self.ROLE_HEIR_KEY)
|
||||||
|
self.model().clear()
|
||||||
|
self.update_headers(self.__class__.headers)
|
||||||
|
set_current = None
|
||||||
|
for key in sorted(self.bal_window.heirs.keys()):
|
||||||
|
heir = self.bal_window.heirs[key]
|
||||||
|
labels = [""] * len(self.Columns)
|
||||||
|
labels[self.Columns.NAME] = key
|
||||||
|
labels[self.Columns.ADDRESS] = heir[0]
|
||||||
|
labels[self.Columns.AMOUNT] = Util.decode_amount(heir[1],self.decimal_point)
|
||||||
|
#labels[self.Columns.LOCKTIME] = str(Util.locktime_to_str(heir[2]))
|
||||||
|
|
||||||
|
items = [QStandardItem(x) for x in labels]
|
||||||
|
items[self.Columns.NAME].setEditable(True)
|
||||||
|
items[self.Columns.ADDRESS].setEditable(True)
|
||||||
|
items[self.Columns.AMOUNT].setEditable(True)
|
||||||
|
#items[self.Columns.LOCKTIME].setEditable(True)
|
||||||
|
items[self.Columns.NAME].setData(key, self.ROLE_HEIR_KEY+1)
|
||||||
|
items[self.Columns.ADDRESS].setData(key, self.ROLE_HEIR_KEY+2)
|
||||||
|
items[self.Columns.AMOUNT].setData(key, self.ROLE_HEIR_KEY+3)
|
||||||
|
#items[self.Columns.LOCKTIME].setData(key, self.ROLE_HEIR_KEY+4)
|
||||||
|
|
||||||
|
self.model().insertRow(self.model().rowCount(), items)
|
||||||
|
|
||||||
|
if key == current_key:
|
||||||
|
idx = self.model().index(row_count, self.Columns.NAME)
|
||||||
|
set_current = QPersistentModelIndex(idx)
|
||||||
|
self.set_current_idx(set_current)
|
||||||
|
# FIXME refresh loses sort order; so set "default" here:
|
||||||
|
self.filter()
|
||||||
|
run_hook('update_heirs_tab', self)
|
||||||
|
|
||||||
|
def refresh_row(self, key, row):
|
||||||
|
# nothing to update here
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_edit_key_from_coordinate(self, row, col):
|
||||||
|
#print("role_data",self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY))
|
||||||
|
#print(col)
|
||||||
|
return self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY+col+1)
|
||||||
|
return col
|
||||||
|
|
||||||
|
def create_toolbar(self, config):
|
||||||
|
toolbar, menu = self.create_toolbar_with_menu('')
|
||||||
|
menu.addAction(_("&New Heir"), self.bal_window.new_heir_dialog)
|
||||||
|
menu.addAction(_("Import"), self.bal_window.import_heirs)
|
||||||
|
menu.addAction(_("Export"), lambda: self.bal_window.export_heirs())
|
||||||
|
#menu.addAction(_("Build Traonsactions"), self.build_transactions)
|
||||||
|
|
||||||
|
self.heir_locktime = HeirsLockTimeEdit(self.window(),0)
|
||||||
|
def on_heir_locktime():
|
||||||
|
if not self.heir_locktime.get_locktime():
|
||||||
|
self.heir_locktime.set_locktime('1y')
|
||||||
|
self.bal_window.will_settings['locktime'] = self.heir_locktime.get_locktime() if self.heir_locktime.get_locktime() else "1y"
|
||||||
|
self.bal_window.bal_plugin.config.set_key('will_settings',self.bal_window.will_settings,save = True)
|
||||||
|
self.heir_locktime.valueEdited.connect(on_heir_locktime)
|
||||||
|
|
||||||
|
self.heir_threshold = HeirsLockTimeEdit(self,0)
|
||||||
|
def on_heir_threshold():
|
||||||
|
if not self.heir_threshold.get_locktime():
|
||||||
|
self.heir_threshold.set_locktime('180d')
|
||||||
|
|
||||||
|
self.bal_window.will_settings['threshold'] = self.heir_threshold.get_locktime()
|
||||||
|
self.bal_window.bal_plugin.config.set_key('will_settings',self.bal_window.will_settings,save = True)
|
||||||
|
self.heir_threshold.valueEdited.connect(on_heir_threshold)
|
||||||
|
|
||||||
|
self.heir_tx_fees = QSpinBox()
|
||||||
|
self.heir_tx_fees.setMinimum(1)
|
||||||
|
self.heir_tx_fees.setMaximum(10000)
|
||||||
|
def on_heir_tx_fees():
|
||||||
|
if not self.heir_tx_fees.value():
|
||||||
|
self.heir_tx_fees.set_value(1)
|
||||||
|
self.bal_window.will_settings['tx_fees'] = self.heir_tx_fees.value()
|
||||||
|
self.bal_window.bal_plugin.config.set_key('will_settings',self.bal_window.will_settings,save = True)
|
||||||
|
self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees)
|
||||||
|
|
||||||
|
|
||||||
|
self.heirs_widget = QWidget()
|
||||||
|
layout = QHBoxLayout()
|
||||||
|
self.heirs_widget.setLayout(layout)
|
||||||
|
|
||||||
|
layout.addWidget(QLabel(_("Delivery Time:")))
|
||||||
|
layout.addWidget(self.heir_locktime)
|
||||||
|
layout.addWidget(HelpButton(_("Locktime* to be used in the transaction\n"
|
||||||
|
+"if you choose Raw, you can insert various options based on suffix:\n"
|
||||||
|
#+" - b: number of blocks after current block(ex: 144b means tomorrow)\n"
|
||||||
|
+" - d: number of days after current day(ex: 1d means tomorrow)\n"
|
||||||
|
+" - y: number of years after currrent day(ex: 1y means one year from today)\n"
|
||||||
|
+"* locktime can be anticipated to update will\n")))
|
||||||
|
|
||||||
|
layout.addWidget(QLabel(" "))
|
||||||
|
layout.addWidget(QLabel(_("Check Alive:")))
|
||||||
|
layout.addWidget(self.heir_threshold)
|
||||||
|
layout.addWidget(HelpButton(_("Check to ask for invalidation.\n"
|
||||||
|
+"When less then this time is missing, ask to invalidate.\n"
|
||||||
|
+"If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n"
|
||||||
|
+"if you choose Raw, you can insert various options based on suffix:\n"
|
||||||
|
#+" - b: number of blocks after current block(ex: 144b means tomorrow)\n"
|
||||||
|
+" - d: number of days after current day(ex: 1d means tomorrow).\n"
|
||||||
|
+" - y: number of years after currrent day(ex: 1y means one year from today).\n\n")))
|
||||||
|
layout.addWidget(QLabel(" "))
|
||||||
|
layout.addWidget(QLabel(_("Fees:")))
|
||||||
|
layout.addWidget(self.heir_tx_fees)
|
||||||
|
layout.addWidget(HelpButton(_("Fee to be used in the transaction")))
|
||||||
|
layout.addWidget(QLabel("sats/vbyte"))
|
||||||
|
layout.addWidget(QLabel(" "))
|
||||||
|
newHeirButton = QPushButton(_("New Heir"))
|
||||||
|
newHeirButton.clicked.connect(self.bal_window.new_heir_dialog)
|
||||||
|
layout.addWidget(newHeirButton)
|
||||||
|
|
||||||
|
toolbar.insertWidget(2, self.heirs_widget)
|
||||||
|
|
||||||
|
return toolbar
|
||||||
|
def update_will_settings(self):
|
||||||
|
self.heir_threshold.set_locktime(self.bal_window.will_settings['threshold'])
|
||||||
|
self.heir_locktime.set_locktime(self.bal_window.will_settings['locktime'])
|
||||||
|
self.heir_tx_fees.setValue(int(self.bal_window.will_settings['tx_fees']))
|
||||||
|
|
||||||
|
def build_transactions(self):
|
||||||
|
will = self.bal_window.prepare_will()
|
||||||
|
|
||||||
255
balqt/locktimeedit.py
Normal file
255
balqt/locktimeedit.py
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# Copyright (C) 2020 The Electrum developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional, Any
|
||||||
|
|
||||||
|
from . import qt_resources
|
||||||
|
if qt_resources.QT_VERSION == 5:
|
||||||
|
from PyQt5.QtCore import Qt, QDateTime, pyqtSignal
|
||||||
|
from PyQt5.QtGui import QPalette, QPainter
|
||||||
|
from PyQt5.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox, QHBoxLayout, QDateTimeEdit)
|
||||||
|
else:
|
||||||
|
from PyQt6.QtCore import Qt, QDateTime, pyqtSignal
|
||||||
|
from PyQt6.QtGui import QPalette, QPainter
|
||||||
|
from PyQt6.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox, QHBoxLayout, QDateTimeEdit)
|
||||||
|
|
||||||
|
from electrum.i18n import _
|
||||||
|
from electrum.bitcoin import NLOCKTIME_MIN, NLOCKTIME_MAX, NLOCKTIME_BLOCKHEIGHT_MAX
|
||||||
|
|
||||||
|
from electrum.gui.qt.util import char_width_in_lineedit, ColorScheme
|
||||||
|
|
||||||
|
|
||||||
|
class HeirsLockTimeEdit(QWidget):
|
||||||
|
|
||||||
|
valueEdited = pyqtSignal()
|
||||||
|
locktime_threshold = 50000000
|
||||||
|
def __init__(self, parent=None,default_index = 1):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
|
||||||
|
hbox = QHBoxLayout()
|
||||||
|
self.setLayout(hbox)
|
||||||
|
hbox.setContentsMargins(0, 0, 0, 0)
|
||||||
|
hbox.setSpacing(0)
|
||||||
|
|
||||||
|
self.locktime_raw_e = LockTimeRawEdit(self,time_edit = self)
|
||||||
|
#self.locktime_height_e = LockTimeHeightEdit(self)
|
||||||
|
self.locktime_date_e = LockTimeDateEdit(self,time_edit = self)
|
||||||
|
#self.editors = [self.locktime_raw_e, self.locktime_height_e, self.locktime_date_e]
|
||||||
|
self.editors = [self.locktime_raw_e, self.locktime_date_e]
|
||||||
|
|
||||||
|
self.combo = QComboBox()
|
||||||
|
#options = [_("Raw"), _("Block height"), _("Date")]
|
||||||
|
options = [_("Raw"),_("Date")]
|
||||||
|
self.option_index_to_editor_map = {
|
||||||
|
0: self.locktime_raw_e,
|
||||||
|
#1: self.locktime_height_e,
|
||||||
|
1: self.locktime_date_e,
|
||||||
|
#2: self.locktime_date_e,
|
||||||
|
}
|
||||||
|
self.combo.addItems(options)
|
||||||
|
|
||||||
|
|
||||||
|
self.editor = self.option_index_to_editor_map[default_index]
|
||||||
|
self.combo.currentIndexChanged.connect(self.on_current_index_changed)
|
||||||
|
self.combo.setCurrentIndex(default_index)
|
||||||
|
self.on_current_index_changed(default_index)
|
||||||
|
|
||||||
|
hbox.addWidget(self.combo)
|
||||||
|
for w in self.editors:
|
||||||
|
hbox.addWidget(w)
|
||||||
|
hbox.addStretch(1)
|
||||||
|
|
||||||
|
#self.locktime_height_e.textEdited.connect(self.valueEdited.emit)
|
||||||
|
self.locktime_raw_e.editingFinished.connect(self.valueEdited.emit)
|
||||||
|
self.locktime_date_e.dateTimeChanged.connect(self.valueEdited.emit)
|
||||||
|
self.combo.currentIndexChanged.connect(self.valueEdited.emit)
|
||||||
|
|
||||||
|
def on_current_index_changed(self,i):
|
||||||
|
for w in self.editors:
|
||||||
|
w.setVisible(False)
|
||||||
|
w.setEnabled(False)
|
||||||
|
prev_locktime = self.editor.get_locktime()
|
||||||
|
self.editor = self.option_index_to_editor_map[i]
|
||||||
|
if self.editor.is_acceptable_locktime(prev_locktime):
|
||||||
|
self.editor.set_locktime(prev_locktime,force=True)
|
||||||
|
self.editor.setVisible(True)
|
||||||
|
self.editor.setEnabled(True)
|
||||||
|
|
||||||
|
def get_locktime(self) -> Optional[str]:
|
||||||
|
return self.editor.get_locktime()
|
||||||
|
|
||||||
|
def set_index(self,index):
|
||||||
|
self.combo.setCurrentIndex(index)
|
||||||
|
self.on_current_index_changed(index)
|
||||||
|
|
||||||
|
def set_locktime(self, x: Any,force=True) -> None:
|
||||||
|
self.editor.set_locktime(x,force)
|
||||||
|
|
||||||
|
|
||||||
|
class _LockTimeEditor:
|
||||||
|
min_allowed_value = NLOCKTIME_MIN
|
||||||
|
max_allowed_value = NLOCKTIME_MAX
|
||||||
|
|
||||||
|
def get_locktime(self) -> Optional[int]:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def set_locktime(self, x: Any,force=True) -> None:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_acceptable_locktime(cls, x: Any) -> bool:
|
||||||
|
if not x: # e.g. empty string
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
x = int(x)
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
return cls.min_allowed_value <= x <= cls.max_allowed_value
|
||||||
|
|
||||||
|
|
||||||
|
class LockTimeRawEdit(QLineEdit, _LockTimeEditor):
|
||||||
|
|
||||||
|
def __init__(self, parent=None,time_edit=None):
|
||||||
|
QLineEdit.__init__(self, parent)
|
||||||
|
self.setFixedWidth(14 * char_width_in_lineedit())
|
||||||
|
self.textChanged.connect(self.numbify)
|
||||||
|
self.isdays = False
|
||||||
|
self.isyears = False
|
||||||
|
self.isblocks = False
|
||||||
|
self.time_edit=time_edit
|
||||||
|
|
||||||
|
def replace_str(self,text):
|
||||||
|
return str(text).replace('d','').replace('y','').replace('b','')
|
||||||
|
|
||||||
|
def checkbdy(self,s,pos,appendix):
|
||||||
|
try:
|
||||||
|
charpos = pos-1
|
||||||
|
charpos = max(0,charpos)
|
||||||
|
charpos = min(len(s)-1,charpos)
|
||||||
|
if appendix == s[charpos]:
|
||||||
|
s=self.replace_str(s)+appendix
|
||||||
|
pos = charpos
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
return pos, s
|
||||||
|
|
||||||
|
def numbify(self):
|
||||||
|
text = self.text().strip()
|
||||||
|
#chars = '0123456789bdy' removed the option to choose locktime by block
|
||||||
|
chars = '0123456789dy'
|
||||||
|
pos = posx = self.cursorPosition()
|
||||||
|
pos = len(''.join([i for i in text[:pos] if i in chars]))
|
||||||
|
s = ''.join([i for i in text if i in chars])
|
||||||
|
self.isdays = False
|
||||||
|
self.isyears = False
|
||||||
|
self.isblocks = False
|
||||||
|
|
||||||
|
pos,s = self.checkbdy(s,pos,'d')
|
||||||
|
pos,s = self.checkbdy(s,pos,'y')
|
||||||
|
pos,s = self.checkbdy(s,pos,'b')
|
||||||
|
|
||||||
|
if 'd' in s: self.isdays = True
|
||||||
|
if 'y' in s: self.isyears = True
|
||||||
|
if 'b' in s: self.isblocks = True
|
||||||
|
|
||||||
|
|
||||||
|
if self.isdays: s= self.replace_str(s) + 'd'
|
||||||
|
if self.isyears: s = self.replace_str(s) + 'y'
|
||||||
|
if self.isblocks: s= self.replace_str(s) + 'b'
|
||||||
|
|
||||||
|
self.set_locktime(s,force=False)
|
||||||
|
# setText sets Modified to False. Instead we want to remember
|
||||||
|
# if updates were because of user modification.
|
||||||
|
self.setModified(self.hasFocus())
|
||||||
|
self.setCursorPosition(pos)
|
||||||
|
|
||||||
|
def get_locktime(self) -> Optional[str]:
|
||||||
|
try:
|
||||||
|
return str(self.text())
|
||||||
|
except Exception as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_locktime(self, x: Any,force=True) -> None:
|
||||||
|
out = str(x)
|
||||||
|
if 'd' in out:
|
||||||
|
out = self.replace_str(x)+'d'
|
||||||
|
elif 'y' in out:
|
||||||
|
out = self.replace_str(x)+'y'
|
||||||
|
elif 'b' in out:
|
||||||
|
out = self.replace_str(x)+'b'
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
out = int(x)
|
||||||
|
except Exception as e:
|
||||||
|
self.setText('')
|
||||||
|
return
|
||||||
|
out = max(out, self.min_allowed_value)
|
||||||
|
out = min(out, self.max_allowed_value)
|
||||||
|
self.setText(str(out))
|
||||||
|
#try:
|
||||||
|
# if self.time_edit and int(out)>self.time_edit.locktime_threshold and not force:
|
||||||
|
# self.time_edit.set_index(1)
|
||||||
|
#except:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
class LockTimeHeightEdit(LockTimeRawEdit):
|
||||||
|
max_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX
|
||||||
|
|
||||||
|
def __init__(self, parent=None,time_edit=None):
|
||||||
|
LockTimeRawEdit.__init__(self, parent)
|
||||||
|
self.setFixedWidth(20 * char_width_in_lineedit())
|
||||||
|
self.time_edit = time_edit
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
super().paintEvent(event)
|
||||||
|
panel = QStyleOptionFrame()
|
||||||
|
self.initStyleOption(panel)
|
||||||
|
textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self)
|
||||||
|
textRect.adjust(2, 0, -10, 0)
|
||||||
|
painter = QPainter(self)
|
||||||
|
painter.setPen(ColorScheme.GRAY.as_color())
|
||||||
|
painter.drawText(textRect, int(Qt.AlignRight | Qt.AlignVCenter), "height")
|
||||||
|
|
||||||
|
|
||||||
|
def get_max_allowed_timestamp() -> int:
|
||||||
|
ts = NLOCKTIME_MAX
|
||||||
|
# Test if this value is within the valid timestamp limits (which is platform-dependent).
|
||||||
|
# see #6170
|
||||||
|
try:
|
||||||
|
datetime.fromtimestamp(ts)
|
||||||
|
except (OSError, OverflowError):
|
||||||
|
ts = 2 ** 31 - 1 # INT32_MAX
|
||||||
|
datetime.fromtimestamp(ts) # test if raises
|
||||||
|
return ts
|
||||||
|
|
||||||
|
|
||||||
|
class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor):
|
||||||
|
min_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX + 1
|
||||||
|
max_allowed_value = get_max_allowed_timestamp()
|
||||||
|
|
||||||
|
def __init__(self, parent=None,time_edit=None):
|
||||||
|
QDateTimeEdit.__init__(self, parent)
|
||||||
|
self.setMinimumDateTime(datetime.fromtimestamp(self.min_allowed_value))
|
||||||
|
self.setMaximumDateTime(datetime.fromtimestamp(self.max_allowed_value))
|
||||||
|
self.setDateTime(QDateTime.currentDateTime())
|
||||||
|
self.time_edit = time_edit
|
||||||
|
|
||||||
|
def get_locktime(self) -> Optional[int]:
|
||||||
|
dt = self.dateTime().toPyDateTime()
|
||||||
|
locktime = int(time.mktime(dt.timetuple()))
|
||||||
|
return locktime
|
||||||
|
|
||||||
|
def set_locktime(self, x: Any,force = False) -> None:
|
||||||
|
if not self.is_acceptable_locktime(x):
|
||||||
|
self.setDateTime(QDateTime.currentDateTime())
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
x = int(x)
|
||||||
|
except Exception:
|
||||||
|
self.setDateTime(QDateTime.currentDateTime())
|
||||||
|
return
|
||||||
|
dt = datetime.fromtimestamp(x)
|
||||||
|
self.setDateTime(dt)
|
||||||
331
balqt/preview_dialog.py
Normal file
331
balqt/preview_dialog.py
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
|
||||||
|
import enum
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from . import qt_resources
|
||||||
|
if qt_resources.QT_VERSION == 5:
|
||||||
|
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QPalette, QColor
|
||||||
|
from PyQt5.QtCore import Qt,QPersistentModelIndex, QModelIndex
|
||||||
|
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QMenu,QAbstractItemView,QWidget)
|
||||||
|
else:
|
||||||
|
from PyQt6.QtGui import QStandardItemModel, QStandardItem, QPalette, QColor
|
||||||
|
from PyQt6.QtCore import Qt,QPersistentModelIndex, QModelIndex
|
||||||
|
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QMenu,QAbstractItemView,QWidget)
|
||||||
|
|
||||||
|
from electrum.i18n import _
|
||||||
|
from electrum.gui.qt.util import (Buttons,read_QIcon, import_meta_gui, export_meta_gui,MessageBoxMixin)
|
||||||
|
from electrum.util import write_json_file,read_json_file,FileImportFailed
|
||||||
|
from electrum.gui.qt.my_treeview import MyTreeView
|
||||||
|
from electrum.transaction import tx_from_any
|
||||||
|
from electrum.network import Network
|
||||||
|
|
||||||
|
|
||||||
|
from ..bal import BalPlugin
|
||||||
|
from .. import willexecutors as Willexecutors
|
||||||
|
from .. import util as Util
|
||||||
|
from .. import will as Will
|
||||||
|
from .baldialog import BalDialog
|
||||||
|
|
||||||
|
class PreviewList(MyTreeView):
|
||||||
|
class Columns(MyTreeView.BaseColumnsEnum):
|
||||||
|
LOCKTIME = enum.auto()
|
||||||
|
TXID = enum.auto()
|
||||||
|
WILLEXECUTOR = enum.auto()
|
||||||
|
STATUS = enum.auto()
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
Columns.LOCKTIME: _('Locktime'),
|
||||||
|
Columns.TXID: _('Txid'),
|
||||||
|
Columns.WILLEXECUTOR: _('Will-Executor'),
|
||||||
|
Columns.STATUS: _('Status'),
|
||||||
|
}
|
||||||
|
|
||||||
|
ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 2000
|
||||||
|
key_role = ROLE_HEIR_KEY
|
||||||
|
|
||||||
|
def __init__(self, parent: 'BalWindow',will):
|
||||||
|
super().__init__(
|
||||||
|
parent=parent.window,
|
||||||
|
stretch_column=self.Columns.TXID,
|
||||||
|
)
|
||||||
|
self.decimal_point=parent.bal_plugin.config.get_decimal_point
|
||||||
|
self.setModel(QStandardItemModel(self))
|
||||||
|
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
|
|
||||||
|
|
||||||
|
if not will is None:
|
||||||
|
self.will = will
|
||||||
|
else:
|
||||||
|
self.will = parent.willitems
|
||||||
|
|
||||||
|
self.bal_window = parent
|
||||||
|
self.wallet=parent.window.wallet
|
||||||
|
self.setModel(QStandardItemModel(self))
|
||||||
|
self.setSortingEnabled(True)
|
||||||
|
self.std_model = self.model()
|
||||||
|
self.config = parent.bal_plugin.config
|
||||||
|
self.bal_plugin=self.bal_window.bal_plugin
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def create_menu(self, position):
|
||||||
|
menu = QMenu()
|
||||||
|
idx = self.indexAt(position)
|
||||||
|
column = idx.column() or self.Columns.TXID
|
||||||
|
selected_keys = []
|
||||||
|
for s_idx in self.selected_in_column(self.Columns.TXID):
|
||||||
|
sel_key = self.model().itemFromIndex(s_idx).data(0)
|
||||||
|
selected_keys.append(sel_key)
|
||||||
|
if selected_keys and idx.isValid():
|
||||||
|
column_title = self.model().horizontalHeaderItem(column).text()
|
||||||
|
column_data = '\n'.join(self.model().itemFromIndex(s_idx).text()
|
||||||
|
for s_idx in self.selected_in_column(column))
|
||||||
|
|
||||||
|
menu.addAction(_("details").format(column_title), lambda: self.show_transaction(selected_keys)).setEnabled(len(selected_keys)<2)
|
||||||
|
menu.addAction(_("check ").format(column_title), lambda: self.check_transactions(selected_keys))
|
||||||
|
|
||||||
|
menu.addSeparator()
|
||||||
|
menu.addAction(_("delete").format(column_title), lambda: self.delete(selected_keys))
|
||||||
|
|
||||||
|
menu.exec(self.viewport().mapToGlobal(position))
|
||||||
|
|
||||||
|
def delete(self,selected_keys):
|
||||||
|
for key in selected_keys:
|
||||||
|
del self.will[key]
|
||||||
|
try:
|
||||||
|
del self.bal_window.willitems[key]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
del self.bal_window.will[key]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def check_transactions(self,selected_keys):
|
||||||
|
wout = {}
|
||||||
|
for k in selected_keys:
|
||||||
|
wout[k] = self.will[k]
|
||||||
|
if wout:
|
||||||
|
self.bal_window.check_transactions(wout)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def show_transaction(self,selected_keys):
|
||||||
|
for key in selected_keys:
|
||||||
|
self.bal_window.show_transaction(self.will[key].tx)
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def select(self,selected_keys):
|
||||||
|
self.selected += selected_keys
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def deselect(self,selected_keys):
|
||||||
|
for key in selected_keys:
|
||||||
|
self.selected.remove(key)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update_will(self,will):
|
||||||
|
self.will.update(will)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.will is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_key = self.get_role_data_for_current_item(col=self.Columns.TXID, role=self.ROLE_HEIR_KEY)
|
||||||
|
self.model().clear()
|
||||||
|
self.update_headers(self.__class__.headers)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set_current = None
|
||||||
|
for txid,bal_tx in self.will.items():
|
||||||
|
if self.bal_window.bal_plugin._hide_replaced and bal_tx.get_status('REPLACED'):
|
||||||
|
continue
|
||||||
|
if self.bal_window.bal_plugin._hide_invalidated and bal_tx.get_status('INVALIDATED'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
tx=bal_tx.tx
|
||||||
|
labels = [""] * len(self.Columns)
|
||||||
|
labels[self.Columns.LOCKTIME] = Util.locktime_to_str(tx.locktime)
|
||||||
|
labels[self.Columns.TXID] = txid
|
||||||
|
we = 'None'
|
||||||
|
if bal_tx.we:
|
||||||
|
we = bal_tx.we['url']
|
||||||
|
labels[self.Columns.WILLEXECUTOR]=we
|
||||||
|
status = bal_tx.status
|
||||||
|
if len(bal_tx.status) > 53:
|
||||||
|
status = "...{}".format(status[-50:])
|
||||||
|
labels[self.Columns.STATUS] = status
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
items=[]
|
||||||
|
for e in labels:
|
||||||
|
if type(e)== list:
|
||||||
|
try:
|
||||||
|
items.append(QStandardItem(*e))
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
items.append(QStandardItem(str(e)))
|
||||||
|
|
||||||
|
#pal = QPalette()
|
||||||
|
#pal.setColor(QPalette.ColorRole.Window, QColor(bal_tx.get_color()))
|
||||||
|
#items[-1].setAutoFillBackground(True)
|
||||||
|
#items[-1o].setPalette(pal)
|
||||||
|
items[-1].setBackground(QColor(bal_tx.get_color()))
|
||||||
|
|
||||||
|
|
||||||
|
self.model().insertRow(self.model().rowCount(), items)
|
||||||
|
if txid == current_key:
|
||||||
|
idx = self.model().index(row_count, self.Columns.TXID)
|
||||||
|
set_current = QPersistentModelIndex(idx)
|
||||||
|
self.set_current_idx(set_current)
|
||||||
|
|
||||||
|
def create_toolbar(self, config):
|
||||||
|
toolbar, menu = self.create_toolbar_with_menu('')
|
||||||
|
menu.addAction(_("Prepare"), self.build_transactions)
|
||||||
|
menu.addAction(_("Display"), self.bal_window.preview_modal_dialog)
|
||||||
|
menu.addAction(_("Sign"), self.ask_password_and_sign_transactions)
|
||||||
|
menu.addAction(_("Export"), self.export_will)
|
||||||
|
#menu.addAction(_("Import"), self.import_will)
|
||||||
|
menu.addAction(_("Broadcast"), self.broadcast)
|
||||||
|
menu.addAction(_("Check"), self.check)
|
||||||
|
menu.addAction(_("Invalidate"), self.invalidate_will)
|
||||||
|
prepareButton = QPushButton(_("Prepare"))
|
||||||
|
prepareButton.clicked.connect(self.build_transactions)
|
||||||
|
signButton = QPushButton(_("Sign"))
|
||||||
|
signButton.clicked.connect(self.ask_password_and_sign_transactions)
|
||||||
|
pushButton = QPushButton(_("Broadcast"))
|
||||||
|
pushButton.clicked.connect(self.broadcast)
|
||||||
|
displayButton = QPushButton(_("Display"))
|
||||||
|
displayButton.clicked.connect(self.bal_window.preview_modal_dialog)
|
||||||
|
hlayout = QHBoxLayout()
|
||||||
|
widget = QWidget()
|
||||||
|
hlayout.addWidget(prepareButton)
|
||||||
|
hlayout.addWidget(signButton)
|
||||||
|
hlayout.addWidget(pushButton)
|
||||||
|
hlayout.addWidget(displayButton)
|
||||||
|
widget.setLayout(hlayout)
|
||||||
|
toolbar.insertWidget(2,widget)
|
||||||
|
|
||||||
|
return toolbar
|
||||||
|
|
||||||
|
def hide_replaced(self):
|
||||||
|
self.bal_window.bal_plugin.hide_replaced()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def hide_invalidated(self):
|
||||||
|
f.bal_window.bal_plugin.hide_invalidated()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def build_transactions(self):
|
||||||
|
will = self.bal_window.prepare_will()
|
||||||
|
if will:
|
||||||
|
self.update_will(will)
|
||||||
|
|
||||||
|
def export_json_file(self,path):
|
||||||
|
write_json_file(path, self.will)
|
||||||
|
|
||||||
|
def export_will(self):
|
||||||
|
self.bal_window.export_will()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def import_will(self):
|
||||||
|
self.bal_window.import_will()
|
||||||
|
|
||||||
|
def ask_password_and_sign_transactions(self):
|
||||||
|
self.bal_window.ask_password_and_sign_transactions(callback=self.update)
|
||||||
|
|
||||||
|
def broadcast(self):
|
||||||
|
self.bal_window.broadcast_transactions()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
self.bal_window.check_transactions(self.bal_window.willitems)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def invalidate_will(self):
|
||||||
|
self.bal_window.invalidate_will()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
class PreviewDialog(BalDialog,MessageBoxMixin):
|
||||||
|
def __init__(self, bal_window, will):
|
||||||
|
self.parent = bal_window.window
|
||||||
|
BalDialog.__init__(self,bal_window = bal_window)
|
||||||
|
self.bal_plugin = bal_window.bal_plugin
|
||||||
|
self.gui_object = self.bal_plugin.gui_object
|
||||||
|
self.config = self.bal_plugin.config
|
||||||
|
self.bal_window = bal_window
|
||||||
|
self.wallet = bal_window.window.wallet
|
||||||
|
self.format_amount = bal_window.window.format_amount
|
||||||
|
self.base_unit = bal_window.window.base_unit
|
||||||
|
self.format_fiat_and_units = bal_window.window.format_fiat_and_units
|
||||||
|
self.fx = bal_window.window.fx
|
||||||
|
self.format_fee_rate = bal_window.window.format_fee_rate
|
||||||
|
self.show_address = bal_window.window.show_address
|
||||||
|
if not will:
|
||||||
|
self.will = bal_window.willitems
|
||||||
|
else:
|
||||||
|
self.will = will
|
||||||
|
self.setWindowTitle(_('Transactions Preview'))
|
||||||
|
self.setMinimumSize(1000, 200)
|
||||||
|
self.size_label = QLabel()
|
||||||
|
self.transactions_list = PreviewList(self.bal_window,self.will)
|
||||||
|
vbox = QVBoxLayout(self)
|
||||||
|
vbox.addWidget(self.size_label)
|
||||||
|
vbox.addWidget(self.transactions_list)
|
||||||
|
buttonbox = QHBoxLayout()
|
||||||
|
|
||||||
|
b = QPushButton(_('Sign'))
|
||||||
|
b.clicked.connect(self.transactions_list.ask_password_and_sign_transactions)
|
||||||
|
buttonbox.addWidget(b)
|
||||||
|
|
||||||
|
b = QPushButton(_('Export Will'))
|
||||||
|
b.clicked.connect(self.transactions_list.export_will)
|
||||||
|
buttonbox.addWidget(b)
|
||||||
|
|
||||||
|
b = QPushButton(_('Broadcast'))
|
||||||
|
b.clicked.connect(self.transactions_list.broadcast)
|
||||||
|
buttonbox.addWidget(b)
|
||||||
|
|
||||||
|
b = QPushButton(_('Invalidate will'))
|
||||||
|
b.clicked.connect(self.transactions_list.invalidate_will)
|
||||||
|
buttonbox.addWidget(b)
|
||||||
|
|
||||||
|
vbox.addLayout(buttonbox)
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
|
def update_will(self,will):
|
||||||
|
self.will.update(will)
|
||||||
|
self.transactions_list.update_will(will)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.transactions_list.update()
|
||||||
|
|
||||||
|
def is_hidden(self):
|
||||||
|
return self.isMinimized() or self.isHidden()
|
||||||
|
|
||||||
|
def show_or_hide(self):
|
||||||
|
if self.is_hidden():
|
||||||
|
self.bring_to_top()
|
||||||
|
else:
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
def bring_to_top(self):
|
||||||
|
self.show()
|
||||||
|
self.raise_()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
event.accept()
|
||||||
16
balqt/qt_resources.py
Normal file
16
balqt/qt_resources.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from .. import bal_resources
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
QT_VERSION=sys._GUI_QT_VERSION
|
||||||
|
except:
|
||||||
|
QT_VERSION=6
|
||||||
|
if QT_VERSION == 5:
|
||||||
|
from PyQt5.QtGui import QIcon,QPixmap
|
||||||
|
else:
|
||||||
|
from PyQt6.QtGui import QIcon,QPixmap
|
||||||
|
|
||||||
|
def read_QIcon(icon_basename: str=bal_resources.DEFAULT_ICON) -> QIcon:
|
||||||
|
return QIcon(bal_resources.icon_path(icon_basename))
|
||||||
|
def read_QPixmap(icon_basename: str=bal_resources.DEFAULT_ICON) -> QPixmap:
|
||||||
|
return QPixmap(bal_resources.icon_path(icon_basename))
|
||||||
|
|
||||||
199
balqt/willdetail.py
Normal file
199
balqt/willdetail.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from . import qt_resources
|
||||||
|
if qt_resources.QT_VERSION == 5:
|
||||||
|
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QWidget,QScrollArea)
|
||||||
|
from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,
|
||||||
|
QColor, QDesktopServices, qRgba, QPainterPath,QPalette)
|
||||||
|
else:
|
||||||
|
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QWidget,QScrollArea)
|
||||||
|
from PyQt6.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,
|
||||||
|
QColor, QDesktopServices, qRgba, QPainterPath,QPalette)
|
||||||
|
|
||||||
|
|
||||||
|
from electrum.util import decimal_point_to_base_unit_name
|
||||||
|
from electrum.i18n import _
|
||||||
|
|
||||||
|
from ..bal import BalPlugin
|
||||||
|
from .. import will as Will
|
||||||
|
from .. import util as Util
|
||||||
|
from .baldialog import BalDialog
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class WillDetailDialog(BalDialog):
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, bal_window):
|
||||||
|
|
||||||
|
self.will = bal_window.willitems
|
||||||
|
self.threshold = Util.parse_locktime_string(bal_window.will_settings['threshold'])
|
||||||
|
self.bal_window = bal_window
|
||||||
|
Will.add_willtree(self.will)
|
||||||
|
super().__init__(bal_window.window)
|
||||||
|
self.config = bal_window.window.config
|
||||||
|
self.wallet = bal_window.wallet
|
||||||
|
self.format_amount = bal_window.window.format_amount
|
||||||
|
self.base_unit = bal_window.window.base_unit
|
||||||
|
self.format_fiat_and_units = bal_window.window.format_fiat_and_units
|
||||||
|
self.fx = bal_window.window.fx
|
||||||
|
self.format_fee_rate = bal_window.window.format_fee_rate
|
||||||
|
self.decimal_point = bal_window.bal_plugin.config.get_decimal_point()
|
||||||
|
self.base_unit_name = decimal_point_to_base_unit_name(self.decimal_point)
|
||||||
|
self.setWindowTitle(_('Will Details'))
|
||||||
|
self.setMinimumSize(670,700)
|
||||||
|
self.vlayout= QVBoxLayout()
|
||||||
|
w=QWidget()
|
||||||
|
hlayout = QHBoxLayout(w)
|
||||||
|
|
||||||
|
b = QPushButton(_('Sign'))
|
||||||
|
b.clicked.connect(self.ask_password_and_sign_transactions)
|
||||||
|
hlayout.addWidget(b)
|
||||||
|
|
||||||
|
b = QPushButton(_('Broadcast'))
|
||||||
|
b.clicked.connect(self.broadcast_transactions)
|
||||||
|
hlayout.addWidget(b)
|
||||||
|
|
||||||
|
b = QPushButton(_('Export'))
|
||||||
|
b.clicked.connect(self.export_will)
|
||||||
|
hlayout.addWidget(b)
|
||||||
|
"""
|
||||||
|
toggle = "Hide"
|
||||||
|
if self.bal_window.bal_plugin._hide_replaced:
|
||||||
|
toggle = "Unhide"
|
||||||
|
self.toggle_replace_button = QPushButton(_(f"{toggle} replaced"))
|
||||||
|
self.toggle_replace_button.clicked.connect(self.toggle_replaced)
|
||||||
|
hlayout.addWidget(self.toggle_replace_button)
|
||||||
|
|
||||||
|
toggle = "Hide"
|
||||||
|
if self.bal_window.bal_plugin._hide_invalidated:
|
||||||
|
toggle = "Unhide"
|
||||||
|
|
||||||
|
self.toggle_invalidate_button = QPushButton(_(f"{toggle} invalidated"))
|
||||||
|
self.toggle_invalidate_button.clicked.connect(self.toggle_invalidated)
|
||||||
|
hlayout.addWidget(self.toggle_invalidate_button)
|
||||||
|
"""
|
||||||
|
b = QPushButton(_('Invalidate'))
|
||||||
|
b.clicked.connect(bal_window.invalidate_will)
|
||||||
|
hlayout.addWidget(b)
|
||||||
|
self.vlayout.addWidget(w)
|
||||||
|
|
||||||
|
self.paint_scroll_area()
|
||||||
|
#vlayout.addWidget(QLabel(_("DON'T PANIC !!! everything is fine, all possible futures are covered")))
|
||||||
|
self.vlayout.addWidget(QLabel(_("Expiration date: ")+Util.locktime_to_str(self.threshold)))
|
||||||
|
self.vlayout.addWidget(self.scrollbox)
|
||||||
|
w=QWidget()
|
||||||
|
hlayout = QHBoxLayout(w)
|
||||||
|
hlayout.addWidget(QLabel(_("Valid Txs:")+ str(len(Will.only_valid_list(self.will)))))
|
||||||
|
hlayout.addWidget(QLabel(_("Total Txs:")+ str(len(self.will))))
|
||||||
|
self.vlayout.addWidget(w)
|
||||||
|
self.setLayout(self.vlayout)
|
||||||
|
|
||||||
|
def paint_scroll_area(self):
|
||||||
|
#self.scrollbox.deleteLater()
|
||||||
|
#self.willlayout.deleteLater()
|
||||||
|
#self.detailsWidget.deleteLater()
|
||||||
|
self.scrollbox = QScrollArea()
|
||||||
|
viewport = QWidget(self.scrollbox)
|
||||||
|
self.willlayout = QVBoxLayout(viewport)
|
||||||
|
self.detailsWidget = WillWidget(parent=self)
|
||||||
|
self.willlayout.addWidget(self.detailsWidget)
|
||||||
|
|
||||||
|
self.scrollbox.setWidget(viewport)
|
||||||
|
viewport.setLayout(self.willlayout)
|
||||||
|
def ask_password_and_sign_transactions(self):
|
||||||
|
self.bal_window.ask_password_and_sign_transactions(callback=self.update)
|
||||||
|
self.update()
|
||||||
|
def broadcast_transactions(self):
|
||||||
|
self.bal_window.broadcast_transactions()
|
||||||
|
self.update()
|
||||||
|
def export_will(self):
|
||||||
|
self.bal_window.export_will()
|
||||||
|
def toggle_replaced(self):
|
||||||
|
self.bal_window.bal_plugin.hide_replaced()
|
||||||
|
toggle = _("Hide")
|
||||||
|
if self.bal_window.bal_plugin._hide_replaced:
|
||||||
|
toggle = _("Unhide")
|
||||||
|
self.toggle_replace_button.setText(f"{toggle} {_('replaced')}")
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def toggle_invalidated(self):
|
||||||
|
self.bal_window.bal_plugin.hide_invalidated()
|
||||||
|
toggle = _("Hide")
|
||||||
|
if self.bal_window.bal_plugin._hide_invalidated:
|
||||||
|
toggle = _("Unhide")
|
||||||
|
self.toggle_invalidate_button.setText(_(f"{toggle} {_('invalidated')}"))
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.will = self.bal_window.willitems
|
||||||
|
pos = self.vlayout.indexOf(self.scrollbox)
|
||||||
|
self.vlayout.removeWidget(self.scrollbox)
|
||||||
|
self.paint_scroll_area()
|
||||||
|
self.vlayout.insertWidget(pos,self.scrollbox)
|
||||||
|
super().update()
|
||||||
|
|
||||||
|
class WillWidget(QWidget):
|
||||||
|
def __init__(self,father=None,parent = None):
|
||||||
|
super().__init__()
|
||||||
|
vlayout = QVBoxLayout()
|
||||||
|
self.setLayout(vlayout)
|
||||||
|
self.will = parent.bal_window.willitems
|
||||||
|
self.parent = parent
|
||||||
|
for w in self.will:
|
||||||
|
if self.will[w].get_status('REPLACED') and self.parent.bal_window.bal_plugin._hide_replaced:
|
||||||
|
continue
|
||||||
|
if self.will[w].get_status('INVALIDATED') and self.parent.bal_window.bal_plugin._hide_invalidated:
|
||||||
|
continue
|
||||||
|
f = self.will[w].father
|
||||||
|
if father == f:
|
||||||
|
qwidget = QWidget()
|
||||||
|
childWidget = QWidget()
|
||||||
|
hlayout=QHBoxLayout(qwidget)
|
||||||
|
qwidget.setLayout(hlayout)
|
||||||
|
vlayout.addWidget(qwidget)
|
||||||
|
detailw=QWidget()
|
||||||
|
detaillayout=QVBoxLayout()
|
||||||
|
detailw.setLayout(detaillayout)
|
||||||
|
|
||||||
|
willpushbutton = QPushButton(w)
|
||||||
|
|
||||||
|
willpushbutton.clicked.connect(partial(self.parent.bal_window.show_transaction,txid=w))
|
||||||
|
detaillayout.addWidget(willpushbutton)
|
||||||
|
locktime = Util.locktime_to_str(self.will[w].tx.locktime)
|
||||||
|
creation = Util.locktime_to_str(self.will[w].time)
|
||||||
|
def qlabel(title,value):
|
||||||
|
label = "<b>"+_(str(title)) + f":</b>\t{str(value)}"
|
||||||
|
return QLabel(label)
|
||||||
|
detaillayout.addWidget(qlabel("Locktime",locktime))
|
||||||
|
detaillayout.addWidget(qlabel("Creation Time",creation))
|
||||||
|
total_fees = self.will[w].tx.input_value() - self.will[w].tx.output_value()
|
||||||
|
decoded_fees = total_fees #Util.decode_amount(total_fees,self.parent.decimal_point)
|
||||||
|
fee_per_byte = round(total_fees/self.will[w].tx.estimated_size(),3)
|
||||||
|
fees_str = str(decoded_fees) + " ("+ str(fee_per_byte) + " sats/vbyte)"
|
||||||
|
detaillayout.addWidget(qlabel("Transaction fees:",fees_str))
|
||||||
|
detaillayout.addWidget(qlabel("Status:",self.will[w].status))
|
||||||
|
detaillayout.addWidget(QLabel(""))
|
||||||
|
detaillayout.addWidget(QLabel("<b>Heirs:</b>"))
|
||||||
|
for heir in self.will[w].heirs:
|
||||||
|
if "w!ll3x3c\"" not in heir:
|
||||||
|
decoded_amount = Util.decode_amount(self.will[w].heirs[heir][3],self.parent.decimal_point)
|
||||||
|
detaillayout.addWidget(qlabel(heir,f"{decoded_amount} {self.parent.base_unit_name}"))
|
||||||
|
if self.will[w].we:
|
||||||
|
detaillayout.addWidget(QLabel(""))
|
||||||
|
detaillayout.addWidget(QLabel(_("<b>Willexecutor:</b:")))
|
||||||
|
decoded_amount = Util.decode_amount(self.will[w].we['base_fee'],self.parent.decimal_point)
|
||||||
|
|
||||||
|
detaillayout.addWidget(qlabel(self.will[w].we['url'],f"{decoded_amount} {self.parent.base_unit_name}"))
|
||||||
|
detaillayout.addStretch()
|
||||||
|
pal = QPalette()
|
||||||
|
pal.setColor(QPalette.ColorRole.Window, QColor(self.will[w].get_color()))
|
||||||
|
detailw.setAutoFillBackground(True)
|
||||||
|
detailw.setPalette(pal)
|
||||||
|
|
||||||
|
hlayout.addWidget(detailw)
|
||||||
|
hlayout.addWidget(WillWidget(w,parent = parent))
|
||||||
|
|
||||||
291
balqt/willexecutor_dialog.py
Normal file
291
balqt/willexecutor_dialog.py
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
import enum
|
||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
from . import qt_resources
|
||||||
|
if qt_resources.QT_VERSION == 5:
|
||||||
|
from PyQt5.QtGui import QStandardItemModel, QStandardItem
|
||||||
|
from PyQt5.QtCore import Qt,QPersistentModelIndex, QModelIndex
|
||||||
|
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QMenu)
|
||||||
|
else:
|
||||||
|
from PyQt6.QtGui import QStandardItemModel, QStandardItem
|
||||||
|
from PyQt6.QtCore import Qt,QPersistentModelIndex, QModelIndex
|
||||||
|
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QMenu)
|
||||||
|
|
||||||
|
|
||||||
|
from electrum.i18n import _
|
||||||
|
from electrum.gui.qt.util import (Buttons,read_QIcon, import_meta_gui, export_meta_gui,MessageBoxMixin)
|
||||||
|
from electrum.util import write_json_file,read_json_file
|
||||||
|
from electrum.gui.qt.my_treeview import MyTreeView
|
||||||
|
|
||||||
|
from ..bal import BalPlugin
|
||||||
|
from .. import util as Util
|
||||||
|
from .. import willexecutors as Willexecutors
|
||||||
|
from .baldialog import BalDialog,BalBlockingWaitingDialog
|
||||||
|
from electrum.logging import get_logger,Logger
|
||||||
|
|
||||||
|
_logger=get_logger(__name__)
|
||||||
|
class WillExecutorList(MyTreeView):
|
||||||
|
class Columns(MyTreeView.BaseColumnsEnum):
|
||||||
|
SELECTED = enum.auto()
|
||||||
|
URL = enum.auto()
|
||||||
|
BASE_FEE = enum.auto()
|
||||||
|
INFO = enum.auto()
|
||||||
|
ADDRESS = enum.auto()
|
||||||
|
STATUS = enum.auto()
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
Columns.SELECTED:_(''),
|
||||||
|
Columns.URL: _('Url'),
|
||||||
|
Columns.BASE_FEE: _('Base fee'),
|
||||||
|
Columns.INFO:_('Info'),
|
||||||
|
Columns.ADDRESS:_('Default Address'),
|
||||||
|
Columns.STATUS: _('S'),
|
||||||
|
}
|
||||||
|
|
||||||
|
ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 2000
|
||||||
|
key_role = ROLE_HEIR_KEY
|
||||||
|
|
||||||
|
def __init__(self, parent: 'WillExecutorDialog'):
|
||||||
|
super().__init__(
|
||||||
|
parent=parent,
|
||||||
|
stretch_column=self.Columns.ADDRESS,
|
||||||
|
editable_columns=[self.Columns.URL,self.Columns.BASE_FEE,self.Columns.ADDRESS,self.Columns.INFO],
|
||||||
|
|
||||||
|
)
|
||||||
|
self.parent = parent
|
||||||
|
self.setModel(QStandardItemModel(self))
|
||||||
|
self.setSortingEnabled(True)
|
||||||
|
self.std_model = self.model()
|
||||||
|
self.config =parent.bal_plugin.config
|
||||||
|
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_menu(self, position):
|
||||||
|
menu = QMenu()
|
||||||
|
idx = self.indexAt(position)
|
||||||
|
column = idx.column() or self.Columns.URL
|
||||||
|
selected_keys = []
|
||||||
|
for s_idx in self.selected_in_column(self.Columns.URL):
|
||||||
|
sel_key = self.model().itemFromIndex(s_idx).data(0)
|
||||||
|
selected_keys.append(sel_key)
|
||||||
|
if selected_keys and idx.isValid():
|
||||||
|
column_title = self.model().horizontalHeaderItem(column).text()
|
||||||
|
column_data = '\n'.join(self.model().itemFromIndex(s_idx).text()
|
||||||
|
for s_idx in self.selected_in_column(column))
|
||||||
|
if Willexecutors.is_selected(self.parent.willexecutors_list[sel_key]):
|
||||||
|
menu.addAction(_("deselect").format(column_title), lambda: self.deselect(selected_keys))
|
||||||
|
else:
|
||||||
|
menu.addAction(_("select").format(column_title), lambda: self.select(selected_keys))
|
||||||
|
if column in self.editable_columns:
|
||||||
|
item = self.model().itemFromIndex(idx)
|
||||||
|
if item.isEditable():
|
||||||
|
persistent = QPersistentModelIndex(idx)
|
||||||
|
menu.addAction(_("Edit {}").format(column_title), lambda p=persistent: self.edit(QModelIndex(p)))
|
||||||
|
|
||||||
|
menu.addAction(_("Ping").format(column_title), lambda: self.ping_willexecutors(selected_keys))
|
||||||
|
menu.addSeparator()
|
||||||
|
menu.addAction(_("delete").format(column_title), lambda: self.delete(selected_keys))
|
||||||
|
|
||||||
|
menu.exec(self.viewport().mapToGlobal(position))
|
||||||
|
|
||||||
|
def ping_willexecutors(self,selected_keys):
|
||||||
|
wout={}
|
||||||
|
for k in selected_keys:
|
||||||
|
wout[k]=self.parent.willexecutors_list[k]
|
||||||
|
self.parent.update_willexecutors(wout)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def get_edit_key_from_coordinate(self, row, col):
|
||||||
|
a= self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY+col)
|
||||||
|
return a
|
||||||
|
|
||||||
|
def delete(self,selected_keys):
|
||||||
|
for key in selected_keys:
|
||||||
|
del self.parent.willexecutors_list[key]
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def select(self,selected_keys):
|
||||||
|
for wid,w in self.parent.willexecutors_list.items():
|
||||||
|
if wid in selected_keys:
|
||||||
|
w['selected']=True
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def deselect(self,selected_keys):
|
||||||
|
for wid,w in self.parent.willexecutors_list.items():
|
||||||
|
if wid in selected_keys:
|
||||||
|
w['selected']=False
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def on_edited(self, idx, edit_key, *, text):
|
||||||
|
prior_name = self.parent.willexecutors_list[edit_key]
|
||||||
|
col = idx.column()
|
||||||
|
try:
|
||||||
|
if col == self.Columns.URL:
|
||||||
|
self.parent.willexecutors_list[text]=self.parent.willexecutors_list[edit_key]
|
||||||
|
del self.parent.willexecutors_list[edit_key]
|
||||||
|
if col == self.Columns.BASE_FEE:
|
||||||
|
self.parent.willexecutors_list[edit_key]["base_fee"] = Util.encode_amount(text,self.config.get_decimal_point())
|
||||||
|
if col == self.Columns.ADDRESS:
|
||||||
|
self.parent.willexecutors_list[edit_key]["address"] = text
|
||||||
|
if col == self.Columns.INFO:
|
||||||
|
self.parent.willexecutors_list[edit_key]["info"] = text
|
||||||
|
self.update()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.parent.willexecutors_list is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
current_key = self.get_role_data_for_current_item(col=self.Columns.URL, role=self.ROLE_HEIR_KEY)
|
||||||
|
self.model().clear()
|
||||||
|
self.update_headers(self.__class__.headers)
|
||||||
|
|
||||||
|
set_current = None
|
||||||
|
|
||||||
|
for url, value in self.parent.willexecutors_list.items():
|
||||||
|
labels = [""] * len(self.Columns)
|
||||||
|
labels[self.Columns.URL] = url
|
||||||
|
if Willexecutors.is_selected(value):
|
||||||
|
labels[self.Columns.SELECTED] = [read_QIcon('confirmed.png'),'']
|
||||||
|
else:
|
||||||
|
labels[self.Columns.SELECTED] = ''
|
||||||
|
labels[self.Columns.BASE_FEE] = Util.decode_amount(value.get('base_fee',0),self.config.get_decimal_point())
|
||||||
|
if str(value.get('status',0)) == "200":
|
||||||
|
labels[self.Columns.STATUS] = [read_QIcon('status_connected.png'),'']
|
||||||
|
else:
|
||||||
|
labels[self.Columns.STATUS] = [read_QIcon('unconfirmed.png'),'']
|
||||||
|
labels[self.Columns.ADDRESS] = str(value.get('address',''))
|
||||||
|
labels[self.Columns.INFO] = str(value.get('info',''))
|
||||||
|
|
||||||
|
items=[]
|
||||||
|
for e in labels:
|
||||||
|
if type(e)== list:
|
||||||
|
try:
|
||||||
|
items.append(QStandardItem(*e))
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
items.append(QStandardItem(e))
|
||||||
|
items[self.Columns.SELECTED].setEditable(False)
|
||||||
|
items[self.Columns.URL].setEditable(True)
|
||||||
|
items[self.Columns.ADDRESS].setEditable(True)
|
||||||
|
items[self.Columns.INFO].setEditable(True)
|
||||||
|
items[self.Columns.BASE_FEE].setEditable(True)
|
||||||
|
items[self.Columns.STATUS].setEditable(False)
|
||||||
|
|
||||||
|
items[self.Columns.URL].setData(url, self.ROLE_HEIR_KEY+1)
|
||||||
|
items[self.Columns.BASE_FEE].setData(url, self.ROLE_HEIR_KEY+2)
|
||||||
|
items[self.Columns.INFO].setData(url, self.ROLE_HEIR_KEY+3)
|
||||||
|
items[self.Columns.ADDRESS].setData(url, self.ROLE_HEIR_KEY+4)
|
||||||
|
|
||||||
|
|
||||||
|
self.model().insertRow(self.model().rowCount(), items)
|
||||||
|
if url == current_key:
|
||||||
|
idx = self.model().index(row_count, self.Columns.NAME)
|
||||||
|
set_current = QPersistentModelIndex(idx)
|
||||||
|
self.set_current_idx(set_current)
|
||||||
|
self.parent.save_willexecutors()
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(e)
|
||||||
|
|
||||||
|
|
||||||
|
class WillExecutorDialog(BalDialog,MessageBoxMixin):
|
||||||
|
def __init__(self, bal_window):
|
||||||
|
BalDialog.__init__(self,bal_window.window)
|
||||||
|
self.bal_plugin = bal_window.bal_plugin
|
||||||
|
self.config = self.bal_plugin.config
|
||||||
|
self.window = bal_window.window
|
||||||
|
self.bal_window = bal_window
|
||||||
|
self.willexecutors_list = Willexecutors.get_willexecutors(self.bal_plugin)
|
||||||
|
|
||||||
|
self.setWindowTitle(_('Will-Executor Service List'))
|
||||||
|
self.setMinimumSize(1000, 200)
|
||||||
|
self.size_label = QLabel()
|
||||||
|
self.willexecutor_list = WillExecutorList(self)
|
||||||
|
|
||||||
|
vbox = QVBoxLayout(self)
|
||||||
|
vbox.addWidget(self.size_label)
|
||||||
|
vbox.addWidget(self.willexecutor_list)
|
||||||
|
buttonbox = QHBoxLayout()
|
||||||
|
|
||||||
|
b = QPushButton(_('Ping'))
|
||||||
|
b.clicked.connect(self.update_willexecutors)
|
||||||
|
buttonbox.addWidget(b)
|
||||||
|
|
||||||
|
b = QPushButton(_('Import'))
|
||||||
|
b.clicked.connect(self.import_file)
|
||||||
|
buttonbox.addWidget(b)
|
||||||
|
|
||||||
|
b = QPushButton(_('Export'))
|
||||||
|
b.clicked.connect(self.export_file)
|
||||||
|
buttonbox.addWidget(b)
|
||||||
|
|
||||||
|
b = QPushButton(_('Add'))
|
||||||
|
b.clicked.connect(self.add)
|
||||||
|
buttonbox.addWidget(b)
|
||||||
|
|
||||||
|
b = QPushButton(_('Close'))
|
||||||
|
b.clicked.connect(self.close)
|
||||||
|
buttonbox.addWidget(b)
|
||||||
|
|
||||||
|
vbox.addLayout(buttonbox)
|
||||||
|
|
||||||
|
self.willexecutor_list.update()
|
||||||
|
|
||||||
|
def add(self):
|
||||||
|
self.willexecutors_list["http://localhost:8080"]={"info":"New Will Executor","base_fee":0,"status":"-1"}
|
||||||
|
self.willexecutor_list.update()
|
||||||
|
|
||||||
|
def import_file(self):
|
||||||
|
import_meta_gui(self, _('willexecutors.json'), self.import_json_file, self.willexecutors_list.update)
|
||||||
|
|
||||||
|
def export_file(self, path):
|
||||||
|
Util.export_meta_gui(self, _('willexecutors.json'), self.export_json_file)
|
||||||
|
|
||||||
|
def export_json_file(self,path):
|
||||||
|
write_json_file(path, self.willexecutors_list)
|
||||||
|
|
||||||
|
def update_willexecutors(self,wes=None):
|
||||||
|
if not wes:
|
||||||
|
self.willexecutors_list = Willexecutors.get_willexecutors(self.bal_plugin, update = True, bal_window = self.bal_window,force=True)
|
||||||
|
else:
|
||||||
|
self.bal_window.ping_willexecutors(wes)
|
||||||
|
self.willexecutors_list.update(wes)
|
||||||
|
self.willexecutor_list.update()
|
||||||
|
|
||||||
|
|
||||||
|
def import_json_file(self, path):
|
||||||
|
data = read_json_file(path)
|
||||||
|
data = self._validate(data)
|
||||||
|
self.willexecutors_list.update(data)
|
||||||
|
self.willexecutor_list.update()
|
||||||
|
|
||||||
|
#TODO validate willexecutor json import file
|
||||||
|
def _validate(self,data):
|
||||||
|
return data
|
||||||
|
|
||||||
|
def is_hidden(self):
|
||||||
|
return self.isMinimized() or self.isHidden()
|
||||||
|
|
||||||
|
def show_or_hide(self):
|
||||||
|
if self.is_hidden():
|
||||||
|
self.bring_to_top()
|
||||||
|
else:
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
def bring_to_top(self):
|
||||||
|
self.show()
|
||||||
|
self.raise_()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def save_willexecutors(self):
|
||||||
|
self.bal_plugin.config.set_key(self.bal_plugin.WILLEXECUTORS,self.willexecutors_list,save=True)
|
||||||
|
|
||||||
263
heirs.py
263
heirs.py
@@ -1,38 +1,25 @@
|
|||||||
import datetime
|
|
||||||
import json
|
|
||||||
import math
|
|
||||||
import random
|
|
||||||
import re
|
import re
|
||||||
import threading
|
import json
|
||||||
import urllib.parse
|
from typing import Optional, Tuple, Dict, Any, TYPE_CHECKING,Sequence,List
|
||||||
import urllib.request
|
|
||||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple
|
|
||||||
|
|
||||||
import dns
|
import dns
|
||||||
|
import threading
|
||||||
|
import math
|
||||||
from dns.exception import DNSException
|
from dns.exception import DNSException
|
||||||
from electrum import bitcoin, constants, descriptor, dnssec
|
|
||||||
from electrum.logging import Logger, get_logger
|
|
||||||
from electrum.transaction import (
|
|
||||||
PartialTransaction,
|
|
||||||
PartialTxInput,
|
|
||||||
PartialTxOutput,
|
|
||||||
TxOutpoint,
|
|
||||||
TxOutput,
|
|
||||||
)
|
|
||||||
from electrum.util import (
|
|
||||||
bfh,
|
|
||||||
read_json_file,
|
|
||||||
to_string,
|
|
||||||
trigger_callback,
|
|
||||||
write_json_file,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
from electrum import bitcoin,dnssec,descriptor,constants
|
||||||
|
from electrum.util import read_json_file, write_json_file, to_string,bfh,trigger_callback
|
||||||
|
from electrum.logging import Logger, get_logger
|
||||||
|
from electrum.transaction import PartialTxInput, PartialTxOutput,TxOutpoint,PartialTransaction,TxOutput
|
||||||
|
import datetime
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
import random
|
||||||
from .util import Util
|
from .util import Util
|
||||||
from .willexecutors import Willexecutors
|
from .willexecutors import Willexecutors
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .simple_config import SimpleConfig
|
|
||||||
from .wallet_db import WalletDB
|
from .wallet_db import WalletDB
|
||||||
|
from .simple_config import SimpleConfig
|
||||||
|
|
||||||
|
|
||||||
_logger = get_logger(__name__)
|
_logger = get_logger(__name__)
|
||||||
@@ -42,8 +29,6 @@ HEIR_AMOUNT = 1
|
|||||||
HEIR_LOCKTIME = 2
|
HEIR_LOCKTIME = 2
|
||||||
HEIR_REAL_AMOUNT = 3
|
HEIR_REAL_AMOUNT = 3
|
||||||
TRANSACTION_LABEL = "inheritance transaction"
|
TRANSACTION_LABEL = "inheritance transaction"
|
||||||
|
|
||||||
|
|
||||||
class AliasNotFoundException(Exception):
|
class AliasNotFoundException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -53,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'):
|
||||||
@@ -77,13 +61,10 @@ def get_current_height(network:'Network'):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_transactions(locktimes, available_utxos, fees, wallet):
|
def prepare_transactions(locktimes, available_utxos, fees, wallet):
|
||||||
available_utxos = sorted(
|
available_utxos=sorted(available_utxos, key=lambda x:"{}:{}:{}".format(x.value_sats(),x.prevout.txid,x.prevout.out_idx))
|
||||||
available_utxos,
|
|
||||||
key=lambda x: "{}:{}:{}".format(
|
|
||||||
x.value_sats(), x.prevout.txid, x.prevout.out_idx
|
|
||||||
),
|
|
||||||
)
|
|
||||||
total_used_utxos = []
|
total_used_utxos = []
|
||||||
txsout={}
|
txsout={}
|
||||||
locktime,_=Util.get_lowest_locktimes(locktimes)
|
locktime,_=Util.get_lowest_locktimes(locktimes)
|
||||||
@@ -108,11 +89,7 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
|
|||||||
out_amount += real_amount
|
out_amount += real_amount
|
||||||
description += f"{name}\n"
|
description += f"{name}\n"
|
||||||
paid_heirs[name]=heir
|
paid_heirs[name]=heir
|
||||||
outputs.append(
|
outputs.append(PartialTxOutput.from_address_and_value(heir[HEIR_ADDRESS], real_amount))
|
||||||
PartialTxOutput.from_address_and_value(
|
|
||||||
heir[HEIR_ADDRESS], real_amount
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -138,16 +115,10 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
|
|||||||
outputs.append(change)
|
outputs.append(change)
|
||||||
for i in range(0,100):
|
for i in range(0,100):
|
||||||
random.shuffle(outputs)
|
random.shuffle(outputs)
|
||||||
tx = PartialTransaction.from_io(
|
print(outputs)
|
||||||
used_utxos,
|
tx = PartialTransaction.from_io(used_utxos, outputs, locktime=Util.parse_locktime_string(locktime,wallet), version=2)
|
||||||
outputs,
|
if len(description)>0: tx.description = description[:-1]
|
||||||
locktime=Util.parse_locktime_string(locktime, wallet),
|
else: tx.description = ""
|
||||||
version=2,
|
|
||||||
)
|
|
||||||
if len(description) > 0:
|
|
||||||
tx.description = description[:-1]
|
|
||||||
else:
|
|
||||||
tx.description = ""
|
|
||||||
tx.heirsvalue = heirsvalue
|
tx.heirsvalue = heirsvalue
|
||||||
tx.set_rbf(True)
|
tx.set_rbf(True)
|
||||||
tx.remove_signatures()
|
tx.remove_signatures()
|
||||||
@@ -178,11 +149,10 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet):
|
|||||||
def get_utxos_from_inputs(tx_inputs,tx,utxos):
|
def get_utxos_from_inputs(tx_inputs,tx,utxos):
|
||||||
for tx_input in tx_inputs:
|
for tx_input in tx_inputs:
|
||||||
prevoutstr=tx_input.prevout.to_str()
|
prevoutstr=tx_input.prevout.to_str()
|
||||||
utxos[prevoutstr] = utxos.get(prevoutstr, {"input": tx_input, "txs": []})
|
utxos[prevoutstr] =utxos.get(prevoutstr,{'input':tx_input,'txs':[]})
|
||||||
utxos[prevoutstr]["txs"].append(tx)
|
utxos[prevoutstr]['txs'].append(tx)
|
||||||
return utxos
|
return utxos
|
||||||
|
|
||||||
|
|
||||||
#TODO calculate de minimum inputs to be invalidated
|
#TODO calculate de minimum inputs to be invalidated
|
||||||
def invalidate_inheritance_transactions(wallet):
|
def invalidate_inheritance_transactions(wallet):
|
||||||
listids = []
|
listids = []
|
||||||
@@ -199,7 +169,7 @@ def invalidate_inheritance_transactions(wallet):
|
|||||||
for key,utxo in utxos.items():
|
for key,utxo in utxos.items():
|
||||||
txid=key.split(":")[0]
|
txid=key.split(":")[0]
|
||||||
if txid in dtxs:
|
if txid in dtxs:
|
||||||
for tx in utxo["txs"]:
|
for tx in utxo['txs']:
|
||||||
txid =tx.txid()
|
txid =tx.txid()
|
||||||
del dtxs[txid]
|
del dtxs[txid]
|
||||||
|
|
||||||
@@ -209,16 +179,16 @@ def invalidate_inheritance_transactions(wallet):
|
|||||||
|
|
||||||
utxos = sorted(utxos.items(), key = lambda item: len(item[1]))
|
utxos = sorted(utxos.items(), key = lambda item: len(item[1]))
|
||||||
|
|
||||||
|
|
||||||
remaining={}
|
remaining={}
|
||||||
invalidated = []
|
invalidated = []
|
||||||
for key,value in utxos:
|
for key,value in utxos:
|
||||||
for tx in value["txs"]:
|
for tx in value['txs']:
|
||||||
txid = tx.txid()
|
txid = tx.txid()
|
||||||
if not txid in invalidated:
|
if not txid in invalidated:
|
||||||
invalidated.append(tx.txid())
|
invalidated.append(tx.txid())
|
||||||
remaining[key] = value
|
remaining[key] = value
|
||||||
|
|
||||||
|
|
||||||
def print_transaction(heirs,tx,locktimes,tx_fees):
|
def print_transaction(heirs,tx,locktimes,tx_fees):
|
||||||
jtx=tx.to_json()
|
jtx=tx.to_json()
|
||||||
print(f"TX: {tx.txid()}\t-\tLocktime: {jtx['locktime']}")
|
print(f"TX: {tx.txid()}\t-\tLocktime: {jtx['locktime']}")
|
||||||
@@ -230,19 +200,13 @@ def print_transaction(heirs, tx, locktimes, tx_fees):
|
|||||||
heirname=""
|
heirname=""
|
||||||
for key in heirs.keys():
|
for key in heirs.keys():
|
||||||
heir=heirs[key]
|
heir=heirs[key]
|
||||||
if heir[HEIR_ADDRESS] == out["address"] and str(heir[HEIR_LOCKTIME]) == str(
|
if heir[HEIR_ADDRESS] == out['address'] and str(heir[HEIR_LOCKTIME]) == str(jtx['locktime']):
|
||||||
jtx["locktime"]
|
|
||||||
):
|
|
||||||
heirname=key
|
heirname=key
|
||||||
print(f"{heirname}\t{out['address']}: {out['value_sats']}")
|
print(f"{heirname}\t{out['address']}: {out['value_sats']}")
|
||||||
|
|
||||||
print()
|
print()
|
||||||
size = tx.estimated_size()
|
size = tx.estimated_size()
|
||||||
print(
|
print("fee: {}\texpected: {}\tsize: {}".format(tx.input_value()-tx.output_value(), size*tx_fees, size))
|
||||||
"fee: {}\texpected: {}\tsize: {}".format(
|
|
||||||
tx.input_value() - tx.output_value(), size * tx_fees, size
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
print()
|
print()
|
||||||
try:
|
try:
|
||||||
@@ -251,7 +215,6 @@ def print_transaction(heirs, tx, locktimes, tx_fees):
|
|||||||
print("impossible to serialize")
|
print("impossible to serialize")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
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():
|
||||||
@@ -260,13 +223,12 @@ def get_change_output(wallet, in_amount, out_amount, fee):
|
|||||||
out.is_change = True
|
out.is_change = True
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
class Heirs(dict, Logger):
|
class Heirs(dict, Logger):
|
||||||
|
|
||||||
def __init__(self, db: "WalletDB"):
|
def __init__(self, db: 'WalletDB'):
|
||||||
Logger.__init__(self)
|
Logger.__init__(self)
|
||||||
self.db = db
|
self.db = db
|
||||||
d = self.db.get("heirs", {})
|
d = self.db.get('heirs', {})
|
||||||
try:
|
try:
|
||||||
self.update(d)
|
self.update(d)
|
||||||
except e as Exception:
|
except e as Exception:
|
||||||
@@ -276,7 +238,7 @@ class Heirs(dict, Logger):
|
|||||||
invalidate_inheritance_transactions(wallet)
|
invalidate_inheritance_transactions(wallet)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
self.db.put("heirs", dict(self))
|
self.db.put('heirs', dict(self))
|
||||||
|
|
||||||
def import_file(self, path):
|
def import_file(self, path):
|
||||||
data = read_json_file(path)
|
data = read_json_file(path)
|
||||||
@@ -301,29 +263,21 @@ class Heirs(dict, Logger):
|
|||||||
locktimes = {}
|
locktimes = {}
|
||||||
for key in self.keys():
|
for key in self.keys():
|
||||||
locktime = Util.parse_locktime_string(self[key][HEIR_LOCKTIME])
|
locktime = Util.parse_locktime_string(self[key][HEIR_LOCKTIME])
|
||||||
if locktime > from_locktime and not a or locktime <= from_locktime and a:
|
if locktime > from_locktime and not a \
|
||||||
|
or locktime <=from_locktime and a:
|
||||||
locktimes[int(locktime)]=None
|
locktimes[int(locktime)]=None
|
||||||
return locktimes.keys()
|
return locktimes.keys()
|
||||||
|
|
||||||
def check_locktime(self):
|
def check_locktime(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def normalize_perc(
|
def normalize_perc(self, heir_list, total_balance, relative_balance,wallet,real=False):
|
||||||
self, heir_list, total_balance, relative_balance, wallet, real=False
|
|
||||||
):
|
|
||||||
amount = 0
|
amount = 0
|
||||||
for key,v in heir_list.items():
|
for key,v in heir_list.items():
|
||||||
try:
|
try:
|
||||||
column = HEIR_AMOUNT
|
column = HEIR_AMOUNT
|
||||||
if real:
|
if real: column = HEIR_REAL_AMOUNT
|
||||||
column = HEIR_REAL_AMOUNT
|
value = int(math.floor(total_balance/relative_balance*self.amount_to_float(v[column])))
|
||||||
value = int(
|
|
||||||
math.floor(
|
|
||||||
total_balance
|
|
||||||
/ relative_balance
|
|
||||||
* self.amount_to_float(v[column])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if value > wallet.dust_threshold():
|
if value > wallet.dust_threshold():
|
||||||
heir_list[key].insert(HEIR_REAL_AMOUNT, value)
|
heir_list[key].insert(HEIR_REAL_AMOUNT, value)
|
||||||
amount += value
|
amount += value
|
||||||
@@ -348,9 +302,7 @@ class Heirs(dict, Logger):
|
|||||||
percent_amount = 0.0
|
percent_amount = 0.0
|
||||||
for key in self.keys():
|
for key in self.keys():
|
||||||
try:
|
try:
|
||||||
cmp = (
|
cmp= Util.parse_locktime_string(self[key][HEIR_LOCKTIME]) - from_locktime
|
||||||
Util.parse_locktime_string(self[key][HEIR_LOCKTIME]) - from_locktime
|
|
||||||
)
|
|
||||||
if cmp<=0:
|
if cmp<=0:
|
||||||
continue
|
continue
|
||||||
if Util.is_perc(self[key][HEIR_AMOUNT]):
|
if Util.is_perc(self[key][HEIR_AMOUNT]):
|
||||||
@@ -368,44 +320,35 @@ class Heirs(dict, Logger):
|
|||||||
_logger.error(e)
|
_logger.error(e)
|
||||||
return fixed_heirs,fixed_amount,percent_heirs,percent_amount
|
return fixed_heirs,fixed_amount,percent_heirs,percent_amount
|
||||||
|
|
||||||
def prepare_lists(
|
|
||||||
self, balance, total_fees, wallet, willexecutor=False, from_locktime=0
|
def prepare_lists(self, balance, total_fees, wallet, willexecutor = False, from_locktime = 0):
|
||||||
):
|
|
||||||
willexecutors_amount = 0
|
willexecutors_amount = 0
|
||||||
willexecutors = {}
|
willexecutors = {}
|
||||||
heir_list = {}
|
heir_list = {}
|
||||||
onlyfixed = False
|
onlyfixed = False
|
||||||
newbalance = balance - total_fees
|
newbalance = balance - total_fees
|
||||||
locktimes = self.get_locktimes(from_locktime)
|
locktimes = self.get_locktimes(from_locktime);
|
||||||
if willexecutor:
|
if willexecutor:
|
||||||
for locktime in locktimes:
|
for locktime in locktimes:
|
||||||
if int(Util.int_locktime(locktime)) > int(from_locktime):
|
if int(Util.int_locktime(locktime)) > int(from_locktime):
|
||||||
try:
|
try:
|
||||||
base_fee = int(willexecutor["base_fee"])
|
base_fee = int(willexecutor['base_fee'])
|
||||||
willexecutors_amount += base_fee
|
willexecutors_amount += base_fee
|
||||||
h = [None] * 4
|
h = [None] * 4
|
||||||
h[HEIR_AMOUNT] = base_fee
|
h[HEIR_AMOUNT] = base_fee
|
||||||
h[HEIR_REAL_AMOUNT] = base_fee
|
h[HEIR_REAL_AMOUNT] = base_fee
|
||||||
h[HEIR_LOCKTIME] = locktime
|
h[HEIR_LOCKTIME] = locktime
|
||||||
h[HEIR_ADDRESS] = willexecutor["address"]
|
h[HEIR_ADDRESS] = willexecutor['address']
|
||||||
willexecutors[
|
willexecutors["w!ll3x3c\""+willexecutor['url']+"\""+str(locktime)] = h
|
||||||
'w!ll3x3c"' + willexecutor["url"] + '"' + str(locktime)
|
|
||||||
] = h
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return [],False
|
return [],False
|
||||||
else:
|
else:
|
||||||
_logger.error(
|
_logger.error(f"heir excluded from will locktime({locktime}){Util.int_locktime(locktime)}<minimum{from_locktime}"),
|
||||||
f"heir excluded from will locktime({locktime}){Util.int_locktime(locktime)}<minimum{from_locktime}"
|
|
||||||
),
|
|
||||||
heir_list.update(willexecutors)
|
heir_list.update(willexecutors)
|
||||||
newbalance -= willexecutors_amount
|
newbalance -= willexecutors_amount
|
||||||
fixed_heirs, fixed_amount, percent_heirs, percent_amount = (
|
fixed_heirs,fixed_amount,percent_heirs,percent_amount = self.fixed_percent_lists_amount(from_locktime,wallet.dust_threshold())
|
||||||
self.fixed_percent_lists_amount(from_locktime, wallet.dust_threshold())
|
|
||||||
)
|
|
||||||
if fixed_amount > newbalance:
|
if fixed_amount > newbalance:
|
||||||
fixed_amount = self.normalize_perc(
|
fixed_amount = self.normalize_perc(fixed_heirs,newbalance,fixed_amount,wallet)
|
||||||
fixed_heirs, newbalance, fixed_amount, wallet
|
|
||||||
)
|
|
||||||
onlyfixed = True
|
onlyfixed = True
|
||||||
|
|
||||||
heir_list.update(fixed_heirs)
|
heir_list.update(fixed_heirs)
|
||||||
@@ -413,40 +356,29 @@ class Heirs(dict, Logger):
|
|||||||
newbalance -= fixed_amount
|
newbalance -= fixed_amount
|
||||||
|
|
||||||
if newbalance > 0:
|
if newbalance > 0:
|
||||||
perc_amount = self.normalize_perc(
|
perc_amount = self.normalize_perc(percent_heirs,newbalance,percent_amount,wallet)
|
||||||
percent_heirs, newbalance, percent_amount, wallet
|
|
||||||
)
|
|
||||||
newbalance -= perc_amount
|
newbalance -= perc_amount
|
||||||
heir_list.update(percent_heirs)
|
heir_list.update(percent_heirs)
|
||||||
|
|
||||||
if newbalance > 0:
|
if newbalance > 0:
|
||||||
newbalance += fixed_amount
|
newbalance += fixed_amount
|
||||||
fixed_amount = self.normalize_perc(
|
fixed_amount = self.normalize_perc(fixed_heirs,newbalance,fixed_amount,wallet,real=True)
|
||||||
fixed_heirs, newbalance, fixed_amount, wallet, real=True
|
|
||||||
)
|
|
||||||
newbalance -= fixed_amount
|
newbalance -= fixed_amount
|
||||||
heir_list.update(fixed_heirs)
|
heir_list.update(fixed_heirs)
|
||||||
|
|
||||||
heir_list = sorted(
|
heir_list = sorted(heir_list.items(), key = lambda item: Util.parse_locktime_string(item[1][HEIR_LOCKTIME],wallet))
|
||||||
heir_list.items(),
|
|
||||||
key=lambda item: Util.parse_locktime_string(item[1][HEIR_LOCKTIME], wallet),
|
|
||||||
)
|
|
||||||
|
|
||||||
locktimes = {}
|
locktimes = {}
|
||||||
for key, value in heir_list:
|
for key, value in heir_list:
|
||||||
locktime=Util.parse_locktime_string(value[HEIR_LOCKTIME])
|
locktime=Util.parse_locktime_string(value[HEIR_LOCKTIME])
|
||||||
if not locktime in locktimes:
|
if not locktime in locktimes: locktimes[locktime]={key:value}
|
||||||
locktimes[locktime] = {key: value}
|
else: locktimes[locktime][key]=value
|
||||||
else:
|
|
||||||
locktimes[locktime][key] = value
|
|
||||||
return locktimes, onlyfixed
|
return locktimes, onlyfixed
|
||||||
|
|
||||||
def is_perc(self,key):
|
def is_perc(self,key):
|
||||||
return Util.is_perc(self[key][HEIR_AMOUNT])
|
return Util.is_perc(self[key][HEIR_AMOUNT])
|
||||||
|
|
||||||
def buildTransactions(
|
def buildTransactions(self,bal_plugin,wallet,tx_fees = None, utxos=None,from_locktime=0):
|
||||||
self, bal_plugin, wallet, tx_fees=None, utxos=None, from_locktime=0
|
|
||||||
):
|
|
||||||
Heirs._validate(self)
|
Heirs._validate(self)
|
||||||
if len(self)<=0:
|
if len(self)<=0:
|
||||||
return
|
return
|
||||||
@@ -456,15 +388,14 @@ class Heirs(dict, Logger):
|
|||||||
if not utxos:
|
if not utxos:
|
||||||
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.get_decimal_point()
|
self.decimal_point=bal_plugin.config.get_decimal_point()
|
||||||
no_willexecutors = bal_plugin.NO_WILLEXECUTOR.get()
|
no_willexecutors = bal_plugin.NO_WILLEXECUTOR.get()
|
||||||
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()
|
||||||
len_utxo_set += 1
|
len_utxo_set += 1
|
||||||
available_utxos.append(utxo)
|
available_utxos.append(utxo)
|
||||||
if len_utxo_set == 0:
|
if len_utxo_set==0: return
|
||||||
return
|
|
||||||
j=-2
|
j=-2
|
||||||
willexecutorsitems = list(willexecutors.items())
|
willexecutorsitems = list(willexecutors.items())
|
||||||
willexecutorslen = len(willexecutorsitems)
|
willexecutorslen = len(willexecutorsitems)
|
||||||
@@ -478,7 +409,7 @@ class Heirs(dict, Logger):
|
|||||||
if not Willexecutors.is_selected(willexecutor):
|
if not Willexecutors.is_selected(willexecutor):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
willexecutor["url"] = url
|
willexecutor['url']=url
|
||||||
elif j == -1:
|
elif j == -1:
|
||||||
if not no_willexecutors:
|
if not no_willexecutors:
|
||||||
continue
|
continue
|
||||||
@@ -495,13 +426,9 @@ class Heirs(dict, Logger):
|
|||||||
for fee in fees:
|
for fee in fees:
|
||||||
total_fees += int(fees[fee])
|
total_fees += int(fees[fee])
|
||||||
newbalance = balance
|
newbalance = balance
|
||||||
locktimes, onlyfixed = self.prepare_lists(
|
locktimes, onlyfixed = self.prepare_lists(balance, total_fees, wallet, willexecutor, from_locktime)
|
||||||
balance, total_fees, wallet, willexecutor, from_locktime
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
txs = prepare_transactions(
|
txs = prepare_transactions(locktimes, available_utxos[:], fees, wallet)
|
||||||
locktimes, available_utxos[:], fees, wallet
|
|
||||||
)
|
|
||||||
if not txs:
|
if not txs:
|
||||||
return {}
|
return {}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -527,6 +454,7 @@ class Heirs(dict, Logger):
|
|||||||
oldfees= fees.get(tx.my_locktime,0)
|
oldfees= fees.get(tx.my_locktime,0)
|
||||||
fees[tx.my_locktime]=fee
|
fees[tx.my_locktime]=fee
|
||||||
|
|
||||||
|
|
||||||
if balance - total_in > wallet.dust_threshold():
|
if balance - total_in > wallet.dust_threshold():
|
||||||
redo = True
|
redo = True
|
||||||
if not redo:
|
if not redo:
|
||||||
@@ -536,34 +464,31 @@ class Heirs(dict, Logger):
|
|||||||
alltxs.update(txs)
|
alltxs.update(txs)
|
||||||
|
|
||||||
return alltxs
|
return alltxs
|
||||||
|
def get_transactions(self,bal_plugin,wallet,tx_fees,utxos=None,from_locktime=0):
|
||||||
def get_transactions(
|
|
||||||
self, bal_plugin, wallet, tx_fees, utxos=None, from_locktime=0
|
|
||||||
):
|
|
||||||
txs=self.buildTransactions(bal_plugin,wallet,tx_fees,utxos,from_locktime)
|
txs=self.buildTransactions(bal_plugin,wallet,tx_fees,utxos,from_locktime)
|
||||||
if txs:
|
if txs:
|
||||||
temp_txs = {}
|
temp_txs = {}
|
||||||
for txid in txs:
|
for txid in txs:
|
||||||
if txs[txid].available_utxos:
|
if txs[txid].available_utxos:
|
||||||
temp_txs.update(
|
temp_txs.update(self.get_transactions(bal_plugin,wallet,tx_fees,txs[txid].available_utxos,txs[txid].locktime))
|
||||||
self.get_transactions(
|
|
||||||
bal_plugin,
|
|
||||||
wallet,
|
|
||||||
tx_fees,
|
|
||||||
txs[txid].available_utxos,
|
|
||||||
txs[txid].locktime,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
txs.update(temp_txs)
|
txs.update(temp_txs)
|
||||||
return txs
|
return txs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def resolve(self, k):
|
def resolve(self, k):
|
||||||
if bitcoin.is_address(k):
|
if bitcoin.is_address(k):
|
||||||
return {"address": k, "type": "address"}
|
return {
|
||||||
|
'address': k,
|
||||||
|
'type': 'address'
|
||||||
|
}
|
||||||
if k in self.keys():
|
if k in self.keys():
|
||||||
_type, addr = self[k]
|
_type, addr = self[k]
|
||||||
if _type == "address":
|
if _type == 'address':
|
||||||
return {"address": addr, "type": "heir"}
|
return {
|
||||||
|
'address': addr,
|
||||||
|
'type': 'heir'
|
||||||
|
}
|
||||||
if openalias := self.resolve_openalias(k):
|
if openalias := self.resolve_openalias(k):
|
||||||
return openalias
|
return openalias
|
||||||
raise AliasNotFoundException("Invalid Bitcoin address or alias", k)
|
raise AliasNotFoundException("Invalid Bitcoin address or alias", k)
|
||||||
@@ -574,10 +499,10 @@ class Heirs(dict, Logger):
|
|||||||
if out:
|
if out:
|
||||||
address, name, validated = out
|
address, name, validated = out
|
||||||
return {
|
return {
|
||||||
"address": address,
|
'address': address,
|
||||||
"name": name,
|
'name': name,
|
||||||
"type": "openalias",
|
'type': 'openalias',
|
||||||
"validated": validated,
|
'validated': validated
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -585,19 +510,21 @@ class Heirs(dict, Logger):
|
|||||||
for k in self.keys():
|
for k in self.keys():
|
||||||
_type, addr = self[k]
|
_type, addr = self[k]
|
||||||
if addr.casefold() == name.casefold():
|
if addr.casefold() == name.casefold():
|
||||||
return {"name": addr, "type": _type, "address": k}
|
return {
|
||||||
|
'name': addr,
|
||||||
|
'type': _type,
|
||||||
|
'address': k
|
||||||
|
}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def fetch_openalias(self, config: "SimpleConfig"):
|
def fetch_openalias(self, config: 'SimpleConfig'):
|
||||||
self.alias_info = None
|
self.alias_info = None
|
||||||
alias = config.OPENALIAS_ID
|
alias = config.OPENALIAS_ID
|
||||||
if alias:
|
if alias:
|
||||||
alias = str(alias)
|
alias = str(alias)
|
||||||
|
|
||||||
def f():
|
def f():
|
||||||
self.alias_info = self._resolve_openalias(alias)
|
self.alias_info = self._resolve_openalias(alias)
|
||||||
trigger_callback("alias_received")
|
trigger_callback('alias_received')
|
||||||
|
|
||||||
t = threading.Thread(target=f)
|
t = threading.Thread(target=f)
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
@@ -605,18 +532,18 @@ class Heirs(dict, Logger):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _resolve_openalias(cls, url: str) -> Optional[Tuple[str, str, bool]]:
|
def _resolve_openalias(cls, url: str) -> Optional[Tuple[str, str, bool]]:
|
||||||
# support email-style addresses, per the OA standard
|
# support email-style addresses, per the OA standard
|
||||||
url = url.replace("@", ".")
|
url = url.replace('@', '.')
|
||||||
try:
|
try:
|
||||||
records, validated = dnssec.query(url, dns.rdatatype.TXT)
|
records, validated = dnssec.query(url, dns.rdatatype.TXT)
|
||||||
except DNSException as e:
|
except DNSException as e:
|
||||||
_logger.info(f"Error resolving openalias: {repr(e)}")
|
_logger.info(f'Error resolving openalias: {repr(e)}')
|
||||||
return None
|
return None
|
||||||
prefix = "btc"
|
prefix = 'btc'
|
||||||
for record in records:
|
for record in records:
|
||||||
string = to_string(record.strings[0], "utf8")
|
string = to_string(record.strings[0], 'utf8')
|
||||||
if string.startswith("oa1:" + prefix):
|
if string.startswith('oa1:' + prefix):
|
||||||
address = cls.find_regex(string, r"recipient_address=([A-Za-z0-9]+)")
|
address = cls.find_regex(string, r'recipient_address=([A-Za-z0-9]+)')
|
||||||
name = cls.find_regex(string, r"recipient_name=([^;]+)")
|
name = cls.find_regex(string, r'recipient_name=([^;]+)')
|
||||||
if not name:
|
if not name:
|
||||||
name = address
|
name = address
|
||||||
if not address:
|
if not address:
|
||||||
@@ -631,6 +558,7 @@ class Heirs(dict, Logger):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def validate_address(address):
|
def validate_address(address):
|
||||||
if not bitcoin.is_address(address):
|
if not bitcoin.is_address(address):
|
||||||
raise NotAnAddress(f"not an address,{address}")
|
raise NotAnAddress(f"not an address,{address}")
|
||||||
@@ -662,7 +590,7 @@ class Heirs(dict, Logger):
|
|||||||
|
|
||||||
def _validate(data,timestamp_to_check=False):
|
def _validate(data,timestamp_to_check=False):
|
||||||
for k, v in list(data.items()):
|
for k, v in list(data.items()):
|
||||||
if k == "heirs":
|
if k == 'heirs':
|
||||||
return Heirs._validate(v)
|
return Heirs._validate(v)
|
||||||
try:
|
try:
|
||||||
Heirs.validate_heir(k,v)
|
Heirs.validate_heir(k,v)
|
||||||
@@ -670,18 +598,11 @@ class Heirs(dict, Logger):
|
|||||||
data.pop(k)
|
data.pop(k)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class NotAnAddress(ValueError):
|
class NotAnAddress(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AmountNotValid(ValueError):
|
class AmountNotValid(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LocktimeNotValid(ValueError):
|
class LocktimeNotValid(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HeirExpiredException(LocktimeNotValid):
|
class HeirExpiredException(LocktimeNotValid):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "BAL",
|
"name": "BAL",
|
||||||
"fullname": "Bitcoin After Life",
|
"fullname": "Bitcoin After Life",
|
||||||
"description": "Provides free and decentralized inheritance support<br> Version: 0.2.3",
|
"description": "Provides free and decentralized inheritance support<br> Version: 0.2.2a",
|
||||||
"author":"Svatantrya",
|
"author":"Svatantrya",
|
||||||
"available_for": ["qt"],
|
"available_for": ["qt"],
|
||||||
"icon":"icons/bal32x32.png"
|
"icon":"icons/bal32x32.png"
|
||||||
|
|||||||
167
util.py
167
util.py
@@ -1,16 +1,13 @@
|
|||||||
import bisect
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
from datetime import datetime,timedelta
|
from datetime import datetime,timedelta
|
||||||
|
import bisect
|
||||||
from electrum.gui.qt.util import getSaveFileName
|
from electrum.gui.qt.util import getSaveFileName
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.transaction import PartialTxOutput
|
from electrum.transaction import PartialTxOutput
|
||||||
from electrum.util import FileExportFailed, FileImportFailed, write_json_file
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
from electrum.util import write_json_file,FileImportFailed,FileExportFailed
|
||||||
|
|
||||||
LOCKTIME_THRESHOLD = 500000000
|
LOCKTIME_THRESHOLD = 500000000
|
||||||
|
|
||||||
|
|
||||||
class Util:
|
class Util:
|
||||||
def locktime_to_str(locktime):
|
def locktime_to_str(locktime):
|
||||||
try:
|
try:
|
||||||
@@ -25,10 +22,9 @@ class Util:
|
|||||||
|
|
||||||
def str_to_locktime(locktime):
|
def str_to_locktime(locktime):
|
||||||
try:
|
try:
|
||||||
if locktime[-1] in ("y", "d", "b"):
|
if locktime[-1] in ('y','d','b'):
|
||||||
return locktime
|
return locktime
|
||||||
else:
|
else: return int(locktime)
|
||||||
return int(locktime)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
dt_object = datetime.fromisoformat(locktime)
|
dt_object = datetime.fromisoformat(locktime)
|
||||||
@@ -43,15 +39,11 @@ class Util:
|
|||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
if locktime[-1] == "y":
|
if locktime[-1] == 'y':
|
||||||
locktime = str(int(locktime[:-1])*365) + "d"
|
locktime = str(int(locktime[:-1])*365) + "d"
|
||||||
if locktime[-1] == "d":
|
if locktime[-1] == 'd':
|
||||||
return int(
|
return int((now + timedelta(days = int(locktime[:-1]))).replace(hour=0,minute=0,second=0,microsecond=0).timestamp())
|
||||||
(now + timedelta(days=int(locktime[:-1])))
|
if locktime[-1] == 'b':
|
||||||
.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
||||||
.timestamp()
|
|
||||||
)
|
|
||||||
if locktime[-1] == "b":
|
|
||||||
locktime = int(locktime[:-1])
|
locktime = int(locktime[:-1])
|
||||||
height = 0
|
height = 0
|
||||||
if w:
|
if w:
|
||||||
@@ -62,14 +54,9 @@ class Util:
|
|||||||
pass
|
pass
|
||||||
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(
|
return int(seconds + minutes*60 + hours*60*60 + days*60*60*24 + blocks * 600)
|
||||||
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 Util.is_perc(amount):
|
||||||
@@ -90,7 +77,7 @@ class Util:
|
|||||||
|
|
||||||
def is_perc(value):
|
def is_perc(value):
|
||||||
try:
|
try:
|
||||||
return value[-1] == "%"
|
return value[-1] == '%'
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -114,11 +101,7 @@ class Util:
|
|||||||
if willexecutora == willexecutorb:
|
if willexecutora == willexecutorb:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
if (
|
if willexecutora['url']==willexecutorb['url'] and willexecutora['address'] == willexecutorb['address'] and willexecutora['base_fee']==willexecutorb['base_fee']:
|
||||||
willexecutora["url"] == willexecutorb["url"]
|
|
||||||
and willexecutora["address"] == willexecutorb["address"]
|
|
||||||
and willexecutora["base_fee"] == willexecutorb["base_fee"]
|
|
||||||
):
|
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
@@ -141,13 +124,9 @@ class Util:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def cmp_heirs_by_values(
|
def cmp_heirs_by_values(heirsa,heirsb,values,exclude_willexecutors=False,reverse = True):
|
||||||
heirsa, heirsb, values, exclude_willexecutors=False, reverse=True
|
|
||||||
):
|
|
||||||
for heira in heirsa:
|
for heira in heirsa:
|
||||||
if (
|
if (exclude_willexecutors and not "w!ll3x3c\"" in heira) or not exclude_willexecutors:
|
||||||
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 Util.cmp_heir_by_values(heirsa[heira],heirsb[heirb],values):
|
||||||
@@ -155,28 +134,15 @@ class Util:
|
|||||||
if not found:
|
if not found:
|
||||||
return False
|
return False
|
||||||
if reverse:
|
if reverse:
|
||||||
return Util.cmp_heirs_by_values(
|
return Util.cmp_heirs_by_values(heirsb,heirsa,values,exclude_willexecutors=exclude_willexecutors,reverse=False)
|
||||||
heirsb,
|
|
||||||
heirsa,
|
|
||||||
values,
|
|
||||||
exclude_willexecutors=exclude_willexecutors,
|
|
||||||
reverse=False,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def cmp_heirs(
|
def cmp_heirs(heirsa,heirsb,cmp_function = lambda x,y: x[0]==y[0] and x[3]==y[3],reverse=True):
|
||||||
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(
|
if not heir in heirsb or not cmp_function(heirsa[heir],heirsb[heir]):
|
||||||
heirsa[heir], heirsb[heir]
|
|
||||||
):
|
|
||||||
if not Util.search_heir_by_values(heirsb,heirsa[heir],[0,3]):
|
if not Util.search_heir_by_values(heirsb,heirsa[heir],[0,3]):
|
||||||
return False
|
return False
|
||||||
if reverse:
|
if reverse:
|
||||||
@@ -229,6 +195,8 @@ class Util:
|
|||||||
|
|
||||||
return value_amount
|
return value_amount
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -268,15 +236,16 @@ class Util:
|
|||||||
else:
|
else:
|
||||||
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):
|
||||||
@@ -296,7 +265,7 @@ class Util:
|
|||||||
|
|
||||||
def search_willtx_per_io(will,tx):
|
def search_willtx_per_io(will,tx):
|
||||||
for wid, w in will.items():
|
for wid, w in will.items():
|
||||||
if Util.cmp_txs(w["tx"], tx["tx"]):
|
if Util.cmp_txs(w['tx'],tx['tx']):
|
||||||
return wid,w
|
return wid,w
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@@ -306,19 +275,15 @@ class Util:
|
|||||||
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:
|
try: return utxo.to_str()
|
||||||
return utxo.to_str()
|
except Exception as e: pass
|
||||||
except Exception as e:
|
try: return utxo.prevout.to_str()
|
||||||
pass
|
except Exception as e: pass
|
||||||
try:
|
|
||||||
return utxo.prevout.to_str()
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
return str(utxo)
|
return str(utxo)
|
||||||
|
|
||||||
def cmp_utxo(utxoa,utxob):
|
def cmp_utxo(utxoa,utxob):
|
||||||
@@ -355,6 +320,7 @@ class Util:
|
|||||||
#return true false same amount different address
|
#return true false same amount different address
|
||||||
#return false false different amount, different address not found
|
#return false false different amount, different address not found
|
||||||
|
|
||||||
|
|
||||||
def din_output(out,outputs):
|
def din_output(out,outputs):
|
||||||
same_amount=[]
|
same_amount=[]
|
||||||
for s_o in outputs:
|
for s_o in outputs:
|
||||||
@@ -367,20 +333,19 @@ class Util:
|
|||||||
|
|
||||||
if len(same_amount)>0:
|
if len(same_amount)>0:
|
||||||
return True, False
|
return True, False
|
||||||
else:
|
else:return False, False
|
||||||
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()
|
||||||
out = PartialTxOutput.from_address_and_value(
|
out = PartialTxOutput.from_address_and_value(change_addresses[0], change_amount)
|
||||||
change_addresses[0], change_amount
|
|
||||||
)
|
|
||||||
out.is_change = True
|
out.is_change = True
|
||||||
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
|
||||||
@@ -389,9 +354,7 @@ class Util:
|
|||||||
return 0
|
return 0
|
||||||
# figure out current block height
|
# figure out current block height
|
||||||
chain_height = chain.height() # learnt from all connected servers, SPV-checked
|
chain_height = chain.height() # learnt from all connected servers, SPV-checked
|
||||||
server_height = (
|
server_height = network.get_server_height() # height claimed by main server, unverified
|
||||||
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)
|
# 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 it's lagging too much, it is the network's job to switch away
|
||||||
if server_height < chain_height - 10:
|
if server_height < chain_height - 10:
|
||||||
@@ -401,41 +364,34 @@ class Util:
|
|||||||
height = min(chain_height, server_height)
|
height = min(chain_height, server_height)
|
||||||
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:
|
||||||
print("doc:",doc(var))
|
print("doc:",doc(var))
|
||||||
except:
|
except: pass
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
print("str:",str(var))
|
print("str:",str(var))
|
||||||
except:
|
except: pass
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
print("repr",repr(var))
|
print("repr",repr(var))
|
||||||
except:
|
except:pass
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
print("dict",dict(var))
|
print("dict",dict(var))
|
||||||
except:
|
except:pass
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
print("dir",dir(var))
|
print("dir",dir(var))
|
||||||
except:
|
except:pass
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
print("type",type(var))
|
print("type",type(var))
|
||||||
except:
|
except:pass
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
print("to_json",var.to_json())
|
print("to_json",var.to_json())
|
||||||
except:
|
except: pass
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
print("__slotnames__",var.__slotnames__)
|
print("__slotnames__",var.__slotnames__)
|
||||||
except:
|
except:pass
|
||||||
pass
|
|
||||||
|
|
||||||
print(f"---end {name}---")
|
print(f"---end {name}---")
|
||||||
|
|
||||||
@@ -456,12 +412,12 @@ class Util:
|
|||||||
Util.print_var(prevout._asdict())
|
Util.print_var(prevout._asdict())
|
||||||
print(f"---prevout-end {name}---")
|
print(f"---prevout-end {name}---")
|
||||||
|
|
||||||
def export_meta_gui(electrum_window: "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,
|
||||||
title=_("Select file to save your {}").format(title),
|
title=_("Select file to save your {}").format(title),
|
||||||
filename="BALplugin_{}".format(title),
|
filename='BALplugin_{}'.format(title),
|
||||||
filter=filter_,
|
filter=filter_,
|
||||||
config=electrum_window.config,
|
config=electrum_window.config,
|
||||||
)
|
)
|
||||||
@@ -472,29 +428,10 @@ class Util:
|
|||||||
except FileExportFailed as e:
|
except FileExportFailed as e:
|
||||||
electrum_window.show_critical(str(e))
|
electrum_window.show_critical(str(e))
|
||||||
else:
|
else:
|
||||||
electrum_window.show_message(
|
electrum_window.show_message(_("Your {0} were exported to '{1}'")
|
||||||
_("Your {0} were exported to '{1}'").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
|
||||||
|
|
||||||
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
|
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
#!env/bin/python3
|
|
||||||
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)
|
|
||||||
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 password == False:
|
|
||||||
password = getpass.getpass("Enter wallet password: ", stream=None)
|
|
||||||
storage.decrypt(password)
|
|
||||||
data = storage.read()
|
|
||||||
json_wallet = json.loads("[" + data + "]")[0]
|
|
||||||
return json_wallet
|
|
||||||
|
|
||||||
|
|
||||||
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 = 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")
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
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 electrum.util import MyEncoder
|
|
||||||
from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, read_wallet, save
|
|
||||||
|
|
||||||
|
|
||||||
class WalletUtilityGUI(QMainWindow):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.initUI()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
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:"))
|
|
||||||
self.password_edit = QLineEdit()
|
|
||||||
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 (*)"
|
|
||||||
)
|
|
||||||
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_wallet(self):
|
|
||||||
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")
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
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]
|
|
||||||
|
|
||||||
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 have_to_save:
|
|
||||||
try:
|
|
||||||
save(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(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__":
|
|
||||||
sys.exit(main())
|
|
||||||
379
will.py
379
will.py
@@ -1,32 +1,20 @@
|
|||||||
import copy
|
import copy
|
||||||
|
|
||||||
from electrum.bitcoin import NLOCKTIME_BLOCKHEIGHT_MAX
|
|
||||||
from electrum.i18n import _
|
|
||||||
from electrum.logging import Logger, get_logger
|
|
||||||
from electrum.transaction import (
|
|
||||||
PartialTransaction,
|
|
||||||
PartialTxInput,
|
|
||||||
PartialTxOutput,
|
|
||||||
Transaction,
|
|
||||||
TxOutpoint,
|
|
||||||
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 .willexecutors import Willexecutors
|
from .willexecutors import Willexecutors
|
||||||
|
from .util import Util
|
||||||
|
|
||||||
|
from electrum.i18n import _
|
||||||
|
|
||||||
|
from electrum.transaction import TxOutpoint,PartialTxInput,tx_from_any,PartialTransaction,PartialTxOutput,Transaction
|
||||||
|
from electrum.util import bfh, decimal_point_to_base_unit_name
|
||||||
|
from electrum.util import write_json_file,read_json_file,FileImportFailed
|
||||||
|
from electrum.logging import get_logger,Logger
|
||||||
|
from electrum.bitcoin import NLOCKTIME_BLOCKHEIGHT_MAX
|
||||||
|
|
||||||
MIN_LOCKTIME = 1
|
MIN_LOCKTIME = 1
|
||||||
MIN_BLOCK = 1
|
MIN_BLOCK = 1
|
||||||
_logger = get_logger(__name__)
|
_logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Will:
|
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):
|
||||||
@@ -47,21 +35,23 @@ class Will:
|
|||||||
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():
|
||||||
if Util.cmp_txs(will[w]["tx"], tx):
|
if Util.cmp_txs(will[w]['tx'],tx):
|
||||||
return will[w]["tx"]
|
return will[w]['tx']
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_tx_from_any(x):
|
def get_tx_from_any(x):
|
||||||
@@ -117,7 +107,7 @@ class Will:
|
|||||||
_logger.error(will[wid].tx.to_json())
|
_logger.error(will[wid].tx.to_json())
|
||||||
|
|
||||||
_logger.error("txid is none")
|
_logger.error("txid is none")
|
||||||
will[wid].set_status("ERROR", True)
|
will[wid].set_status('ERROR',True)
|
||||||
errors[wid]=will[wid]
|
errors[wid]=will[wid]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -128,9 +118,7 @@ class Will:
|
|||||||
will[wid]=WillItem(ow.to_dict())
|
will[wid]=WillItem(ow.to_dict())
|
||||||
|
|
||||||
for i in range(0,len(outputs)):
|
for i in range(0,len(outputs)):
|
||||||
Will.change_input(
|
Will.change_input(will,wid,i,outputs[i],others_inputs,to_delete,to_add)
|
||||||
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()
|
||||||
@@ -155,15 +143,13 @@ 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(
|
if Util.cmp_heirs_by_values(ow.heirs,nw.heirs,[0,1],exclude_willexecutors = True):
|
||||||
ow.heirs, nw.heirs, [0, 1], exclude_willexecutors=True
|
|
||||||
):
|
|
||||||
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']:
|
||||||
if int(ow.we["base_fee"]) > int(nw.we["base_fee"]):
|
if int(ow.we['base_fee'])>int(nw.we['base_fee']):
|
||||||
return anticipate
|
return anticipate
|
||||||
else:
|
else:
|
||||||
if int(ow.tx_fees) != int(nw.tx_fees):
|
if int(ow.tx_fees) != int(nw.tx_fees):
|
||||||
@@ -184,6 +170,7 @@ class Will:
|
|||||||
return anticipate
|
return anticipate
|
||||||
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()
|
||||||
@@ -196,10 +183,7 @@ class Will:
|
|||||||
old_txid = w.tx.txid()
|
old_txid = w.tx.txid()
|
||||||
ntx = None
|
ntx = None
|
||||||
for i in range(0,len(inputs)):
|
for i in range(0,len(inputs)):
|
||||||
if (
|
if inputs[i].prevout.txid.hex() == otxid and inputs[i].prevout.out_idx == idx:
|
||||||
inputs[i].prevout.txid.hex() == otxid
|
|
||||||
and inputs[i].prevout.out_idx == idx
|
|
||||||
):
|
|
||||||
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)
|
||||||
@@ -214,20 +198,12 @@ 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.change_input(will, wid, i, outputs[i],others_inputs,to_delete,to_append)
|
||||||
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'):
|
||||||
inputs = wi.tx.inputs()
|
inputs = wi.tx.inputs()
|
||||||
for i in inputs:
|
for i in inputs:
|
||||||
prevout_str = i.prevout.to_str()
|
prevout_str = i.prevout.to_str()
|
||||||
@@ -252,6 +228,7 @@ 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 = []
|
||||||
@@ -265,9 +242,8 @@ 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.change_input(will,nid,i,outputs[i],new_inputs,to_delete,to_append)
|
||||||
will, nid, i, outputs[i], new_inputs, to_delete, to_append
|
|
||||||
)
|
|
||||||
|
|
||||||
for w in to_delete:
|
for w in to_delete:
|
||||||
try:
|
try:
|
||||||
@@ -279,6 +255,7 @@ class Will:
|
|||||||
if redo:
|
if redo:
|
||||||
Will.search_anticipate_rec(will,old_inputs)
|
Will.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 = Will.get_all_inputs(old_will,only_valid=True)
|
||||||
all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
|
all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
|
||||||
@@ -293,6 +270,7 @@ class Will:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
for oid in Will.only_valid(old_will):
|
for oid in Will.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
|
||||||
@@ -338,23 +316,18 @@ class Will:
|
|||||||
if utxo_str in prevout_to_spend:
|
if utxo_str in prevout_to_spend:
|
||||||
balance += inputs[utxo_str][0][2].value_sats()
|
balance += inputs[utxo_str][0][2].value_sats()
|
||||||
utxo_to_spend.append(utxo)
|
utxo_to_spend.append(utxo)
|
||||||
|
|
||||||
if len(utxo_to_spend) > 0:
|
if len(utxo_to_spend) > 0:
|
||||||
change_addresses = wallet.get_change_addresses_for_new_transaction()
|
change_addresses = wallet.get_change_addresses_for_new_transaction()
|
||||||
out = PartialTxOutput.from_address_and_value(change_addresses[0], balance)
|
out = PartialTxOutput.from_address_and_value(change_addresses[0], balance)
|
||||||
out.is_change = True
|
out.is_change = True
|
||||||
locktime = Util.get_current_height(wallet.network)
|
locktime = Util.get_current_height(wallet.network)
|
||||||
tx = PartialTransaction.from_io(
|
tx = PartialTransaction.from_io(utxo_to_spend, [out], locktime=locktime, version=2)
|
||||||
utxo_to_spend, [out], locktime=locktime, version=2
|
|
||||||
)
|
|
||||||
tx.set_rbf(True)
|
tx.set_rbf(True)
|
||||||
fee=tx.estimated_size()*fees_per_byte
|
fee=tx.estimated_size()*fees_per_byte
|
||||||
if balance -fee >0:
|
if balance -fee >0:
|
||||||
out = PartialTxOutput.from_address_and_value(
|
out = PartialTxOutput.from_address_and_value(change_addresses[0],balance - fee)
|
||||||
change_addresses[0], balance - fee
|
tx = PartialTransaction.from_io(utxo_to_spend,[out], locktime=locktime, version=2)
|
||||||
)
|
|
||||||
tx = PartialTransaction.from_io(
|
|
||||||
utxo_to_spend, [out], locktime=locktime, version=2
|
|
||||||
)
|
|
||||||
tx.set_rbf(True)
|
tx.set_rbf(True)
|
||||||
|
|
||||||
_logger.debug(f"invalidation tx: {tx}")
|
_logger.debug(f"invalidation tx: {tx}")
|
||||||
@@ -367,9 +340,10 @@ class Will:
|
|||||||
_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):
|
||||||
@@ -378,36 +352,33 @@ class Will:
|
|||||||
inutxo = Util.in_utxo(inp,all_utxos)
|
inutxo = Util.in_utxo(inp,all_utxos)
|
||||||
for w in ws:
|
for w in ws:
|
||||||
wi=w[1]
|
wi=w[1]
|
||||||
if (
|
if wi.get_status('VALID') or wi.get_status('CONFIRMED') or wi.get_status('PENDING'):
|
||||||
wi.get_status("VALID")
|
|
||||||
or wi.get_status("CONFIRMED")
|
|
||||||
or wi.get_status("PENDING")
|
|
||||||
):
|
|
||||||
prevout_id=w[2].prevout.txid.hex()
|
prevout_id=w[2].prevout.txid.hex()
|
||||||
if not inutxo:
|
if not inutxo:
|
||||||
if prevout_id in will:
|
if prevout_id in will:
|
||||||
wo=will[prevout_id]
|
wo=will[prevout_id]
|
||||||
if wo.get_status("REPLACED"):
|
if wo.get_status('REPLACED'):
|
||||||
wi.set_status("REPLACED", True)
|
wi.set_status('REPLACED',True)
|
||||||
if wo.get_status("INVALIDATED"):
|
if wo.get_status("INVALIDATED"):
|
||||||
wi.set_status("INVALIDATED", True)
|
wi.set_status('INVALIDATED',True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if wallet.db.get_transaction(wi._id):
|
if wallet.db.get_transaction(wi._id):
|
||||||
wi.set_status("CONFIRMED", True)
|
wi.set_status('CONFIRMED',True)
|
||||||
else:
|
else:
|
||||||
wi.set_status("INVALIDATED", True)
|
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:
|
||||||
_logger.debug("a child was found")
|
_logger.debug("a child was found")
|
||||||
wi.set_status("REPLACED", True)
|
wi.set_status('REPLACED',True)
|
||||||
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:
|
||||||
@@ -416,7 +387,7 @@ class 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):
|
||||||
@@ -427,12 +398,13 @@ class Will:
|
|||||||
if not inp_str in utxos_list:
|
if not inp_str in utxos_list:
|
||||||
if wallet:
|
if wallet:
|
||||||
height= Will.check_tx_height(w.tx,wallet)
|
height= Will.check_tx_height(w.tx,wallet)
|
||||||
|
|
||||||
if height < 0:
|
if height < 0:
|
||||||
Will.set_invalidate(wid,willtree)
|
Will.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"):
|
||||||
@@ -448,27 +420,22 @@ class Will:
|
|||||||
Will.reflect_to_children(wc)
|
Will.reflect_to_children(wc)
|
||||||
|
|
||||||
def check_amounts(heirs,willexecutors,all_utxos,timestamp_to_check,dust):
|
def check_amounts(heirs,willexecutors,all_utxos,timestamp_to_check,dust):
|
||||||
fixed_heirs, fixed_amount, perc_heirs, perc_amount = (
|
fixed_heirs,fixed_amount,perc_heirs,perc_amount = heirs.fixed_percent_lists_amount(timestamp_to_check,dust,reverse=True)
|
||||||
heirs.fixed_percent_lists_amount(timestamp_to_check, dust, reverse=True)
|
|
||||||
)
|
|
||||||
wallet_balance = 0
|
wallet_balance = 0
|
||||||
for utxo in all_utxos:
|
for utxo in all_utxos:
|
||||||
wallet_balance += utxo.value_sats()
|
wallet_balance += utxo.value_sats()
|
||||||
|
|
||||||
if fixed_amount >= wallet_balance:
|
if fixed_amount >= wallet_balance:
|
||||||
raise FixedAmountException(
|
raise FixedAmountException(f"Fixed amount({fixed_amount}) >= {wallet_balance}")
|
||||||
f"Fixed amount({fixed_amount}) >= {wallet_balance}"
|
|
||||||
)
|
|
||||||
if perc_amount != 100:
|
if perc_amount != 100:
|
||||||
raise PercAmountException(f"Perc amount({perc_amount}) =! 100%")
|
raise PercAmountException(f"Perc amount({perc_amount}) =! 100%")
|
||||||
|
|
||||||
for url,wex in willexecutors.items():
|
for url,wex in willexecutors.items():
|
||||||
if Willexecutors.is_selected(wex):
|
if Willexecutors.is_selected(wex):
|
||||||
temp_balance = wallet_balance - int(wex["base_fee"])
|
temp_balance = wallet_balance - int(wex['base_fee'])
|
||||||
if fixed_amount >= temp_balance:
|
if fixed_amount >= temp_balance:
|
||||||
raise FixedAmountException(
|
raise FixedAmountException(f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}")
|
||||||
f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check):
|
def check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check):
|
||||||
Will.add_willtree(will)
|
Will.add_willtree(will)
|
||||||
@@ -477,87 +444,68 @@ class Will:
|
|||||||
Will.check_invalidated(will,utxos_list,wallet)
|
Will.check_invalidated(will,utxos_list,wallet)
|
||||||
|
|
||||||
all_inputs=Will.get_all_inputs(will,only_valid = True)
|
all_inputs=Will.get_all_inputs(will,only_valid = True)
|
||||||
|
|
||||||
all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_inputs)
|
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
|
Will.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=Will.get_all_inputs(will,only_valid = True)
|
||||||
|
|
||||||
Will.search_rai(all_inputs,all_utxos,will,wallet)
|
Will.search_rai(all_inputs,all_utxos,will,wallet)
|
||||||
|
|
||||||
def is_will_valid(
|
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,
|
|
||||||
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)
|
Will.check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check)
|
||||||
|
|
||||||
|
|
||||||
if heirs:
|
if heirs:
|
||||||
if not Will.check_willexecutors_and_heirs(
|
if not Will.check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,timestamp_to_check,tx_fees):
|
||||||
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=Will.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:
|
||||||
for utxo in all_utxos:
|
for utxo in all_utxos:
|
||||||
if utxo.value_sats() > 68 * tx_fees:
|
if utxo.value_sats() > 68 * tx_fees:
|
||||||
if not Util.in_utxo(utxo,all_inputs.keys()):
|
if not Util.in_utxo(utxo,all_inputs.keys()):
|
||||||
_logger.info("utxo is not spent",utxo.to_json())
|
_logger.info("utxo is not spent",utxo.to_json())
|
||||||
_logger.debug(all_inputs.keys())
|
_logger.debug(all_inputs.keys())
|
||||||
raise NotCompleteWillException(
|
raise NotCompleteWillException("Some utxo in the wallet is not included")
|
||||||
"Some utxo in the wallet is not included"
|
|
||||||
)
|
|
||||||
|
|
||||||
_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:
|
||||||
if w[1].get_status("VALID"):
|
if w[1].get_status('VALID'):
|
||||||
locktime = int(wid[0][1].tx.locktime)
|
locktime = int(wid[0][1].tx.locktime)
|
||||||
if locktime <= NLOCKTIME_BLOCKHEIGHT_MAX:
|
if locktime <= NLOCKTIME_BLOCKHEIGHT_MAX:
|
||||||
if locktime < int(block_to_check):
|
if locktime < int(block_to_check):
|
||||||
raise WillExpiredException(
|
raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{block_to_check}")
|
||||||
f"Will Expired {wid[0][0]}: {locktime}<{block_to_check}"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if locktime < int(timestamp_to_check):
|
if locktime < int(timestamp_to_check):
|
||||||
raise WillExpiredException(
|
raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}")
|
||||||
f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def check_all_input_spent_are_in_wallet():
|
def check_all_input_spent_are_in_wallet():
|
||||||
_logger.info("check all input spent are in wallet or valid txs")
|
_logger.info("check all input spent are in wallet or valid txs")
|
||||||
for inp,ws in all_inputs.items():
|
for inp,ws in all_inputs.items():
|
||||||
if not Util.in_utxo(inp,all_utxos):
|
if not Util.in_utxo(inp,all_utxos):
|
||||||
for w in ws:
|
for w in ws:
|
||||||
if w[1].get_status("VALID"):
|
if w[1].get_status('VALID'):
|
||||||
prevout_id = w[2].prevout.txid.hex()
|
prevout_id = w[2].prevout.txid.hex()
|
||||||
parentwill = will.get(prevout_id,False)
|
parentwill = will.get(prevout_id,False)
|
||||||
if not parentwill or not parentwill.get_status("VALID"):
|
if not parentwill or not parentwill.get_status('VALID'):
|
||||||
w[1].set_status("INVALIDATED", True)
|
w[1].set_status('INVALIDATED',True)
|
||||||
|
|
||||||
|
|
||||||
def only_valid_list(will):
|
def only_valid_list(will):
|
||||||
out={}
|
out={}
|
||||||
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
|
||||||
|
|
||||||
@@ -565,13 +513,11 @@ class Will:
|
|||||||
out=[]
|
out=[]
|
||||||
for wid,w in will.items():
|
for wid,w in will.items():
|
||||||
wi = w
|
wi = w
|
||||||
if wi.get_status("VALID") or wi.get_status("REPLACED"):
|
if wi.get_status('VALID') or wi.get_status('REPLACED'):
|
||||||
out.append(wid)
|
out.append(wid)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def check_willexecutors_and_heirs(
|
def check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,check_date,tx_fees):
|
||||||
will, heirs, willexecutors, self_willexecutor, check_date, tx_fees
|
|
||||||
):
|
|
||||||
_logger.debug("check willexecutors heirs")
|
_logger.debug("check willexecutors heirs")
|
||||||
no_willexecutor = 0
|
no_willexecutor = 0
|
||||||
willexecutors_found = {}
|
willexecutors_found = {}
|
||||||
@@ -588,25 +534,16 @@ class Will:
|
|||||||
their = will[wid].heirs[wheir]
|
their = will[wid].heirs[wheir]
|
||||||
if heir := heirs.get(wheir,None):
|
if heir := heirs.get(wheir,None):
|
||||||
|
|
||||||
if (
|
if heir[0] == their[0] and heir[1] == their[1] and Util.parse_locktime_string(heir[2]) >= Util.parse_locktime_string(their[2]):
|
||||||
heir[0] == their[0]
|
|
||||||
and heir[1] == their[1]
|
|
||||||
and Util.parse_locktime_string(heir[2])
|
|
||||||
>= Util.parse_locktime_string(their[2])
|
|
||||||
):
|
|
||||||
count = heirs_found.get(wheir,0)
|
count = heirs_found.get(wheir,0)
|
||||||
heirs_found[wheir]=count + 1
|
heirs_found[wheir]=count + 1
|
||||||
else:
|
else:
|
||||||
_logger.debug(
|
_logger.debug("heir not present transaction is not valid:",wid,w)
|
||||||
"heir not present transaction is not valid:", wid, w
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
if willexecutor := w.we:
|
if willexecutor := w.we:
|
||||||
count = willexecutors_found.get(willexecutor["url"], 0)
|
count = willexecutors_found.get(willexecutor['url'],0)
|
||||||
if Util.cmp_willexecutor(
|
if Util.cmp_willexecutor(willexecutor,willexecutors.get(willexecutor['url'],None)):
|
||||||
willexecutor, willexecutors.get(willexecutor["url"], None)
|
willexecutors_found[willexecutor['url']]=count+1
|
||||||
):
|
|
||||||
willexecutors_found[willexecutor["url"]] = count + 1
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
no_willexecutor += 1
|
no_willexecutor += 1
|
||||||
@@ -634,50 +571,46 @@ class Will:
|
|||||||
class WillItem(Logger):
|
class WillItem(Logger):
|
||||||
|
|
||||||
STATUS_DEFAULT = {
|
STATUS_DEFAULT = {
|
||||||
"ANTICIPATED": ["Anticipated", False],
|
'ANTICIPATED': ['Anticipated', False],
|
||||||
"BROADCASTED": ["Broadcasted", False],
|
'BROADCASTED': ['Broadcasted', False],
|
||||||
"CHECKED": ["Checked", False],
|
'CHECKED': ['Checked', False],
|
||||||
"CHECK_FAIL": ["Check Failed", False],
|
'CHECK_FAIL': ['Check Failed',False],
|
||||||
"COMPLETE": ["Signed", False],
|
'COMPLETE': ['Signed', False],
|
||||||
"CONFIRMED": ["Confirmed", False],
|
'CONFIRMED': ['Confirmed', False],
|
||||||
"ERROR": ["Error", False],
|
'ERROR': ['Error', False],
|
||||||
"EXPIRED": ["Expired", False],
|
'EXPIRED': ['Expired', False],
|
||||||
"EXPORTED": ["Exported", False],
|
'EXPORTED': ['Exported', False],
|
||||||
"IMPORTED": ["Imported", False],
|
'IMPORTED': ['Imported', False],
|
||||||
"INVALIDATED": ["Invalidated", False],
|
'INVALIDATED': ['Invalidated', False],
|
||||||
"PENDING": ["Pending", False],
|
'PENDING': ['Pending', False],
|
||||||
"PUSH_FAIL": ["Push failed", False],
|
'PUSH_FAIL': ['Push failed', False],
|
||||||
"PUSHED": ["Pushed", False],
|
'PUSHED': ['Pushed', False],
|
||||||
"REPLACED": ["Replaced", False],
|
'REPLACED': ['Replaced', False],
|
||||||
"RESTORED": ["Restored", False],
|
'RESTORED': ['Restored', False],
|
||||||
"VALID": ["Valid", True],
|
'VALID': ['Valid', True],
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_status(self,status,value=True):
|
def set_status(self,status,value=True):
|
||||||
#_logger.trace(
|
_logger.debug("set status {} - {} {} -> {}".format(self._id,status,self.STATUS[status][1],value))
|
||||||
# "set status {} - {} {} -> {}".format(
|
|
||||||
# self._id, status, self.STATUS[status][1], value
|
|
||||||
# )
|
|
||||||
#)
|
|
||||||
if self.STATUS[status][1] == bool(value):
|
if self.STATUS[status][1] == bool(value):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.status += "." +("NOT " if not value else "" + _(self.STATUS[status][0]))
|
self.status += "." +("NOT " if not value else "" + _(self.STATUS[status][0]))
|
||||||
self.STATUS[status][1] = bool(value)
|
self.STATUS[status][1] = bool(value)
|
||||||
if value:
|
if value:
|
||||||
if status in ["INVALIDATED", "REPLACED", "CONFIRMED", "PENDING"]:
|
if status in ['INVALIDATED','REPLACED','CONFIRMED','PENDING']:
|
||||||
self.STATUS["VALID"][1] = False
|
self.STATUS['VALID'][1] = False
|
||||||
|
|
||||||
if status in ["CONFIRMED", "PENDING"]:
|
if status in ['CONFIRMED','PENDING']:
|
||||||
self.STATUS["INVALIDATED"][1] = False
|
self.STATUS['INVALIDATED'][1] = False
|
||||||
|
|
||||||
if status in ["PUSHED"]:
|
if status in ['PUSHED']:
|
||||||
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 ["CHECKED"]:
|
|
||||||
self.STATUS["PUSHED"][1] = True
|
if status in ['CHECKED']:
|
||||||
self.STATUS["PUSH_FAIL"][1] = False
|
self.STATUS['PUSHED'][1] = True
|
||||||
|
self.STATUS['PUSH_FAIL'][1] = False
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -685,22 +618,19 @@ class WillItem(Logger):
|
|||||||
return self.STATUS[status][1]
|
return self.STATUS[status][1]
|
||||||
|
|
||||||
def __init__(self,w,_id=None,wallet=None):
|
def __init__(self,w,_id=None,wallet=None):
|
||||||
if isinstance(
|
if isinstance(w,WillItem,):
|
||||||
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 = Will.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)
|
||||||
self.description = w.get("description", None)
|
self.description = w.get('description',None)
|
||||||
self.time = w.get("time", None)
|
self.time = w.get('time',None)
|
||||||
self.change = w.get("change", None)
|
self.change = w.get('change',None)
|
||||||
self.tx_fees = w.get("baltx_fees", 0)
|
self.tx_fees = w.get('tx_fees',0)
|
||||||
self.father = w.get("Father", None)
|
self.father = w.get('Father',None)
|
||||||
self.children = w.get("Children", None)
|
self.children = w.get('Children',None)
|
||||||
self.STATUS = copy.deepcopy(WillItem.STATUS_DEFAULT)
|
self.STATUS = copy.deepcopy(WillItem.STATUS_DEFAULT)
|
||||||
for s in self.STATUS:
|
for s in self.STATUS:
|
||||||
self.STATUS[s][1]=w.get(s,WillItem.STATUS_DEFAULT[s][1])
|
self.STATUS[s][1]=w.get(s,WillItem.STATUS_DEFAULT[s][1])
|
||||||
@@ -716,17 +646,19 @@ class WillItem(Logger):
|
|||||||
if wallet:
|
if wallet:
|
||||||
self.tx.add_info_from_wallet(wallet)
|
self.tx.add_info_from_wallet(wallet)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
out = {
|
out = {
|
||||||
"_id": self._id,
|
'_id':self._id,
|
||||||
"tx": self.tx,
|
'tx':self.tx,
|
||||||
"heirs": self.heirs,
|
'heirs':self.heirs,
|
||||||
"willexecutor": self.we,
|
'willexecutor':self.we,
|
||||||
"status": self.status,
|
'status':self.status,
|
||||||
"description": self.description,
|
'description':self.description,
|
||||||
"time": self.time,
|
'time':self.time,
|
||||||
"change": self.change,
|
'change':self.change,
|
||||||
"baltx_fees": self.tx_fees,
|
'tx_fees':self.tx_fees
|
||||||
}
|
}
|
||||||
for key in self.STATUS:
|
for key in self.STATUS:
|
||||||
try:
|
try:
|
||||||
@@ -742,7 +674,7 @@ class WillItem(Logger):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
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,Will.check_anticipate(ow,self))
|
||||||
if int(nl) < self.tx.locktime:
|
if int(nl) < self.tx.locktime:
|
||||||
self.tx.locktime = int(nl)
|
self.tx.locktime = int(nl)
|
||||||
@@ -750,6 +682,7 @@ class WillItem(Logger):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def search_anticipate(self,all_inputs):
|
def search_anticipate(self,all_inputs):
|
||||||
anticipated = False
|
anticipated = False
|
||||||
for ow in self.search(all_inputs):
|
for ow in self.search(all_inputs):
|
||||||
@@ -778,22 +711,21 @@ class WillItem(Logger):
|
|||||||
|
|
||||||
def check_willexecutor(self):
|
def check_willexecutor(self):
|
||||||
try:
|
try:
|
||||||
if resp := Willexecutors.check_transaction(self._id, self.we["url"]):
|
if resp:=Willexecutors.check_transaction(self._id,self.we['url']):
|
||||||
if "tx" in resp and resp["tx"] == str(self.tx):
|
if 'tx' in resp and resp['tx']==str(self.tx):
|
||||||
self.set_status("PUSHED")
|
self.set_status('PUSHED')
|
||||||
self.set_status("CHECKED")
|
self.set_status('CHECKED')
|
||||||
else:
|
else:
|
||||||
self.set_status("CHECK_FAIL")
|
self.set_status('CHECK_FAIL')
|
||||||
self.set_status("PUSHED", False)
|
self.set_status('PUSHED',False)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.set_status("CHECK_FAIL")
|
self.set_status('CHECK_FAIL')
|
||||||
self.set_status("PUSHED", False)
|
self.set_status('PUSHED',False)
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.error(f"exception checking transaction: {e}")
|
_logger.error(f"exception checking transaction: {e}")
|
||||||
self.set_status("CHECK_FAIL")
|
self.set_status('CHECK_FAIL')
|
||||||
|
|
||||||
def get_color(self):
|
def get_color(self):
|
||||||
if self.get_status("INVALIDATED"):
|
if self.get_status("INVALIDATED"):
|
||||||
return "#f87838"
|
return "#f87838"
|
||||||
@@ -816,54 +748,29 @@ class WillItem(Logger):
|
|||||||
else:
|
else:
|
||||||
return "#ffffff"
|
return "#ffffff"
|
||||||
|
|
||||||
|
|
||||||
class WillException(Exception):
|
class WillException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WillExpiredException(WillException):
|
class WillExpiredException(WillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NotCompleteWillException(WillException):
|
class NotCompleteWillException(WillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HeirChangeException(NotCompleteWillException):
|
class HeirChangeException(NotCompleteWillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TxFeesChangedException(NotCompleteWillException):
|
class TxFeesChangedException(NotCompleteWillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HeirNotFoundException(NotCompleteWillException):
|
class HeirNotFoundException(NotCompleteWillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WillexecutorChangeException(NotCompleteWillException):
|
class WillexecutorChangeException(NotCompleteWillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoWillExecutorNotPresent(NotCompleteWillException):
|
class NoWillExecutorNotPresent(NotCompleteWillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WillExecutorNotPresent(NotCompleteWillException):
|
class WillExecutorNotPresent(NotCompleteWillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoHeirsException(WillException):
|
class NoHeirsException(WillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AmountException(WillException):
|
class AmountException(WillException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PercAmountException(AmountException):
|
class PercAmountException(AmountException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FixedAmountException(AmountException):
|
class FixedAmountException(AmountException):
|
||||||
pass
|
pass
|
||||||
|
|||||||
158
willexecutors.py
158
willexecutors.py
@@ -1,21 +1,18 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from aiohttp import ClientResponse
|
from aiohttp import ClientResponse
|
||||||
|
|
||||||
|
from electrum.network import Network
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
|
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 electrum.logging import get_logger
|
|
||||||
from electrum.network import Network
|
|
||||||
|
|
||||||
from .bal import BalPlugin
|
from .bal import BalPlugin
|
||||||
from .util import Util
|
from .util import Util
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = 5
|
DEFAULT_TIMEOUT = 5
|
||||||
_logger = get_logger(__name__)
|
_logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Willexecutors:
|
class Willexecutors:
|
||||||
|
|
||||||
def save(bal_plugin, willexecutors):
|
def save(bal_plugin, willexecutors):
|
||||||
@@ -23,9 +20,7 @@ class Willexecutors:
|
|||||||
aw[constants.net.NET_NAME]=willexecutors
|
aw[constants.net.NET_NAME]=willexecutors
|
||||||
bal_plugin.WILLEXECUTORS.set(aw)
|
bal_plugin.WILLEXECUTORS.set(aw)
|
||||||
|
|
||||||
def get_willexecutors(
|
def get_willexecutors(bal_plugin, update = False,bal_window=False,force=False,task=True):
|
||||||
bal_plugin, update=False, bal_window=False, force=False, task=True
|
|
||||||
):
|
|
||||||
willexecutors = bal_plugin.WILLEXECUTORS.get()
|
willexecutors = bal_plugin.WILLEXECUTORS.get()
|
||||||
willexecutors=willexecutors.get(constants.net.NET_NAME,{})
|
willexecutors=willexecutors.get(constants.net.NET_NAME,{})
|
||||||
to_del=[]
|
to_del=[]
|
||||||
@@ -35,7 +30,7 @@ class Willexecutors:
|
|||||||
continue
|
continue
|
||||||
Willexecutors.initialize_willexecutor(willexecutors[w],w)
|
Willexecutors.initialize_willexecutor(willexecutors[w],w)
|
||||||
for w in to_del:
|
for w in to_del:
|
||||||
_logger.error("error Willexecutor to delete type:{} ", type(willexecutor[w]),w)
|
print("ERROR: WILLEXECUTOR TO DELETE:", w)
|
||||||
del willexecutors[w]
|
del willexecutors[w]
|
||||||
bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME,{})
|
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():
|
||||||
@@ -52,51 +47,42 @@ class Willexecutors:
|
|||||||
ping_willexecutors = True
|
ping_willexecutors = True
|
||||||
if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force:
|
if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force:
|
||||||
if bal_window:
|
if bal_window:
|
||||||
ping_willexecutors = bal_window.window.question(
|
ping_willexecutors = bal_window.window.question(_("Contact willexecutors servers to update payment informations?"))
|
||||||
_(
|
|
||||||
"Contact willexecutors servers to update payment informations?"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if ping_willexecutors:
|
if ping_willexecutors:
|
||||||
if task:
|
if task:
|
||||||
bal_window.ping_willexecutors(willexecutors,task)
|
bal_window.ping_willexecutors(willexecutors,task)
|
||||||
else:
|
else:
|
||||||
bal_window.ping_willexecutors_task(willexecutors)
|
bal_window.ping_willexecutors_task(willexecutors)
|
||||||
w_sorted = dict(
|
w_sorted = dict(sorted(willexecutors.items(), key=lambda w:w[1].get('sort',0),reverse=True))
|
||||||
sorted(
|
|
||||||
willexecutors.items(), key=lambda w: w[1].get("sort", 0), reverse=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return w_sorted
|
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:
|
||||||
willexecutor["selected"] = value
|
willexecutor['selected']=value
|
||||||
try:
|
try:
|
||||||
return willexecutor["selected"]
|
return willexecutor['selected']
|
||||||
except:
|
except:
|
||||||
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'):
|
||||||
if willitem.get_status("COMPLETE"):
|
if willitem.get_status('COMPLETE'):
|
||||||
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 Willexecutors.is_selected(willexecutor):
|
||||||
if not url in willexecutors:
|
if not url in willexecutors:
|
||||||
willexecutor["txs"] = ""
|
willexecutor['txs']=""
|
||||||
willexecutor["txsids"] = []
|
willexecutor['txsids']=[]
|
||||||
willexecutor["broadcast_status"] = _("Waiting...")
|
willexecutor['broadcast_status']= _("Waiting...")
|
||||||
willexecutors[url]=willexecutor
|
willexecutors[url]=willexecutor
|
||||||
willexecutors[url]["txs"] += str(willitem.tx) + "\n"
|
willexecutors[url]['txs']+=str(willitem.tx)+"\n"
|
||||||
willexecutors[url]["txsids"].append(wid)
|
willexecutors[url]['txsids'].append(wid)
|
||||||
|
|
||||||
return willexecutors
|
return willexecutors
|
||||||
|
|
||||||
@@ -105,81 +91,66 @@ class Willexecutors:
|
|||||||
for url,v in willexecutors.items():
|
for url,v in willexecutors.items():
|
||||||
if Willexecutors.is_selected(willexecutor):
|
if Willexecutors.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 Willexecutors.is_selected(willexecutor):
|
||||||
if "txs" in willexecutor:
|
if 'txs' in willexecutor:
|
||||||
Willexecutors.push_transactions_to_willexecutor(
|
Willexecutors.push_transactions_to_willexecutor(willexecutors[url]['txs'],url)
|
||||||
willexecutors[url]["txs"], url
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_request(method, url, data=None, *, timeout=10):
|
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'] = f"BalPlugin v:{BalPlugin.version()}"
|
||||||
headers["Content-Type"] = "text/plain"
|
headers['Content-Type']='text/plain'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if method == "get":
|
if method == 'get':
|
||||||
response = Network.send_http_on_proxy(
|
response = Network.send_http_on_proxy(method, url,
|
||||||
method,
|
|
||||||
url,
|
|
||||||
params=data,
|
params=data,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
on_finish=Willexecutors.handle_response,
|
on_finish=Willexecutors.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=Willexecutors.handle_response,
|
||||||
timeout=timeout,
|
timeout=timeout)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise Exception(f"unexpected {method=!r}")
|
raise Exception(f"unexpected {method=!r}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.error(f"exception sending request {e}")
|
_logger.error(f"exception sending request {e}")
|
||||||
raise e
|
raise e
|
||||||
else:
|
else:
|
||||||
_logger.debug(f"--> {response}")
|
_logger.debug(f'--> {response}')
|
||||||
return response
|
return response
|
||||||
|
|
||||||
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']=Willexecutors.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(
|
if w:=Willexecutors.send_request('post', willexecutor['url']+"/"+constants.net.NET_NAME+"/pushtxs", data=willexecutor['txs'].encode('ascii')):
|
||||||
"post",
|
willexecutor['broadcast_status'] = _("Success")
|
||||||
willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs",
|
|
||||||
data=willexecutor["txs"].encode("ascii"),
|
|
||||||
):
|
|
||||||
willexecutor["broadcast_status"] = _("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}")
|
||||||
raise Exception(w)
|
raise Exception(w)
|
||||||
else:
|
else:
|
||||||
@@ -189,7 +160,7 @@ class Willexecutors:
|
|||||||
if str(e) == "already present":
|
if str(e) == "already present":
|
||||||
raise Willexecutors.AlreadyPresentException()
|
raise Willexecutors.AlreadyPresentException()
|
||||||
out=False
|
out=False
|
||||||
willexecutor["broadcast_status"] = _("Failed")
|
willexecutor['broadcast_status'] = _("Failed")
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -197,6 +168,7 @@ class Willexecutors:
|
|||||||
for url,we in willexecutors.items():
|
for url,we in willexecutors.items():
|
||||||
Willexecutors.get_info_task(url,we)
|
Willexecutors.get_info_task(url,we)
|
||||||
|
|
||||||
|
|
||||||
def get_info_task(url,willexecutor):
|
def get_info_task(url,willexecutor):
|
||||||
w=None
|
w=None
|
||||||
try:
|
try:
|
||||||
@@ -205,37 +177,35 @@ class Willexecutors:
|
|||||||
netname="bitcoin"
|
netname="bitcoin"
|
||||||
if constants.net.NET_NAME!="mainnet":
|
if constants.net.NET_NAME!="mainnet":
|
||||||
netname=constants.net.NET_NAME
|
netname=constants.net.NET_NAME
|
||||||
w = Willexecutors.send_request("get", url + "/" + netname + "/info")
|
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']
|
||||||
willexecutor["address"] = w["address"]
|
willexecutor['address'] = w['address']
|
||||||
if not willexecutor["info"]:
|
if not willexecutor['info']:
|
||||||
willexecutor["info"] = w["info"]
|
willexecutor['info'] = w['info']
|
||||||
_logger.debug(f"response_data {w['address']}")
|
_logger.debug(f"response_data {w['address']}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.error(f"error {e} contacting {url}: {w}")
|
_logger.error(f"error {e} contacting {url}: {w}")
|
||||||
willexecutor["status"] = "KO"
|
willexecutor['status']="KO"
|
||||||
|
|
||||||
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'] = Willexecutors.is_selected(willexecutor,selected)
|
||||||
|
|
||||||
def download_list(bal_plugin):
|
def download_list(bal_plugin):
|
||||||
try:
|
try:
|
||||||
l = Willexecutors.send_request(
|
l = Willexecutors.send_request('get',"https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100")
|
||||||
"get", "https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100"
|
del l['status']
|
||||||
)
|
|
||||||
del l["status"]
|
|
||||||
for w in l:
|
for w in l:
|
||||||
willexecutor=l[w]
|
willexecutor=l[w]
|
||||||
Willexecutors.initialize_willexecutor(willexecutor, w, "New", False)
|
Willexecutors.initialize_willexecutor(willexecutor,w,'New',False)
|
||||||
#bal_plugin.WILLEXECUTORS.set(l)
|
#bal_plugin.WILLEXECUTORS.set(l)
|
||||||
#bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True)
|
#bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True)
|
||||||
return l
|
return l
|
||||||
@@ -243,14 +213,13 @@ class Willexecutors:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.error(f"Failed to download willexecutors list: {e}")
|
_logger.error(f"Failed to download willexecutors list: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_willexecutors_list_from_json(bal_plugin):
|
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.WILLEXECUTORS.set(willexecutors)
|
||||||
return h
|
return h
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -261,15 +230,12 @@ class Willexecutors:
|
|||||||
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(
|
w = Willexecutors.send_request('post',url+"/searchtx",data=txid.encode('ascii'))
|
||||||
"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:
|
class WillExecutor:
|
||||||
def __init__(self,url,base_fee,chain,info,version):
|
def __init__(self,url,base_fee,chain,info,version):
|
||||||
self.url = url
|
self.url = url
|
||||||
@@ -279,4 +245,12 @@ class WillExecutor:
|
|||||||
self.version = version
|
self.version = version
|
||||||
|
|
||||||
def from_dict(d):
|
def from_dict(d):
|
||||||
we = WillExecutor(d["url"], d["base_fee"], d["chain"], d["info"], d["version"])
|
we = WillExecutor(
|
||||||
|
d['url'],
|
||||||
|
d['base_fee'],
|
||||||
|
d['chain'],
|
||||||
|
d['info'],
|
||||||
|
d['version']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user