diff --git a/bal.py b/bal.py
index b9abd54..62a5da2 100644
--- a/bal.py
+++ b/bal.py
@@ -1,15 +1,10 @@
import random
import os
-from hashlib import sha256
-from typing import NamedTuple, Optional, Dict, Tuple
from electrum.plugin import BasePlugin
-from electrum.util import to_bytes, bfh
from electrum import json_db
from electrum.transaction import tx_from_any
-from . import util as Util
-from . import willexecutors as Willexecutors
import os
json_db.register_dict('heirs', tuple, None)
json_db.register_dict('will', lambda x: get_will(x), None)
@@ -17,11 +12,10 @@ json_db.register_dict('will_settings', lambda x:x, None)
from electrum.logging import get_logger
def get_will(x):
try:
- #print("______________________________________________________________________________________________________")
- #print(x)
+
+
x['tx']=tx_from_any(x['tx'])
except Exception as e:
- #Util.print_var(x)
raise e
return x
@@ -69,7 +63,7 @@ class BalPlugin(BasePlugin):
HIDE_INVALIDATED:True,
ALLOW_REPUSH: False,
WILLEXECUTORS: {
- 'http://bitcoin-after.life:9137': {
+ 'https://bitcoin-after.life:9137': {
"base_fee": 100000,
"status": "New",
"info":"Bitcoin After Life Will Executor",
@@ -102,10 +96,8 @@ class BalPlugin(BasePlugin):
def config_get(self,key):
v = self.config.get(key,None)
- print("config get",key,v)
if v is None:
self.config.set_key(key,self.DEFAULT_SETTINGS[key])
- print("config setkey",key)
v = self.DEFAULT_SETTINGS[key]
return v
diff --git a/heirs.py b/heirs.py
index 2523ee2..3a86be4 100644
--- a/heirs.py
+++ b/heirs.py
@@ -15,8 +15,8 @@ import datetime
import urllib.request
import urllib.parse
from .bal import BalPlugin
-from . import util as Util
-from . import willexecutors as Willexecutors
+from .util import Util
+from .willexecutors import Willexecutors
if TYPE_CHECKING:
from .wallet_db import WalletDB
from .simple_config import SimpleConfig
@@ -38,6 +38,7 @@ def reduce_outputs(in_amount, out_amount, fee, outputs):
for output in outputs:
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
def get_current_height(network:'Network'):
#if no network or not up to date, just set locktime to zero
@@ -57,7 +58,7 @@ def get_current_height(network:'Network'):
# discourage "fee sniping"
height = min(chain_height, server_height)
return height
-
+"""
diff --git a/manifest.json b/manifest.json
index b1061fc..16ca7ba 100644
--- a/manifest.json
+++ b/manifest.json
@@ -3,5 +3,6 @@
"fullname": "Bitcoin After Life",
"description": "Provides free and decentralized inheritance support",
"author":"SvÄtantrya",
- "available_for": ["qt"]
+ "available_for": ["qt"],
+ "icon":"icons/bal32x32.png"
}
diff --git a/qt.py b/qt.py
index adf081d..8989342 100644
--- a/qt.py
+++ b/qt.py
@@ -7,68 +7,334 @@ Bitcoin after life
'''
+
+
+
+
+import copy
+from datetime import datetime
+from decimal import Decimal
+import enum
+from functools import partial
+import json
import os
import random
+import sys
import traceback
-from functools import partial
-import sys
-import copy
-
-import sys
-
-from electrum.plugin import hook
-from electrum.i18n import _
-from electrum.util import make_dir, InvalidPassword, UserCancelled,resource_path
-from electrum.util import bfh, read_json_file,write_json_file,decimal_point_to_base_unit_name,FileImportFailed,FileExportFailed
-
-from electrum.gui.qt.util import (EnterButton, WWLabel,
- Buttons, CloseButton, OkButton,import_meta_gui,export_meta_gui,char_width_in_lineedit,CancelButton,HelpButton)
-
-from electrum.gui.qt.qrtextedit import ScanQRTextEdit
-from electrum.gui.qt.main_window import StatusBarButton
-from electrum.gui.qt.password_dialog import PasswordDialog
-from electrum.gui.qt.transaction_dialog import TxDialog
-from electrum import constants
-from electrum.transaction import Transaction
-from .bal import BalPlugin
-from .heirs import Heirs
-from . import util as Util
-from . import will as Will
-
-from .balqt.locktimeedit import HeirsLockTimeEdit
-from .balqt.willexecutor_dialog import WillExecutorDialog
-from .balqt.preview_dialog import PreviewDialog,PreviewList
-from .balqt.heir_list import HeirList
-from .balqt.amountedit import PercAmountEdit
-from .balqt.willdetail import WillDetailDialog
-from .balqt.closedialog import BalCloseDialog
-from .balqt import qt_resources
-from . import willexecutors as Willexecutors
-from electrum.transaction import tx_from_any
import time
-from electrum import json_db
-from electrum.json_db import StoredDict
-import datetime
+from typing import (
+ TYPE_CHECKING,
+ Callable,
+ Optional,
+ List,
+ Union,
+ Tuple,
+ Mapping,Any)
import urllib.parse
import urllib.request
-from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple, Mapping
-from .balqt.baldialog import BalDialog,BalWaitingDialog,BalBlockingWaitingDialog,bal_checkbox
-from electrum.logging import Logger
-if qt_resources.QT_VERSION == 5:
- from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize
- from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,QIcon,
- QColor, QDesktopServices, qRgba, QPainterPath,QPalette)
+try:
+ QT_VERSION=sys._GUI_QT_VERSION
+except:
+ QT_VERSION=6
+
+if 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)
+ from PyQt5.QtCore import (
+ QPersistentModelIndex,
+ QModelIndex,
+ Qt,
+ QRectF,
+ QRect,
+ QSizeF,
+ QUrl,
+ QPoint,
+ QSize,
+ QDateTime,
+ pyqtProperty,
+ pyqtSignal,
+ pyqtSlot,
+ QObject,
+ QEventLoop,
+ pyqtSignal)
+ from PyQt5.QtGui import (
+ QStandardItemModel,
+ QStandardItem,
+ QPalette,
+ QColor,
+ QPixmap,
+ QImage,
+ QBitmap,
+ QPainter,
+ QFontDatabase,
+ QPen,
+ QFont,
+ QColor,
+ QDesktopServices,
+ qRgba,
+ QPainterPath,
+ QPalette,
+ QPixmap,
+ QImage,
+ QBitmap,
+ QPainter,
+ QFontDatabase,
+ QPen,
+ QFont,
+ QIcon,
+ QColor,
+ QDesktopServices,
+ qRgba,
+ QPainterPath,
+ QPalette)
+ from PyQt5.QtWidgets import (
+ QDialog,
+ QVBoxLayout,
+ QHBoxLayout,
+ QPushButton,
+ QLabel,
+ QMenu,
+ QDialog,
+ QVBoxLayout,
+ QHBoxLayout,
+ QPushButton,
+ QLabel,
+ QWidget,
+ QScrollArea,
+ QAbstractItemView,
+ QWidget,
+ QDateTimeEdit,
+ QLineEdit,
+ QStyle,
+ QStyleOptionFrame,
+ QSizePolicy,
+ QCheckBox,
+ QGridLayout,
+ QHBoxLayout,
+ QLabel,
+ QLineEdit,
+ QMenu,
+ QMenuBar,
+ QPushButton,
+ QScrollArea,
+ QSpacerItem,
+ QSizePolicy,
+ QSpinBox,
+ QVBoxLayout,
+ QWidget,
+ QStyle,
+ QStyleOptionFrame,
+ QComboBox,
+ QHBoxLayout,
+ )
+else: #QT6
+ from PyQt6.QtCore import (
+ Qt,
+ QDateTime,
+ QPersistentModelIndex,
+ QModelIndex,
+ pyqtProperty,
+ pyqtSignal,
+ pyqtSlot,
+ QObject,
+ pyqtSignal,
+ QSize,
+ Qt,
+ QRectF,
+ QRect,
+ QSizeF,
+ QUrl,
+ QPoint,
+ QSize)
+ from PyQt6.QtGui import (
+ QStandardItemModel,
+ QStandardItem,
+ QPalette,
+ QColor,
+ QPixmap,
+ QImage,
+ QBitmap,
+ QPainter,
+ QFontDatabase,
+ QPen,
+ QFont,
+ QColor,
+ QDesktopServices,
+ qRgba,
+ QPainterPath,
+ QPalette,
+ QPainter,
+ QPixmap,
+ QImage,
+ QBitmap,
+ QPainter,
+ QFontDatabase,
+ QPen,
+ QFont,
+ QIcon,
+ QColor,
+ QDesktopServices,
+ qRgba,
+ QPainterPath,
+ QPalette)
+
+ from PyQt6.QtWidgets import (
+ QDialog,
+ QVBoxLayout,
+ QHBoxLayout,
+ QPushButton,
+ QLabel,
+ QMenu,
+ QAbstractItemView,
+ QWidget,
+ QDialog,
+ QVBoxLayout,
+ QHBoxLayout,
+ QPushButton,
+ QLabel,
+ QWidget,
+ QScrollArea,
+ QDateTimeEdit,
+ QLabel,
+ QVBoxLayout,
+ QCheckBox,
+ QWidget,
+ QLabel,
+ QVBoxLayout,
+ QCheckBox,
+ QLineEdit,
+ QStyle,
+ QStyleOptionFrame,
+ QSizePolicy,
+ QGridLayout,
+ QVBoxLayout,
+ QHBoxLayout,
+ QLabel,
+ QPushButton,
+ QLineEdit,
+ QCheckBox,
+ QSpinBox,
+ QMenuBar,
+ QMenu,
+ QLineEdit,
+ QScrollArea,
+ QWidget,
+ QSpacerItem,
+ QComboBox,
+ QSizePolicy)
+
+
+
+
+from electrum import json_db
+from electrum import constants
+from electrum.bitcoin import (
+ is_address,
+ NLOCKTIME_MIN,
+ NLOCKTIME_MAX,
+ NLOCKTIME_BLOCKHEIGHT_MAX)
+from electrum.gui.qt.amountedit import (
+ BTCAmountEdit,
+ char_width_in_lineedit,
+ ColorScheme)
+
+if TYPE_CHECKING:
+ from electrum.gui.qt.main_window import ElectrumWindow
+
+from electrum.gui.qt.util import (
+ Buttons,
+ read_QIcon,
+ import_meta_gui,
+ export_meta_gui,
+ MessageBoxMixin,
+ Buttons,
+ read_QIcon,
+ export_meta_gui,
+ MessageBoxMixin,
+ char_width_in_lineedit,
+ ColorScheme,
+ Buttons,
+ CancelButton,
+ char_width_in_lineedit,
+ CloseButton,
+ EnterButton,
+ HelpButton,
+ MessageBoxMixin,
+ OkButton,
+ TaskThread,
+ WindowModalDialog,
+ WWLabel,
+ )
+from electrum.gui.qt.main_window import StatusBarButton
+from electrum.gui.qt.my_treeview import (
+ MyTreeView,
+ MySortModel)
+from electrum.gui.qt.transaction_dialog import TxDialog
+from electrum.gui.qt.password_dialog import PasswordDialog
+from electrum.gui.qt.qrtextedit import ScanQRTextEdit
+from electrum.i18n import _
+from electrum.logging import get_logger,Logger
+from electrum.json_db import StoredDict
+from electrum.network import (
+ Network,
+ TxBroadcastError,
+ BestEffortRequestFailed
+ )
+from electrum.plugin import (
+ hook,
+ run_hook)
+from electrum.transaction import (
+ Transaction,
+ tx_from_any)
+from electrum.util import (
+ write_json_file,
+ read_json_file,
+ make_dir,
+ InvalidPassword,
+ UserCancelled,
+ resource_path,
+ write_json_file,
+ read_json_file,
+ FileImportFailed,
+ bfh,
+ read_json_file,
+ write_json_file,
+ decimal_point_to_base_unit_name,
+ FileImportFailed,
+ DECIMAL_POINT,
+ FEERATE_PRECISION,
+ quantize_feerate,
+ UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE,
+ FileExportFailed)
+
+
+from .bal import BalPlugin
+from .bal_resources import (
+ DEFAULT_ICON,
+ icon_path)
+from .heirs import Heirs
+from .util import Util
+from .will import (
+ Will,
+ WillItem,
+ NoHeirsException,
+ NoWillExecutorNotPresent,
+ NotCompleteWillException,
+ AmountException,
+ HeirNotFoundException,
+ HeirChangeException,
+ WillexecutorChangeException,
+ WillExecutorNotPresent,
+ TxFeesChangedException,
+ WillExpiredException)
+
+from .willexecutors import Willexecutors
+_logger = get_logger(__name__)
+
- from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel,
- QPushButton, QLineEdit,QCheckBox,QSpinBox,QMenuBar,QMenu,QLineEdit,QScrollArea,QWidget,QSpacerItem,QSizePolicy)
-else:
- from PyQt6.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize
- from PyQt6.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,QIcon,
- QColor, QDesktopServices, qRgba, QPainterPath,QPalette)
- from PyQt6.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel,
- QPushButton, QLineEdit,QCheckBox,QSpinBox,QMenuBar,QMenu,QLineEdit,QScrollArea,QWidget,QSpacerItem,QSizePolicy)
class Plugin(BalPlugin,Logger):
@@ -81,29 +347,22 @@ class Plugin(BalPlugin,Logger):
@hook
def init_qt(self,gui_object):
- print("********************************************************************************************************************")
self.logger.info("HOOK init qt")
- print("logger")
try:
self.gui_object=gui_object
- print(dir(gui_object))
for window in gui_object.windows:
wallet = window.wallet
if wallet:
window.show_warning(_('Please restart Electrum to activate the BAL plugin'), title=_('Success'))
return
w = BalWindow(self,window)
- print("windows.winid",window.winid)
self.bal_windows[window.winId]= w
for child in window.children():
if isinstance(child,QMenuBar):
- print("found menubar")
for menu_child in child.children():
if isinstance(menu_child,QMenu):
- print("found qmenu")
try:
if menu_child.title()==_("&Tools"):
- print("found tools")
w.init_menubar_tools(menu_child)
except Exception as e:
@@ -120,7 +379,7 @@ class Plugin(BalPlugin,Logger):
def create_status_bar(self, sb):
self.logger.info("HOOK create status bar")
return
- b = StatusBarButton(qt_resources.read_QIcon('bal32x32.png'), "Bal "+_("Bitcoin After Life"),
+ b = StatusBarButton(read_QIcon('bal32x32.png'), "Bal "+_("Bitcoin After Life"),
partial(self.setup_dialog, sb), sb.height())
sb.addPermanentWidget(b)
@@ -134,12 +393,12 @@ class Plugin(BalPlugin,Logger):
def load_wallet(self,wallet, main_window):
self.logger.info("HOOK load wallet")
w = self.get_window(main_window)
- print(dir(w))
w.wallet = wallet
w.init_will()
w.willexecutors = Willexecutors.get_willexecutors(self, update=False, bal_window=w)
w.disable_plugin = False
w.ok=True
+
@hook
def close_wallet(self,wallet):
for winid,win in self.bal_windows.items():
@@ -152,7 +411,7 @@ class Plugin(BalPlugin,Logger):
w=BalWindow(self,window)
self.bal_windows[window.winId]=w
return w
-
+
def requires_settings(self):
return True
@@ -178,87 +437,26 @@ class Plugin(BalPlugin,Logger):
return
self.extension = bool(keystore.get_passphrase(password))
return keystore.get_seed(password)
+
def settings_dialog(self,window,wallet):
d = BalDialog(window, self.get_window_title("Settings"))
d.setMinimumSize(100, 200)
- qicon=qt_resources.read_QPixmap("bal32x32.png")
+ qicon=read_QPixmap("bal32x32.png")
lbl_logo = QLabel()
lbl_logo.setPixmap(qicon)
- #heir_locktime_time = QSpinBox()
- #heir_locktime_time.setMinimum(0)
- #heir_locktime_time.setMaximum(3650)
- #heir_locktime_time.setValue(int(self.config_get(BalPlugin.LOCKTIME_TIME)))
- #def on_heir_locktime_time():
- # value = heir_locktime_time.value()
- # self.config.set_key(BalPlugin.LOCKTIME_TIME,value,save=True)
- #heir_locktime_time.valueChanged.connect(on_heir_locktime_time)
-
- ##heir_locktimedelta_time = QSpinBox()
- #heir_locktimedelta_time.setMinimum(0)
- #heir_locktimedelta_time.setMaximum(3650)
- #heir_locktimedelta_time.setValue(int(self.config_get(BalPlugin.LOCKTIMEDELTA_TIME)))
- #def on_heir_locktime_time():
- #value = heir_locktime_time.value
- #self.config.set_key(BalPlugin.LOCKTIME_TIME,value,save=True)
- #heir_locktime_time.valueChanged.connect(on_heir_locktime_time)
-
- #heir_locktime_blocks = QSpinBox()
- #heir_locktime_blocks.setMinimum(0)
- #heir_locktime_blocks.setMaximum(144*3650)
- #heir_locktime_blocks.setValue(int(self.config_get(BalPlugin.LOCKTIME_BLOCKS)))
- #def on_heir_locktime_blocks():
- #value = heir_locktime_blocks.value()
- #self.config.set_key(BalPlugin.LOCKTIME_BLOCKS,value,save=True)
- #heir_locktime_blocks.valueChanged.connect(on_heir_locktime_blocks)
-
- #heir_locktimedelta_blocks = QSpinBox()
- #heir_locktimedelta_blocks.setMinimum(0)
- #heir_locktimedelta_blocks.setMaximum(144*3650)
- #heir_locktimedelta_blocks.setValue(int(self.config_get(BalPlugin.LOCKTIMEDELTA_BLOCKS)))
- #def on_heir_locktimedelta_blocks():
- #value = heir_locktimedelta_blocks.value()
- #self.config.set_key(BalPlugin.LOCKTIMEDELTA_TIME,value,save=True)
- #heir_locktimedelta_blocks.valueChanged.connect(on_heir_locktimedelta_blocks)
-
- #heir_tx_fees = QSpinBox()
- #heir_tx_fees.setMinimum(1)
- #heir_tx_fees.setMaximum(10000)
- #heir_tx_fees.setValue(int(self.config_get(BalPlugin.TX_FEES)))
- #def on_heir_tx_fees():
- #value = heir_tx_fees.value()
- #self.config.set_key(BalPlugin.TX_FEES,value,save=True)
- #heir_tx_fees.valueChanged.connect(on_heir_tx_fees)
- #heir_broadcast = bal_checkbox(self, BalPlugin.BROADCAST)
- #heir_ask_broadcast = bal_checkbox(self, BalPlugin.ASK_BROADCAST)
- #heir_invalidate = bal_checkbox(self, BalPlugin.INVALIDATE)
- #heir_ask_invalidate = bal_checkbox(self, BalPlugin.ASK_INVALIDATE)
- #heir_preview = bal_checkbox(self, BalPlugin.PREVIEW)
heir_ping_willexecutors = bal_checkbox(self, BalPlugin.PING_WILLEXECUTORS)
heir_ask_ping_willexecutors = bal_checkbox(self, BalPlugin.ASK_PING_WILLEXECUTORS)
- #print("setkey broadcast")
- #self.config.set_key(BalPlugin.BROADCAST,True)
heir_no_willexecutor = bal_checkbox(self, BalPlugin.NO_WILLEXECUTOR)
heir_hide_replaced = bal_checkbox(self,BalPlugin.HIDE_REPLACED,self)
heir_hide_invalidated = bal_checkbox(self,BalPlugin.HIDE_INVALIDATED,self)
- #heir_allow_repush = bal_checkbox(self,BalPlugin.ALLOW_REPUSH,self)
heir_repush = QPushButton("Rebroadcast transactions")
heir_repush.clicked.connect(partial(self.broadcast_transactions,True))
grid=QGridLayout(d)
- #add_widget(grid,"Refresh Time Days",heir_locktime_time,0,"Delta days for inputs to be invalidated and transactions resubmitted")
- #add_widget(grid,"Refresh Blocks",heir_locktime_blocks,1,"Delta blocks for inputs to be invalidated and transaction resubmitted")
- #add_widget(grid,"Transaction fees",heir_tx_fees,1,"Default transaction fees")
- #add_widget(grid,"Broadcast transactions",heir_broadcast,3,"")
- #add_widget(grid," - Ask before",heir_ask_broadcast,4,"")
- #add_widget(grid,"Invalidate transactions",heir_invalidate,5,"")
- #add_widget(grid," - Ask before",heir_ask_invalidate,6,"")
- #add_widget(grid,"Show preview before sign",heir_preview,7,"")
-
- #grid.addWidget(lbl_logo,0,0)
add_widget(grid,"Hide Replaced",heir_hide_replaced, 1, "Hide replaced transactions from will detail and list")
add_widget(grid,"Hide Invalidated",heir_hide_invalidated ,2,"Hide invalidated transactions from will detail and list")
add_widget(grid,"Ping Willexecutors",heir_ping_willexecutors,3,"Ping willexecutors to get payment info before compiling will")
@@ -266,8 +464,6 @@ class Plugin(BalPlugin,Logger):
add_widget(grid,"Backup Transaction",heir_no_willexecutor,5,"Add transactions without willexecutor")
grid.addWidget(heir_repush,6,0)
grid.addWidget(HelpButton("Broadcast all transactions to willexecutors including those already pushed"),6,2)
- #add_widget(grid,"Max Allowed TimeDelta Days",heir_locktimedelta_time,8,"")
- #add_widget(grid,"Max Allowed BlocksDelta",heir_locktimedelta_blocks,9,"")
if ret := bool(d.exec()):
try:
@@ -279,7 +475,6 @@ class Plugin(BalPlugin,Logger):
def broadcast_transactions(self,force):
for k,w in self.bal_windows.items():
- print(dir(w))
w.broadcast_transactions(force)
def update_all(self):
@@ -300,7 +495,6 @@ class shown_cv():
class BalWindow(Logger):
def __init__(self,bal_plugin: 'BalPlugin',window: 'ElectrumWindow'):
Logger.__init__(self)
- self.logger.info("loggo tutto")
self.bal_plugin = bal_plugin
self.window = window
self.heirs = {}
@@ -336,9 +530,8 @@ class BalWindow(Logger):
- print("add tab heir",self.heirs_tab);
- add_optional_tab(self.window.tabs, self.heirs_tab, qt_resources.read_QIcon("heir.png"), _("&Heirs"))
- add_optional_tab(self.window.tabs, self.will_tab, qt_resources.read_QIcon("will.png"), _("&Will"))
+ add_optional_tab(self.window.tabs, self.heirs_tab, read_QIcon("heir.png"), _("&Heirs"))
+ add_optional_tab(self.window.tabs, self.will_tab, read_QIcon("will.png"), _("&Will"))
tools_menu.addSeparator()
self.tools_menu.willexecutors_action = tools_menu.addAction(_("&Will-Executors"), self.show_willexecutor_dialog)
self.window.view_menu.addSeparator()
@@ -348,7 +541,7 @@ class BalWindow(Logger):
def load_willitems(self):
self.willitems={}
for wid,w in self.will.items():
- self.willitems[wid]=Will.WillItem(w,wallet=self.wallet)
+ self.willitems[wid]=WillItem(w,wallet=self.wallet)
if self.willitems:
self.will_list.will=self.willitems
self.will_list.update_will(self.willitems)
@@ -397,7 +590,6 @@ class BalWindow(Logger):
def create_heirs_tab(self):
self.heir_list = l = HeirList(self)
- print("heir_list",l)
tab = self.window.create_list_tab(l)
tab.is_shown_cv = shown_cv(True)
return tab
@@ -409,7 +601,7 @@ class BalWindow(Logger):
return tab
def new_heir_dialog(self):
- d = BalDialog(self.window, self.get_window_title("New heir"))
+ d = BalDialog(self.window, self.bal_plugin.get_window_title("New heir"))
vbox = QVBoxLayout(d)
grid = QGridLayout()
@@ -429,22 +621,10 @@ class BalWindow(Logger):
grid.addWidget(heir_address, 2, 1)
grid.addWidget(HelpButton("heir bitcoin address"),2,2)
- #grid.addWidget(QLabel(_("xPub")), 2, 2)
grid.addWidget(QLabel(_("Amount")),3,0)
grid.addWidget(heir_amount,3,1)
grid.addWidget(HelpButton("Fixed or Percentage amount if end with %"),3,2)
- #grid.addWidget(QLabel(_("LockTime")), 4, 0)
- #grid.addWidget(heir_locktime, 4, 1)
- #grid.addWidget(HelpButton("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"
- # +"when using d or y time will be set to 00:00 for privacy reasons\n"
- # +"when used without suffix it can be used to indicate:\n"
- # +" - exact block(if value is less than 500,000,000)\n"
- # +" - exact block timestamp(if value greater than 500,000,000"),4,2)
-
vbox.addLayout(grid)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
while d.exec():
@@ -518,7 +698,7 @@ class BalWindow(Logger):
if Willexecutors.is_selected(w):
f=True
if not f:
- raise Will.NoWillExecutorNotPresent("No Will-Executor or backup transaction selected")
+ raise NoWillExecutorNotPresent("No Will-Executor or backup transaction selected")
txs = self.heirs.get_transactions(self.bal_plugin,self.window.wallet,self.will_settings['tx_fees'],None,self.date_to_check)
self.logger.info(txs)
creation_time = time.time()
@@ -537,7 +717,7 @@ class BalWindow(Logger):
tx['time'] = creation_time
tx['heirs'] = copy.deepcopy(txs[txid].heirs)
tx['txchildren'] = []
- will[txid]=Will.WillItem(tx,_id=txid,wallet=self.wallet)
+ will[txid]=WillItem(tx,_id=txid,wallet=self.wallet)
self.update_will(will)
except Exception as e:
raise e
@@ -565,7 +745,7 @@ class BalWindow(Logger):
def init_class_variables(self):
if not self.heirs:
- raise Will.NoHeirsException()
+ raise NoHeirsException()
return
try:
self.date_to_check = Util.parse_locktime_string(self.will_settings['threshold'])
@@ -592,7 +772,7 @@ class BalWindow(Logger):
self.init_class_variables()
try:
Will.check_amounts(self.heirs,self.willexecutors,self.window.wallet.get_utxos(),self.date_to_check,self.window.wallet.dust_threshold())
- except Will.AmountException as e:
+ except AmountException as e:
self.show_warning(_(f"In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.\n{e}"))
locktime = Util.parse_locktime_string(self.will_settings['locktime'])
if locktime < self.date_to_check:
@@ -609,23 +789,23 @@ class BalWindow(Logger):
try:
self.check_will()
- except Will.WillExpiredException as e:
+ except WillExpiredException as e:
self.invalidate_will()
return
- except Will.NoHeirsException:
+ except NoHeirsException:
return
- except Will.NotCompleteWillException as e:
+ except NotCompleteWillException as e:
self.logger.info("{}:{}".format(type(e),e))
message = False
- if isinstance(e,Will.HeirChangeException):
+ if isinstance(e,HeirChangeException):
message ="Heirs changed:"
- elif isinstance(e,Will.WillExecutorNotPresent):
+ elif isinstance(e,WillExecutorNotPresent):
message = "Will-Executor not present:"
- elif isinstance(e,Will.WillexecutorChangeException):
+ elif isinstance(e,WillexecutorChangeException):
message = "Will-Executor changed"
- elif isinstance(e,Will.TxFeesChangedException):
+ elif isinstance(e,TxFeesChangedException):
message = "Txfees are changed"
- elif isinstance(e,Will.HeirNotFoundException):
+ elif isinstance(e,HeirNotFoundException):
message = "Heir not found"
if message:
@@ -638,9 +818,9 @@ class BalWindow(Logger):
self.check_will()
for wid,w in self.willitems.items():
self.wallet.set_label(wid,"BAL Transaction")
- except Will.WillExpiredException as e:
+ except WillExpiredException as e:
self.invalidate_will()
- except Will.NotCompleteWillException as e:
+ except NotCompleteWillException as e:
self.show_error("Error:{}\n {}".format(str(e),_("Please, check your heirs, locktime and threshold!")))
self.window.history_list.update()
@@ -666,7 +846,7 @@ class BalWindow(Logger):
external_keypairs=external_keypairs,
payment_identifier=payment_identifier,
)
- d.setWindowIcon(qt_resources.read_QIcon("bal32x32.png"))
+ d.setWindowIcon(read_QIcon("bal32x32.png"))
except SerializationError as e:
self.logger.error('unable to deserialize the transaction')
parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
@@ -967,3 +1147,1637 @@ def add_widget(grid,label,widget,row,help_):
grid.addWidget(QLabel(_(label)),row,0)
grid.addWidget(widget,row,1)
grid.addWidget(HelpButton(help_),row,2)
+
+
+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_date_e = LockTimeDateEdit(self,time_edit = self)
+ self.editors = [self.locktime_raw_e, self.locktime_date_e]
+
+ self.combo = QComboBox()
+ options = [_("Raw"),_("Date")]
+ self.option_index_to_editor_map = {
+ 0: self.locktime_raw_e,
+ 1: 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_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()
+
+ 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))
+
+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)
+
+
+_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+='%'
+
+
+ self.setText(s)
+ self.setModified(self.hasFocus())
+ self.setCursorPosition(pos)
+
+ 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")
+
+
+
+class BalDialog(WindowModalDialog):
+
+ def __init__(self,parent,title=None, icon = 'bal32x32.png'):
+ self.parent=parent
+ WindowModalDialog.__init__(self,self.parent,title)
+ self.setWindowIcon(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)
+
+
+
+
+
+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.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
+
+ 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 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 AmountException:
+ self.msg_edit_row(''+_("In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts"+""))
+
+ self.msg_set_checking()
+ have_to_build=False
+ try:
+ self.bal_window.check_will()
+ self.msg_set_checking('Ok')
+ except 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 NoHeirsException:
+ self.msg_set_checking("No Heirs")
+ except NotCompleteWillException as e:
+ message = False
+ have_to_build=True
+ if isinstance(e,HeirChangeException):
+ message ="Heirs changed:"
+ elif isinstance(e,WillExecutorNotPresent):
+ message = "Will-Executor not present"
+ elif isinstance(e,WillexecutorChangeException):
+ message = "Will-Executor changed"
+ elif isinstance(e,TxFeesChangedException):
+ message = "Txfees are changed"
+ elif isinstance(e,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))
+
+ 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)
+ _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)
+
+
+ def ask_password(self,msg=None):
+ self.password=self.bal_window.get_wallet_password(msg,parent=self)
+
+ def msg_edit_row(self,line,row=None):
+ _logger.debug(f"{row},{line}")
+
+ try:
+ self.labels[row]=line
+ except Exception as e:
+ self.labels.append(line)
+ row=len(self.labels)-1
+
+ self.updatemessage.emit()
+
+ return row
+
+ def msg_del_row(self,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
+
+
+
+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'),
+ }
+ 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.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 == 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:
+ prior_name = (edit_key,)+prior_name[:col-1]+(text,)+prior_name[col:]
+
+ try:
+ self.bal_window.set_heir(prior_name)
+ except Exception as e:
+ pass
+
+ try:
+ self.bal_window.set_heir((edit_key,)+original)
+ except Exception as e:
+ 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):
+ 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():
+ 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)
+
+ 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.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)
+
+ 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):
+ return self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY+col+1)
+
+ 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())
+
+ 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"
+ +" - 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"
+ +" - 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()
+
+
+
+
+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)))
+
+ 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(_("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()
+
+def read_QIcon(icon_basename: str=DEFAULT_ICON) -> QIcon:
+ return QIcon(icon_path(icon_basename))
+def read_QPixmap(icon_basename: str=DEFAULT_ICON) -> QPixmap:
+ return QPixmap(icon_path(icon_basename))
+
+
+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)
+ b = QPushButton(_('Invalidate'))
+ b.clicked.connect(bal_window.invalidate_will)
+ hlayout.addWidget(b)
+ self.vlayout.addWidget(w)
+
+ self.paint_scroll_area()
+ 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 = 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 = ""+_(str(title)) + f":\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
+ 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("Heirs:"))
+ 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(_("Willexecutor: LOCKTIME_THRESHOLD:
- dt = datetime.fromtimestamp(locktime).isoformat()
- return dt
-
- except Exception as e:
- #print(e)
- pass
- return str(locktime)
-
-def str_to_locktime(locktime):
- try:
- if locktime[-1] in ('y','d','b'):
- return locktime
- else: return int(locktime)
- except Exception as e:
- pass
- #print(e)
- dt_object = datetime.fromisoformat(locktime)
- timestamp = dt_object.timestamp()
- return int(timestamp)
-
-def parse_locktime_string(locktime,w=None):
- try:
- return int(locktime)
-
- except Exception as e:
- pass
- #print("parse_locktime_string",e)
- try:
- now = datetime.now()
- if locktime[-1] == 'y':
- locktime = str(int(locktime[:-1])*365) + "d"
- if locktime[-1] == 'd':
- return int((now + timedelta(days = int(locktime[:-1]))).replace(hour=0,minute=0,second=0,microsecond=0).timestamp())
- if locktime[-1] == 'b':
- locktime = int(locktime[:-1])
- height = 0
- if w:
- height = get_current_height(w.network)
- locktime+=int(height)
- return int(locktime)
- except Exception as e:
- print("parse_locktime_string",e)
- #raise e
- return 0
-
-
-def int_locktime(seconds=0,minutes=0,hours=0, days=0, blocks = 0):
- return int(seconds + minutes*60 + hours*60*60 + days*60*60*24 + blocks * 600)
-
-def encode_amount(amount, decimal_point):
- if is_perc(amount):
- return amount
- else:
+class Util:
+ def locktime_to_str(locktime):
try:
- return int(float(amount)*pow(10,decimal_point))
- except:
- return 0
+ locktime=int(locktime)
+ if locktime > LOCKTIME_THRESHOLD:
+ dt = datetime.fromtimestamp(locktime).isoformat()
+ return dt
-def decode_amount(amount,decimal_point):
- if is_perc(amount):
- return amount
- else:
- num=8-decimal_point
- basestr="{{:0{}.{}f}}".format(num,num)
- return "{:08.8f}".format(float(amount)/pow(10,decimal_point))
+ except Exception as e:
+ pass
+ return str(locktime)
-def is_perc(value):
+ def str_to_locktime(locktime):
+ try:
+ if locktime[-1] in ('y','d','b'):
+ return locktime
+ else: return int(locktime)
+ except Exception as e:
+ pass
+ dt_object = datetime.fromisoformat(locktime)
+ timestamp = dt_object.timestamp()
+ return int(timestamp)
+
+ def parse_locktime_string(locktime,w=None):
+ try:
+ return int(locktime)
+
+ except Exception as e:
+ pass
+ try:
+ now = datetime.now()
+ if locktime[-1] == 'y':
+ locktime = str(int(locktime[:-1])*365) + "d"
+ if locktime[-1] == 'd':
+ return int((now + timedelta(days = int(locktime[:-1]))).replace(hour=0,minute=0,second=0,microsecond=0).timestamp())
+ if locktime[-1] == 'b':
+ locktime = int(locktime[:-1])
+ height = 0
+ if w:
+ height = Util.get_current_height(w.network)
+ locktime+=int(height)
+ return int(locktime)
+ except Exception as e:
+ pass
+ return 0
+
+
+ def int_locktime(seconds=0,minutes=0,hours=0, days=0, blocks = 0):
+ return int(seconds + minutes*60 + hours*60*60 + days*60*60*24 + blocks * 600)
+
+ def encode_amount(amount, decimal_point):
+ if Util.is_perc(amount):
+ return amount
+ else:
+ try:
+ return int(float(amount)*pow(10,decimal_point))
+ except:
+ return 0
+
+ def decode_amount(amount,decimal_point):
+ if Util.is_perc(amount):
+ return amount
+ else:
+ num=8-decimal_point
+ basestr="{{:0{}.{}f}}".format(num,num)
+ return "{:08.8f}".format(float(amount)/pow(10,decimal_point))
+
+ def is_perc(value):
try:
return value[-1] == '%'
except:
return False
-def cmp_array(heira,heirb):
- try:
- if not len(heira) == len(heirb):
- return False
- for h in range(0,len(heira)):
- if not heira[h] == heirb[h]:
+ def cmp_array(heira,heirb):
+ try:
+ if not len(heira) == len(heirb):
return False
- return True
- except:
- return False
-
-def cmp_heir(heira,heirb):
- if heira[0] == heirb[0] and heira[1] == heirb[1]:
- return True
- return False
-
-def cmp_willexecutor(willexecutora,willexecutorb):
- if willexecutora == willexecutorb:
- return True
- try:
- if willexecutora['url']==willexecutorb['url'] and willexecutora['address'] == willexecutorb['address'] and willexecutora['base_fee']==willexecutorb['base_fee']:
+ for h in range(0,len(heira)):
+ if not heira[h] == heirb[h]:
+ return False
return True
- except:
- return False
- return False
-
-def search_heir_by_values(heirs,heir,values):
- #print()
- for h,v in heirs.items():
- found = False
- for val in values:
- if val in v and v[val] != heir[val]:
- found = True
-
- if not found:
- return h
- return False
-
-def cmp_heir_by_values(heira,heirb,values):
- for v in values:
- if heira[v] != heirb[v]:
+ except:
return False
- return True
-def cmp_heirs_by_values(heirsa,heirsb,values,exclude_willexecutors=False,reverse = True):
- for heira in heirsa:
- if (exclude_willexecutors and not "w!ll3x3c\"" in heira) or not exclude_willexecutors:
+ def cmp_heir(heira,heirb):
+ if heira[0] == heirb[0] and heira[1] == heirb[1]:
+ return True
+ return False
+
+ def cmp_willexecutor(willexecutora,willexecutorb):
+ if willexecutora == willexecutorb:
+ return True
+ try:
+ if willexecutora['url']==willexecutorb['url'] and willexecutora['address'] == willexecutorb['address'] and willexecutora['base_fee']==willexecutorb['base_fee']:
+ return True
+ except:
+ return False
+ return False
+
+ def search_heir_by_values(heirs,heir,values):
+ for h,v in heirs.items():
found = False
- for heirb in heirsb:
- if cmp_heir_by_values(heirsa[heira],heirsb[heirb],values):
- found=True
+ for val in values:
+ if val in v and v[val] != heir[val]:
+ found = True
+
if not found:
- #print(f"not_found {heira}--{heirsa[heira]}")
+ return h
+ return False
+
+ def cmp_heir_by_values(heira,heirb,values):
+ for v in values:
+ if heira[v] != heirb[v]:
return False
- if reverse:
- return cmp_heirs_by_values(heirsb,heirsa,values,exclude_willexecutors=exclude_willexecutors,reverse=False)
- else:
return True
-
-def cmp_heirs(heirsa,heirsb,cmp_function = lambda x,y: x[0]==y[0] and x[3]==y[3],reverse=True):
- try:
- for heir in heirsa:
- if not "w!ll3x3c\"" in heir:
- if not heir in heirsb or not cmp_function(heirsa[heir],heirsb[heir]):
- if not search_heir_by_values(heirsb,heirsa[heir],[0,3]):
- return False
+
+ def cmp_heirs_by_values(heirsa,heirsb,values,exclude_willexecutors=False,reverse = True):
+ for heira in heirsa:
+ if (exclude_willexecutors and not "w!ll3x3c\"" in heira) or not exclude_willexecutors:
+ found = False
+ for heirb in heirsb:
+ if Util.cmp_heir_by_values(heirsa[heira],heirsb[heirb],values):
+ found=True
+ if not found:
+ return False
if reverse:
- return cmp_heirs(heirsb,heirsa,cmp_function,False)
+ return Util.cmp_heirs_by_values(heirsb,heirsa,values,exclude_willexecutors=exclude_willexecutors,reverse=False)
else:
return True
- except Exception as e:
- raise e
- return False
-
-def cmp_inputs(inputsa,inputsb):
- if len(inputsa) != len(inputsb):
- return False
- for inputa in inputsa:
- if not in_utxo(inputa,inputsb):
- return False
- return True
-
-def cmp_outputs(outputsa,outputsb,willexecutor_output = None):
- if len(outputsa) != len(outputsb):
- return False
- for outputa in outputsa:
- if not cmp_output(outputa,willexecutor_output):
- if not in_output(outputa,outputsb):
- return False
- return True
-
-def cmp_txs(txa,txb):
- if not cmp_inputs(txa.inputs(),txb.inputs()):
- return False
- if not cmp_outputs(txa.outputs(),txb.outputs()):
- return False
- return True
-
-def get_value_amount(txa,txb):
- outputsa=txa.outputs()
- outputsb=txb.outputs()
- value_amount = 0
- #if len(outputsa) != len(outputsb):
- # print("outputlen is different")
- # return False
-
- for outa in outputsa:
- same_amount,same_address = in_output(outa,txb.outputs())
- if not (same_amount or same_address):
- #print("outa notin txb", same_amount,same_address)
- return False
- if same_amount and same_address:
- value_amount+=outa.value
- if same_amount:
- pass
- #print("same amount")
- if same_address:
- pass
- #print("same address")
-
- return value_amount
- #not needed
- #for outb in outputsb:
- # if not in_output(outb,txa.outputs()):
- # print("outb notin txb")
- # return False
-
-
-
-def chk_locktime(timestamp_to_check,block_height_to_check,locktime):
- #TODO BUG: WHAT HAPPEN AT THRESHOLD?
- locktime=int(locktime)
- if locktime > LOCKTIME_THRESHOLD and locktime > timestamp_to_check:
- return True
- elif locktime < LOCKTIME_THRESHOLD and locktime > block_height_to_check:
- return True
- else:
- return False
-
-def anticipate_locktime(locktime,blocks=0,hours=0,days=0):
- locktime = int(locktime)
- out=0
- if locktime> LOCKTIME_THRESHOLD:
- seconds = blocks*600 + hours*3600 + days*86400
- dt = datetime.fromtimestamp(locktime)
- dt -= timedelta(seconds=seconds)
- out = dt.timestamp()
- else:
- blocks -= hours*6 + days*144
- out = locktime + blocks
-
- if out < 1:
- out = 1
- return out
-
-def cmp_locktime(locktimea,locktimeb):
- if locktimea==locktimeb:
- return 0
- strlocktime = str(locktimea)
- strlocktimeb = str(locktimeb)
- intlocktimea = str_to_locktime(strlocktimea)
- intlocktimeb = str_to_locktime(strlocktimeb)
- if locktimea[-1] in "ydb":
- if locktimeb[-1] == locktimea[-1]:
- return int(strlocktimea[-1])-int(strlocktimeb[-1])
- else:
- return int(locktimea)-(locktimeb)
-
-
-def get_lowest_valid_tx(available_utxos,will):
- will = sorted(will.items(),key = lambda x: x[1]['tx'].locktime)
- for txid,willitem in will.items():
- pass
-
-def get_locktimes(will):
- locktimes = {}
- for txid,willitem in will.items():
- locktimes[willitem['tx'].locktime]=True
- return locktimes.keys()
-
-def get_lowest_locktimes(locktimes):
- sorted_timestamp=[]
- sorted_block=[]
- for l in locktimes:
- #print("locktime:",parse_locktime_string(l))
- l=parse_locktime_string(l)
- if l < LOCKTIME_THRESHOLD:
- bisect.insort(sorted_block,l)
- else:
- bisect.insort(sorted_timestamp,l)
-
- return sorted(sorted_timestamp), sorted(sorted_block)
-
-def get_lowest_locktimes_from_will(will):
- return get_lowest_locktimes(get_locktimes(will))
-
-def search_willtx_per_io(will,tx):
- for wid, w in will.items():
- if cmp_txs(w['tx'],tx['tx']):
- return wid,w
- return None, None
-
-def invalidate_will(will):
- raise Exception("not implemented")
-
-def get_will_spent_utxos(will):
- utxos=[]
- for txid,willitem in will.items():
- utxos+=willitem['tx'].inputs()
- return utxos
-
-def utxo_to_str(utxo):
- try: return utxo.to_str()
- except Exception as e: pass
- try: return utxo.prevout.to_str()
- except Exception as e: pass
- return str(utxo)
-
-def cmp_utxo(utxoa,utxob):
- utxoa=utxo_to_str(utxoa)
- utxob=utxo_to_str(utxob)
- if utxoa == utxob:
- #if utxoa.prevout.txid==utxob.prevout.txid and utxoa.prevout.out_idx == utxob.prevout.out_idx:
- return True
- else:
- return False
-
-def in_utxo(utxo, utxos):
- for s_u in utxos:
- if cmp_utxo(s_u,utxo):
- return True
- return False
-
-def txid_in_utxo(txid,utxos):
- for s_u in utxos:
- if s_u.prevout.txid == txid:
- return True
- return False
-
-def cmp_output(outputa,outputb):
- return outputa.address == outputb.address and outputa.value == outputb.value
-
-def in_output(output,outputs):
- for s_o in outputs:
- if cmp_output(s_o,output):
- return True
- return False
-
-#check all output with the same amount if none have the same address it can be a change
-#return true true same address same amount
-#return true false same amount different address
-#return false false different amount, different address not found
-
-
-def din_output(out,outputs):
- same_amount=[]
- for s_o in outputs:
- if int(out.value) == int(s_o.value):
- same_amount.append(s_o)
- if out.address==s_o.address:
- #print("SAME_:",out.address,s_o.address)
- return True, True
+ def cmp_heirs(heirsa,heirsb,cmp_function = lambda x,y: x[0]==y[0] and x[3]==y[3],reverse=True):
+ try:
+ for heir in heirsa:
+ if not "w!ll3x3c\"" in heir:
+ if not heir in heirsb or not cmp_function(heirsa[heir],heirsb[heir]):
+ if not Util.search_heir_by_values(heirsb,heirsa[heir],[0,3]):
+ return False
+ if reverse:
+ return Util.cmp_heirs(heirsb,heirsa,cmp_function,False)
else:
+ return True
+ except Exception as e:
+ raise e
+ return False
+
+ def cmp_inputs(inputsa,inputsb):
+ if len(inputsa) != len(inputsb):
+ return False
+ for inputa in inputsa:
+ if not Util.in_utxo(inputa,inputsb):
+ return False
+ return True
+
+ def cmp_outputs(outputsa,outputsb,willexecutor_output = None):
+ if len(outputsa) != len(outputsb):
+ return False
+ for outputa in outputsa:
+ if not Util.cmp_output(outputa,willexecutor_output):
+ if not Util.in_output(outputa,outputsb):
+ return False
+ return True
+
+ def cmp_txs(txa,txb):
+ if not Util.cmp_inputs(txa.inputs(),txb.inputs()):
+ return False
+ if not Util.cmp_outputs(txa.outputs(),txb.outputs()):
+ return False
+ return True
+
+ def get_value_amount(txa,txb):
+ outputsa=txa.outputs()
+ outputsb=txb.outputs()
+ value_amount = 0
+
+ for outa in outputsa:
+ same_amount,same_address = Util.in_output(outa,txb.outputs())
+ if not (same_amount or same_address):
+ return False
+ if same_amount and same_address:
+ value_amount+=outa.value
+ if same_amount:
+ pass
+ if same_address:
pass
- #print("NOT SAME_:",out.address,s_o.address)
- if len(same_amount)>0:
- return True, False
- else:return False, False
+ return value_amount
-def get_change_output(wallet,in_amount,out_amount,fee):
- change_amount = int(in_amount - out_amount - fee)
- if change_amount > wallet.dust_threshold():
- change_addresses = wallet.get_change_addresses_for_new_transaction()
- out = PartialTxOutput.from_address_and_value(change_addresses[0], change_amount)
- out.is_change = True
+
+ def chk_locktime(timestamp_to_check,block_height_to_check,locktime):
+ #TODO BUG: WHAT HAPPEN AT THRESHOLD?
+ locktime=int(locktime)
+ if locktime > LOCKTIME_THRESHOLD and locktime > timestamp_to_check:
+ return True
+ elif locktime < LOCKTIME_THRESHOLD and locktime > block_height_to_check:
+ return True
+ else:
+ return False
+
+ def anticipate_locktime(locktime,blocks=0,hours=0,days=0):
+ locktime = int(locktime)
+ out=0
+ if locktime> LOCKTIME_THRESHOLD:
+ seconds = blocks*600 + hours*3600 + days*86400
+ dt = datetime.fromtimestamp(locktime)
+ dt -= timedelta(seconds=seconds)
+ out = dt.timestamp()
+ else:
+ blocks -= hours*6 + days*144
+ out = locktime + blocks
+
+ if out < 1:
+ out = 1
return out
+ def cmp_locktime(locktimea,locktimeb):
+ if locktimea==locktimeb:
+ return 0
+ strlocktime = str(locktimea)
+ strlocktimeb = str(locktimeb)
+ intlocktimea = Util.str_to_locktime(strlocktimea)
+ intlocktimeb = Util.str_to_locktime(strlocktimeb)
+ if locktimea[-1] in "ydb":
+ if locktimeb[-1] == locktimea[-1]:
+ return int(strlocktimea[-1])-int(strlocktimeb[-1])
+ else:
+ return int(locktimea)-(locktimeb)
+
-def get_current_height(network:'Network'):
- #if no network or not up to date, just set locktime to zero
- if not network:
- return 0
- chain = network.blockchain()
- if chain.is_tip_stale():
- return 0
- # figure out current block height
- chain_height = chain.height() # learnt from all connected servers, SPV-checked
- server_height = network.get_server_height() # height claimed by main server, unverified
- # note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork)
- # - if it's lagging too much, it is the network's job to switch away
- if server_height < chain_height - 10:
- # the diff is suspiciously large... give up and use something non-fingerprintable
- return 0
- # discourage "fee sniping"
- height = min(chain_height, server_height)
- return height
+ def get_lowest_valid_tx(available_utxos,will):
+ will = sorted(will.items(),key = lambda x: x[1]['tx'].locktime)
+ for txid,willitem in will.items():
+ pass
+
+ def get_locktimes(will):
+ locktimes = {}
+ for txid,willitem in will.items():
+ locktimes[willitem['tx'].locktime]=True
+ return locktimes.keys()
+
+ def get_lowest_locktimes(locktimes):
+ sorted_timestamp=[]
+ sorted_block=[]
+ for l in locktimes:
+ l=Util.parse_locktime_string(l)
+ if l < LOCKTIME_THRESHOLD:
+ bisect.insort(sorted_block,l)
+ else:
+ bisect.insort(sorted_timestamp,l)
+
+ return sorted(sorted_timestamp), sorted(sorted_block)
+
+ def get_lowest_locktimes_from_will(will):
+ return Util.get_lowest_locktimes(Util.get_locktimes(will))
+
+ def search_willtx_per_io(will,tx):
+ for wid, w in will.items():
+ if Util.cmp_txs(w['tx'],tx['tx']):
+ return wid,w
+ return None, None
+
+ def invalidate_will(will):
+ raise Exception("not implemented")
+
+ def get_will_spent_utxos(will):
+ utxos=[]
+ for txid,willitem in will.items():
+ utxos+=willitem['tx'].inputs()
+
+ return utxos
+
+ def utxo_to_str(utxo):
+ try: return utxo.to_str()
+ except Exception as e: pass
+ try: return utxo.prevout.to_str()
+ except Exception as e: pass
+ return str(utxo)
+
+ def cmp_utxo(utxoa,utxob):
+ utxoa=Util.utxo_to_str(utxoa)
+ utxob=Util.utxo_to_str(utxob)
+ if utxoa == utxob:
+ return True
+ else:
+ return False
+
+ def in_utxo(utxo, utxos):
+ for s_u in utxos:
+ if Util.cmp_utxo(s_u,utxo):
+ return True
+ return False
+
+ def txid_in_utxo(txid,utxos):
+ for s_u in utxos:
+ if s_u.prevout.txid == txid:
+ return True
+ return False
+
+ def cmp_output(outputa,outputb):
+ return outputa.address == outputb.address and outputa.value == outputb.value
+
+ def in_output(output,outputs):
+ for s_o in outputs:
+ if Util.cmp_output(s_o,output):
+ return True
+ return False
+
+ #check all output with the same amount if none have the same address it can be a change
+ #return true true same address same amount
+ #return true false same amount different address
+ #return false false different amount, different address not found
-def print_var(var,name = "",veryverbose=False):
- print(f"---{name}---")
- if not var is None:
+ def din_output(out,outputs):
+ same_amount=[]
+ for s_o in outputs:
+ if int(out.value) == int(s_o.value):
+ same_amount.append(s_o)
+ if out.address==s_o.address:
+ return True, True
+ else:
+ pass
+
+ if len(same_amount)>0:
+ return True, False
+ else:return False, False
+
+
+ def get_change_output(wallet,in_amount,out_amount,fee):
+ change_amount = int(in_amount - out_amount - fee)
+ if change_amount > wallet.dust_threshold():
+ change_addresses = wallet.get_change_addresses_for_new_transaction()
+ out = PartialTxOutput.from_address_and_value(change_addresses[0], change_amount)
+ out.is_change = True
+ return out
+
+
+ def get_current_height(network:'Network'):
+ #if no network or not up to date, just set locktime to zero
+ if not network:
+ return 0
+ chain = network.blockchain()
+ if chain.is_tip_stale():
+ return 0
+ # figure out current block height
+ chain_height = chain.height() # learnt from all connected servers, SPV-checked
+ server_height = network.get_server_height() # height claimed by main server, unverified
+ # note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork)
+ # - if it's lagging too much, it is the network's job to switch away
+ if server_height < chain_height - 10:
+ # the diff is suspiciously large... give up and use something non-fingerprintable
+ return 0
+ # discourage "fee sniping"
+ height = min(chain_height, server_height)
+ return height
+
+
+ def print_var(var,name = "",veryverbose=False):
+ print(f"---{name}---")
+ if not var is None:
+ try:
+ print("doc:",doc(var))
+ except: pass
+ try:
+ print("str:",str(var))
+ except: pass
+ try:
+ print("repr",repr(var))
+ except:pass
+ try:
+ print("dict",dict(var))
+ except:pass
+ try:
+ print("dir",dir(var))
+ except:pass
+ try:
+ print("type",type(var))
+ except:pass
+ try:
+ print("to_json",var.to_json())
+ except: pass
+ try:
+ print("__slotnames__",var.__slotnames__)
+ except:pass
+
+ print(f"---end {name}---")
+
+ def print_utxo(utxo, name = ""):
+ print(f"---utxo-{name}---")
+ Util.print_var(utxo,name)
+ Util.print_prevout(utxo.prevout,name)
+ Util.print_var(utxo.script_sig,f"{name}-script-sig")
+ Util.print_var(utxo.witness,f"{name}-witness")
+ print("_TxInput__address:",utxo._TxInput__address)
+ print("_TxInput__scriptpubkey:",utxo._TxInput__scriptpubkey)
+ print("_TxInput__value_sats:",utxo._TxInput__value_sats)
+ print(f"---utxo-end {name}---")
+
+ def print_prevout(prevout, name = ""):
+ print(f"---prevout-{name}---")
+ Util.print_var(prevout,f"{name}-prevout")
+ Util.print_var(prevout._asdict())
+ print(f"---prevout-end {name}---")
+
+ def export_meta_gui(electrum_window: 'ElectrumWindow', title, exporter):
+ filter_ = "All files (*)"
+ filename = getSaveFileName(
+ parent=electrum_window,
+ title=_("Select file to save your {}").format(title),
+ filename='BALplugin_{}'.format(title),
+ filter=filter_,
+ config=electrum_window.config,
+ )
+ if not filename:
+ return
try:
- print("doc:",doc(var))
- except: pass
- try:
- print("str:",str(var))
- except: pass
- try:
- print("repr",repr(var))
- except:pass
- try:
- print("dict",dict(var))
- except:pass
- try:
- print("dir",dir(var))
- except:pass
- try:
- print("type",type(var))
- except:pass
- try:
- print("to_json",var.to_json())
- except: pass
- try:
- print("__slotnames__",var.__slotnames__)
- except:pass
-
- print(f"---end {name}---")
-
-def print_utxo(utxo, name = ""):
- print(f"---utxo-{name}---")
- print_var(utxo,name)
- print_prevout(utxo.prevout,name)
- print_var(utxo.script_sig,f"{name}-script-sig")
- print_var(utxo.witness,f"{name}-witness")
- #print("madonnamaiala_TXInput__scriptpubkey:",utxo._TXInput__scriptpubkey)
- print("_TxInput__address:",utxo._TxInput__address)
- print("_TxInput__scriptpubkey:",utxo._TxInput__scriptpubkey)
- print("_TxInput__value_sats:",utxo._TxInput__value_sats)
- print(f"---utxo-end {name}---")
-
-def print_prevout(prevout, name = ""):
- print(f"---prevout-{name}---")
- print_var(prevout,f"{name}-prevout")
- print_var(prevout._asdict())
- print(f"---prevout-end {name}---")
-
-def export_meta_gui(electrum_window: 'ElectrumWindow', title, exporter):
- filter_ = "All files (*)"
- filename = getSaveFileName(
- parent=electrum_window,
- title=_("Select file to save your {}").format(title),
- filename='BALplugin_{}'.format(title),
- filter=filter_,
- config=electrum_window.config,
- )
- if not filename:
- return
- try:
- exporter(filename)
- except FileExportFailed as e:
- electrum_window.show_critical(str(e))
- else:
- electrum_window.show_message(_("Your {0} were exported to '{1}'")
- .format(title, str(filename)))
+ exporter(filename)
+ except FileExportFailed as e:
+ electrum_window.show_critical(str(e))
+ else:
+ electrum_window.show_message(_("Your {0} were exported to '{1}'")
+ .format(title, str(filename)))
-def copy(dicto,dictfrom):
- for k,v in dictfrom.items():
- dicto[k]=v
+ def copy(dicto,dictfrom):
+ for k,v in dictfrom.items():
+ dicto[k]=v
diff --git a/will.py b/will.py
index b651806..1cfdea8 100644
--- a/will.py
+++ b/will.py
@@ -1,7 +1,7 @@
import copy
-from . import willexecutors as Willexecutors
-from . import util as Util
+from .willexecutors import Willexecutors
+from .util import Util
from electrum.i18n import _
@@ -16,579 +16,551 @@ MIN_BLOCK = 1
_logger = get_logger(__name__)
#return an array with the list of children
-def get_children(will,willid):
- out = []
- for _id in will:
- inputs = will[_id].tx.inputs()
- for idi in range(0,len(inputs)):
- _input = inputs[idi]
- if _input.prevout.txid.hex() == willid:
- out.append([_id,idi,_input.prevout.out_idx])
- return out
+class Will:
+ def get_children(will,willid):
+ out = []
+ for _id in will:
+ inputs = will[_id].tx.inputs()
+ for idi in range(0,len(inputs)):
+ _input = inputs[idi]
+ if _input.prevout.txid.hex() == willid:
+ out.append([_id,idi,_input.prevout.out_idx])
+ return out
-#build a tree with parent transactions
-def add_willtree(will):
- for willid in will:
- will[willid].children = get_children(will,willid)
- for child in will[willid].children:
- if not will[child[0]].father:
- will[child[0]].father = willid
+ #build a tree with parent transactions
+ def add_willtree(will):
+ for willid in will:
+ will[willid].children = Will.get_children(will,willid)
+ for child in will[willid].children:
+ if not will[child[0]].father:
+ will[child[0]].father = willid
-#return a list of will sorted by locktime
-def get_sorted_will(will):
- return sorted(will.items(),key = lambda x: x[1]['tx'].locktime)
-
+ #return a list of will sorted by locktime
+ def get_sorted_will(will):
+ return sorted(will.items(),key = lambda x: x[1]['tx'].locktime)
+
-def only_valid(will):
- for k,v in will.items():
- if v.get_status('VALID'):
- yield k
+ def only_valid(will):
+ for k,v in will.items():
+ if v.get_status('VALID'):
+ yield k
-def search_equal_tx(will,tx,wid):
- for w in will:
- if w != wid and not tx.to_json() != will[w]['tx'].to_json():
- if will[w]['tx'].txid() != tx.txid():
- if Util.cmp_txs(will[w]['tx'],tx):
- return will[w]['tx']
- return False
+ def search_equal_tx(will,tx,wid):
+ for w in will:
+ if w != wid and not tx.to_json() != will[w]['tx'].to_json():
+ if will[w]['tx'].txid() != tx.txid():
+ if Util.cmp_txs(will[w]['tx'],tx):
+ return will[w]['tx']
+ return False
-def get_tx_from_any(x):
- try:
- a=str(x)
- return tx_from_any(a)
-
- except Exception as e:
- raise e
-
- return x
-
-def add_info_from_will(will,wid,wallet):
- if isinstance(will[wid].tx,str):
- will[wid].tx = get_tx_from_any(will[wid].tx)
- if wallet:
- will[wid].tx.add_info_from_wallet(wallet)
- for txin in will[wid].tx.inputs():
- txid = txin.prevout.txid.hex()
- if txid in will:
- change = will[txid].tx.outputs()[txin.prevout.out_idx]
- txin._trusted_value_sats = change.value
- try:
- txin.script_descriptor = change.script_descriptor
- except:
- pass
- txin.is_mine=True
- txin._TxInput__address=change.address
- txin._TxInput__scriptpubkey = change.scriptpubkey
- txin._TxInput__value_sats = change.value
- txin._trusted_value_sats = change.value
-
-def normalize_will(will,wallet = None,others_inputs = {}):
- to_delete = []
- to_add = {}
- #add info from wallet
- for wid in will:
- add_info_from_will(will,wid,wallet)
- errors ={}
- for wid in will:
-
- txid = will[wid].tx.txid()
-
- if txid is None:
- _logger.error("##########")
- _logger.error(wid)
- _logger.error(will[wid])
- _logger.error(will[wid].tx.to_json())
+ def get_tx_from_any(x):
+ try:
+ a=str(x)
+ return tx_from_any(a)
- _logger.error("txid is none")
- will[wid].set_status('ERROR',True)
- errors[wid]=will[wid]
- continue
+ except Exception as e:
+ raise e
- if txid != wid:
- outputs = will[wid].tx.outputs()
- ow=will[wid]
- ow.normalize_locktime(others_inputs)
- will[wid]=ow.to_dict()
+ return x
- for i in range(0,len(outputs)):
- change_input(will,wid,i,outputs[i],others_inputs,to_delete,to_add)
+ def add_info_from_will(will,wid,wallet):
+ if isinstance(will[wid].tx,str):
+ will[wid].tx = Will.get_tx_from_any(will[wid].tx)
+ if wallet:
+ will[wid].tx.add_info_from_wallet(wallet)
+ for txin in will[wid].tx.inputs():
+ txid = txin.prevout.txid.hex()
+ if txid in will:
+ change = will[txid].tx.outputs()[txin.prevout.out_idx]
+ txin._trusted_value_sats = change.value
+ try:
+ txin.script_descriptor = change.script_descriptor
+ except:
+ pass
+ txin.is_mine=True
+ txin._TxInput__address=change.address
+ txin._TxInput__scriptpubkey = change.scriptpubkey
+ txin._TxInput__value_sats = change.value
+ txin._trusted_value_sats = change.value
- to_delete.append(wid)
- to_add[ow.tx.txid()]=ow.to_dict()
+ def normalize_will(will,wallet = None,others_inputs = {}):
+ to_delete = []
+ to_add = {}
+ #add info from wallet
+ for wid in will:
+ Will.add_info_from_will(will,wid,wallet)
+ errors ={}
+ for wid in will:
- for eid,err in errors.items():
- new_txid = err.tx.txid()
+ txid = will[wid].tx.txid()
- for k,w in to_add.items():
- will[k] = w
+ if txid is None:
+ _logger.error("##########")
+ _logger.error(wid)
+ _logger.error(will[wid])
+ _logger.error(will[wid].tx.to_json())
+
+ _logger.error("txid is none")
+ will[wid].set_status('ERROR',True)
+ errors[wid]=will[wid]
+ continue
- for wid in to_delete:
- if wid in will:
- del will[wid]
+ if txid != wid:
+ outputs = will[wid].tx.outputs()
+ ow=will[wid]
+ ow.normalize_locktime(others_inputs)
+ will[wid]=ow.to_dict()
-def new_input(txid,idx,change):
- prevout = TxOutpoint(txid=bfh(txid), out_idx=idx)
- inp = PartialTxInput(prevout=prevout)
- inp._trusted_value_sats = change.value
- inp.is_mine=True
- inp._TxInput__address=change.address
- inp._TxInput__scriptpubkey = change.scriptpubkey
- inp._TxInput__value_sats = change.value
- return inp
+ for i in range(0,len(outputs)):
+ Will.change_input(will,wid,i,outputs[i],others_inputs,to_delete,to_add)
-def check_anticipate(ow:'WillItem',nw:'WillItem'):
- anticipate = Util.anticipate_locktime(ow.tx.locktime,days=1)
- if int(nw.tx.locktime) >= int(anticipate):
- if Util.cmp_heirs_by_values(ow.heirs,nw.heirs,[0,1],exclude_willexecutors = True):
- print("same heirs",ow._id,nw._id)
- if nw.we and ow.we:
- if ow.we['url'] == nw.we['url']:
- print("same willexecutors", ow.we['url'],nw.we['url'])
- if int(ow.we['base_fee'])>int(nw.we['base_fee']):
- print("anticipate")
- return anticipate
- else:
- if int(ow.tx_fees) != int(nw.tx_fees):
+ to_delete.append(wid)
+ to_add[ow.tx.txid()]=ow.to_dict()
+
+ for eid,err in errors.items():
+ new_txid = err.tx.txid()
+
+ for k,w in to_add.items():
+ will[k] = w
+
+ for wid in to_delete:
+ if wid in will:
+ del will[wid]
+
+ def new_input(txid,idx,change):
+ prevout = TxOutpoint(txid=bfh(txid), out_idx=idx)
+ inp = PartialTxInput(prevout=prevout)
+ inp._trusted_value_sats = change.value
+ inp.is_mine=True
+ inp._TxInput__address=change.address
+ inp._TxInput__scriptpubkey = change.scriptpubkey
+ inp._TxInput__value_sats = change.value
+ return inp
+
+ def check_anticipate(ow:'WillItem',nw:'WillItem'):
+ anticipate = Util.anticipate_locktime(ow.tx.locktime,days=1)
+ if int(nw.tx.locktime) >= int(anticipate):
+ if Util.cmp_heirs_by_values(ow.heirs,nw.heirs,[0,1],exclude_willexecutors = True):
+ if nw.we and ow.we:
+ if ow.we['url'] == nw.we['url']:
+ if int(ow.we['base_fee'])>int(nw.we['base_fee']):
return anticipate
else:
- print("keep the same")
- #_logger.debug("ow,base fee > nw.base_fee")
- ow.tx.locktime
+ if int(ow.tx_fees) != int(nw.tx_fees):
+ return anticipate
+ else:
+ ow.tx.locktime
+ else:
+ ow.tx.locktime
else:
- #_logger.debug("ow.we['url']({ow.we['url']}) == nw.we['url']({nw.we['url']})")
- print("keep the same")
- ow.tx.locktime
- else:
- if nw.we == ow.we:
- if not Util.cmp_heirs_by_values(ow.heirs,nw.heirs,[0,3]):
- return anticipate
+ if nw.we == ow.we:
+ if not Util.cmp_heirs_by_values(ow.heirs,nw.heirs,[0,3]):
+ return anticipate
+ else:
+ return ow.tx.locktime
else:
return ow.tx.locktime
- else:
- return ow.tx.locktime
- else:
- return anticipate
- return 4294967295+1
-
-
-def change_input(will, otxid, idx, change,others_inputs,to_delete,to_append):
- ow = will[otxid]
- ntxid = ow.tx.txid()
- if otxid != ntxid:
- for wid in will:
- w = will[wid]
- inputs = w.tx.inputs()
- outputs = w.tx.outputs()
- found = False
- old_txid = w.tx.txid()
- ntx = None
- for i in range(0,len(inputs)):
- if inputs[i].prevout.txid.hex() == otxid and inputs[i].prevout.out_idx == idx:
- if isinstance(w.tx,Transaction):
- will[wid].tx = PartialTransaction.from_tx(w.tx)
- will[wid].tx.set_rbf(True)
- will[wid].tx._inputs[i]=new_input(wid,idx,change)
- found = True
- if found == True:
- pass
-
- new_txid = will[wid].tx.txid()
- if old_txid != new_txid:
- to_delete.append(old_txid)
- to_append[new_txid]=will[wid]
- outputs = will[wid].tx.outputs()
- for i in range(0,len(outputs)):
- change_input(will, wid, i, outputs[i],others_inputs,to_delete,to_append)
-
-def get_all_inputs(will,only_valid = False):
- all_inputs = {}
- for w,wi in will.items():
- if not only_valid or wi.get_status('VALID'):
- inputs = wi.tx.inputs()
- for i in inputs:
- prevout_str = i.prevout.to_str()
- inp=[w,will[w],i]
- if not prevout_str in all_inputs:
- all_inputs[prevout_str] = [inp]
- else:
- all_inputs[prevout_str].append(inp)
- return all_inputs
-
-def get_all_inputs_min_locktime(all_inputs):
- all_inputs_min_locktime = {}
-
- for i,values in all_inputs.items():
- min_locktime = min(values,key = lambda x:x[1].tx.locktime)[1].tx.locktime
- for w in values:
- if w[1].tx.locktime == min_locktime:
- if not i in all_inputs_min_locktime:
- all_inputs_min_locktime[i]=[w]
- else:
- all_inputs_min_locktime[i].append(w)
-
- return all_inputs_min_locktime
-
-
-def search_anticipate_rec(will,old_inputs):
- redo = False
- to_delete = []
- to_append = {}
- new_inputs = get_all_inputs(will,only_valid = True)
- for nid,nwi in will.items():
- if nwi.search_anticipate(new_inputs) or nwi.search_anticipate(old_inputs):
- if nid != nwi.tx.txid():
- redo = True
- to_delete.append(nid)
- to_append[nwi.tx.txid()] = nwi
- outputs = nwi.tx.outputs()
- for i in range(0,len(outputs)):
- change_input(will,nid,i,outputs[i],new_inputs,to_delete,to_append)
-
-
- for w in to_delete:
- try:
- del will[w]
- except:
- pass
- for k,w in to_append.items():
- will[k]=w
- if redo:
- search_anticipate_rec(will,old_inputs)
-
-
-def update_will(old_will,new_will):
- all_old_inputs = get_all_inputs(old_will,only_valid=True)
- all_inputs_min_locktime = get_all_inputs_min_locktime(all_old_inputs)
- all_new_inputs = get_all_inputs(new_will)
- #check if the new input is already spent by other transaction
- #if it is use the same locktime, or anticipate.
- search_anticipate_rec(new_will,all_old_inputs)
-
- other_inputs = get_all_inputs(old_will,{})
- try:
- normalize_will(new_will,others_inputs=other_inputs)
- except Exception as e:
- raise e
-
-
- for oid in only_valid(old_will):
- if oid in new_will:
- new_heirs = new_will[oid].heirs
- new_we = new_will[oid].we
-
- new_will[oid]=old_will[oid]
- new_will[oid].heirs = new_heirs
- new_will[oid].we = new_we
- print(f"found {oid}")
-
- continue
- else:
- print(f"not found {oid}")
- continue
-
-def get_higher_input_for_tx(will):
- out = {}
- for wid in will:
- wtx = will[wid].tx
- found = False
- for inp in wtx.inputs():
- if inp.prevout.txid.hex() in will:
- found = True
- break
- if not found:
- out[inp.prevout.to_str()] = inp
- return out
-
-def invalidate_will(will,wallet,fees_per_byte):
- will_only_valid = only_valid_list(will)
- inputs = get_all_inputs(will_only_valid)
- utxos = wallet.get_utxos()
- filtered_inputs = []
- prevout_to_spend = []
- for prevout_str,ws in inputs.items():
- for w in ws:
- if not w[0] in filtered_inputs:
- filtered_inputs.append(w[0])
- if not prevout_str in prevout_to_spend:
- prevout_to_spend.append(prevout_str)
- balance = 0
- utxo_to_spend = []
- for utxo in utxos:
- utxo_str=utxo.prevout.to_str()
- if utxo_str in prevout_to_spend:
- balance += inputs[utxo_str][0][2].value_sats()
- utxo_to_spend.append(utxo)
-
- if len(utxo_to_spend) > 0:
- change_addresses = wallet.get_change_addresses_for_new_transaction()
- out = PartialTxOutput.from_address_and_value(change_addresses[0], balance)
- out.is_change = True
- locktime = Util.get_current_height(wallet.network)
- tx = PartialTransaction.from_io(utxo_to_spend, [out], locktime=locktime, version=2)
- tx.set_rbf(True)
- fee=tx.estimated_size()*fees_per_byte
- if balance -fee >0:
- out = PartialTxOutput.from_address_and_value(change_addresses[0],balance - fee)
- tx = PartialTransaction.from_io(utxo_to_spend,[out], locktime=locktime, version=2)
- tx.set_rbf(True)
-
- _logger.debug(f"invalidation tx: {tx}")
- return tx
-
- else:
- _logger.debug("balance - fee <=0")
- pass
- else:
- _logger.debug("len utxo_to_spend <=0")
- pass
-
-
-def is_new(will):
- for wid,w in will.items():
- if w.get_status('VALID') and not w.get_status('COMPLETE'):
- return True
-
-def search_rai (all_inputs,all_utxos,will,wallet):
- will_only_valid = only_valid_or_replaced_list(will)
- for inp,ws in all_inputs.items():
- inutxo = Util.in_utxo(inp,all_utxos)
- for w in ws:
- wi=w[1]
- if wi.get_status('VALID') or wi.get_status('CONFIRMED') or wi.get_status('PENDING'):
- prevout_id=w[2].prevout.txid.hex()
- if not inutxo:
- if prevout_id in will:
- wo=will[prevout_id]
- if wo.get_status('REPLACED'):
- wi.set_status('REPLACED',True)
- if wo.get_status("INVALIDATED"):
- wi.set_status('INVALIDATED',True)
-
- else:
- if wallet.db.get_transaction(wi._id):
- wi.set_status('CONFIRMED',True)
- else:
- wi.set_status('INVALIDATED',True)
- #else:
- # if prevout_id in will:
- # wo = will[prevout_id]
- # ttx= wallet.db.get_transaction(prevout_id)
- # if ttx:
- # _logger.error("transaction in wallet should be early detected")
- # #wi.set_status('CONFIRMED',True)
- # #else:
- # # _logger.error("transaction not in will or utxo")
- # # wi.set_status('INVALIDATED',True)
-
- for child in wi.search(all_inputs):
- if child.tx.locktime < wi.tx.locktime:
- _logger.debug("a child was found")
- wi.set_status('REPLACED',True)
else:
+ return anticipate
+ return 4294967295+1
+
+
+ def change_input(will, otxid, idx, change,others_inputs,to_delete,to_append):
+ ow = will[otxid]
+ ntxid = ow.tx.txid()
+ if otxid != ntxid:
+ for wid in will:
+ w = will[wid]
+ inputs = w.tx.inputs()
+ outputs = w.tx.outputs()
+ found = False
+ old_txid = w.tx.txid()
+ ntx = None
+ for i in range(0,len(inputs)):
+ if inputs[i].prevout.txid.hex() == otxid and inputs[i].prevout.out_idx == idx:
+ if isinstance(w.tx,Transaction):
+ will[wid].tx = PartialTransaction.from_tx(w.tx)
+ will[wid].tx.set_rbf(True)
+ will[wid].tx._inputs[i]=Will.new_input(wid,idx,change)
+ found = True
+ if found == True:
+ pass
+
+ new_txid = will[wid].tx.txid()
+ if old_txid != new_txid:
+ to_delete.append(old_txid)
+ to_append[new_txid]=will[wid]
+ outputs = will[wid].tx.outputs()
+ for i in range(0,len(outputs)):
+ Will.change_input(will, wid, i, outputs[i],others_inputs,to_delete,to_append)
+
+ def get_all_inputs(will,only_valid = False):
+ all_inputs = {}
+ for w,wi in will.items():
+ if not only_valid or wi.get_status('VALID'):
+ inputs = wi.tx.inputs()
+ for i in inputs:
+ prevout_str = i.prevout.to_str()
+ inp=[w,will[w],i]
+ if not prevout_str in all_inputs:
+ all_inputs[prevout_str] = [inp]
+ else:
+ all_inputs[prevout_str].append(inp)
+ return all_inputs
+
+ def get_all_inputs_min_locktime(all_inputs):
+ all_inputs_min_locktime = {}
+
+ for i,values in all_inputs.items():
+ min_locktime = min(values,key = lambda x:x[1].tx.locktime)[1].tx.locktime
+ for w in values:
+ if w[1].tx.locktime == min_locktime:
+ if not i in all_inputs_min_locktime:
+ all_inputs_min_locktime[i]=[w]
+ else:
+ all_inputs_min_locktime[i].append(w)
+
+ return all_inputs_min_locktime
+
+
+ def search_anticipate_rec(will,old_inputs):
+ redo = False
+ to_delete = []
+ to_append = {}
+ new_inputs = Will.get_all_inputs(will,only_valid = True)
+ for nid,nwi in will.items():
+ if nwi.search_anticipate(new_inputs) or nwi.search_anticipate(old_inputs):
+ if nid != nwi.tx.txid():
+ redo = True
+ to_delete.append(nid)
+ to_append[nwi.tx.txid()] = nwi
+ outputs = nwi.tx.outputs()
+ for i in range(0,len(outputs)):
+ Will.change_input(will,nid,i,outputs[i],new_inputs,to_delete,to_append)
+
+
+ for w in to_delete:
+ try:
+ del will[w]
+ except:
pass
-
-def utxos_strs(utxos):
- return [Util.utxo_to_str(u) for u in utxos]
+ for k,w in to_append.items():
+ will[k]=w
+ if redo:
+ Will.search_anticipate_rec(will,old_inputs)
-def set_invalidate(wid,will=[]):
- will[wid].set_status("INVALIDATED",True)
- if will[wid].children:
- for c in self.children.items():
- set_invalidate(c[0],will)
+ def update_will(old_will,new_will):
+ all_old_inputs = Will.get_all_inputs(old_will,only_valid=True)
+ all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs)
+ all_new_inputs = Will.get_all_inputs(new_will)
+ #check if the new input is already spent by other transaction
+ #if it is use the same locktime, or anticipate.
+ Will.search_anticipate_rec(new_will,all_old_inputs)
-def check_tx_height(tx, wallet):
- info=wallet.get_tx_info(tx)
- return info.tx_mined_status.height
+ other_inputs = Will.get_all_inputs(old_will,{})
+ try:
+ Will.normalize_will(new_will,others_inputs=other_inputs)
+ except Exception as e:
+ raise e
+
-#check if transactions are stil valid tecnically valid
-def check_invalidated(willtree,utxos_list,wallet):
- for wid,w in willtree.items():
- if not w.father:
- for inp in w.tx.inputs():
- inp_str = Util.utxo_to_str(inp)
- #print(utxos_list)
- #print(inp_str)
- #print(inp_str in utxos_list)
- #print("notin: ",not inp_str in utxos_list)
- if not inp_str in utxos_list:
- #print("quindi qua non ci arrivo?")
- if wallet:
- height= check_tx_height(w.tx,wallet)
+ for oid in Will.only_valid(old_will):
+ if oid in new_will:
+ new_heirs = new_will[oid].heirs
+ new_we = new_will[oid].we
- if height < 0:
- #_logger.debug(f"heigth {height}")
- set_invalidate(wid,willtree)
- elif height == 0:
- w.set_status("PENDING",True)
- else:
- w.set_status('CONFIRMED',True)
+ new_will[oid]=old_will[oid]
+ new_will[oid].heirs = new_heirs
+ new_will[oid].we = new_we
-def reflect_to_children(treeitem):
- if not treeitem.get_status("VALID"):
- _logger.debug(f"{tree:item._id} status not valid looking for children")
- for child in treeitem.children:
- wc = willtree[child]
- if wc.get_status("VALID"):
- if treeitem.get_status("INVALIDATED"):
- wc.set_status("INVALIDATED",True)
- if treeitem.get_status("REPLACED"):
- wc.set_status("REPLACED",True)
- if wc.children:
- reflect_to_children(wc)
+ continue
+ else:
+ continue
-def check_amounts(heirs,willexecutors,all_utxos,timestamp_to_check,dust):
- fixed_heirs,fixed_amount,perc_heirs,perc_amount = heirs.fixed_percent_lists_amount(timestamp_to_check,dust,reverse=True)
- wallet_balance = 0
- for utxo in all_utxos:
- wallet_balance += utxo.value_sats()
+ def get_higher_input_for_tx(will):
+ out = {}
+ for wid in will:
+ wtx = will[wid].tx
+ found = False
+ for inp in wtx.inputs():
+ if inp.prevout.txid.hex() in will:
+ found = True
+ break
+ if not found:
+ out[inp.prevout.to_str()] = inp
+ return out
- if fixed_amount >= wallet_balance:
- raise FixedAmountException(f"Fixed amount({fixed_amount}) >= {wallet_balance}")
- if perc_amount != 100:
- raise PercAmountException(f"Perc amount({perc_amount}) =! 100%")
-
- for url,wex in willexecutors.items():
- if Willexecutors.is_selected(wex):
- temp_balance = wallet_balance - int(wex['base_fee'])
- if fixed_amount >= temp_balance:
- raise FixedAmountException(f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}")
-
-
-def check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check):
- add_willtree(will)
- utxos_list= utxos_strs(all_utxos)
-
- check_invalidated(will,utxos_list,wallet)
- #from pprint import pprint
- #for wid,w in will.items():
- # pprint(w.to_dict())
-
- all_inputs=get_all_inputs(will,only_valid = True)
-
- all_inputs_min_locktime = get_all_inputs_min_locktime(all_inputs)
-
- check_will_expired(all_inputs_min_locktime,block_to_check,timestamp_to_check)
-
- all_inputs=get_all_inputs(will,only_valid = True)
-
- search_rai(all_inputs,all_utxos,will,wallet)
-
-def is_will_valid(will, block_to_check, timestamp_to_check, tx_fees, all_utxos,heirs={},willexecutors={},self_willexecutor=False, wallet=False, callback_not_valid_tx=None):
-
- check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check)
-
-
- if heirs:
- if not check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,timestamp_to_check,tx_fees):
- raise NotCompleteWillException()
-
-
- all_inputs=get_all_inputs(will,only_valid = True)
-
- _logger.info('check all utxo in wallet are spent')
- if all_inputs:
- for utxo in all_utxos:
- if utxo.value_sats() > 68 * tx_fees:
- if not Util.in_utxo(utxo,all_inputs.keys()):
- _logger.info("utxo is not spent",utxo.to_json())
- _logger.debug(all_inputs.keys())
- raise NotCompleteWillException("Some utxo in the wallet is not included")
-
- _logger.info('will ok')
- return True
-
-def check_will_expired(all_inputs_min_locktime,block_to_check,timestamp_to_check):
- _logger.info("check if some transaction is expired")
- for prevout_str, wid in all_inputs_min_locktime.items():
- for w in wid:
- if w[1].get_status('VALID'):
- locktime = int(wid[0][1].tx.locktime)
- if locktime <= NLOCKTIME_BLOCKHEIGHT_MAX:
- if locktime < int(block_to_check):
- raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{block_to_check}")
- else:
- if locktime < int(timestamp_to_check):
- raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}")
-
-def check_all_input_spent_are_in_wallet():
- _logger.info("check all input spent are in wallet or valid txs")
- for inp,ws in all_inputs.items():
- if not Util.in_utxo(inp,all_utxos):
+ def invalidate_will(will,wallet,fees_per_byte):
+ will_only_valid = Will.only_valid_list(will)
+ inputs = Will.get_all_inputs(will_only_valid)
+ utxos = wallet.get_utxos()
+ filtered_inputs = []
+ prevout_to_spend = []
+ for prevout_str,ws in inputs.items():
for w in ws:
- if w[1].get_status('VALID'):
- prevout_id = w[2].prevout.txid.hex()
- parentwill = will.get(prevout_id,False)
- if not parentwill or not parentwill.get_status('VALID'):
- w[1].set_status('INVALIDATED',True)
+ if not w[0] in filtered_inputs:
+ filtered_inputs.append(w[0])
+ if not prevout_str in prevout_to_spend:
+ prevout_to_spend.append(prevout_str)
+ balance = 0
+ utxo_to_spend = []
+ for utxo in utxos:
+ utxo_str=utxo.prevout.to_str()
+ if utxo_str in prevout_to_spend:
+ balance += inputs[utxo_str][0][2].value_sats()
+ utxo_to_spend.append(utxo)
+ if len(utxo_to_spend) > 0:
+ change_addresses = wallet.get_change_addresses_for_new_transaction()
+ out = PartialTxOutput.from_address_and_value(change_addresses[0], balance)
+ out.is_change = True
+ locktime = Util.get_current_height(wallet.network)
+ tx = PartialTransaction.from_io(utxo_to_spend, [out], locktime=locktime, version=2)
+ tx.set_rbf(True)
+ fee=tx.estimated_size()*fees_per_byte
+ if balance -fee >0:
+ out = PartialTxOutput.from_address_and_value(change_addresses[0],balance - fee)
+ tx = PartialTransaction.from_io(utxo_to_spend,[out], locktime=locktime, version=2)
+ tx.set_rbf(True)
+
+ _logger.debug(f"invalidation tx: {tx}")
+ return tx
-def only_valid_list(will):
- out={}
- for wid,w in will.items():
- if w.get_status('VALID'):
- out[wid]=w
- return out
-
-def only_valid_or_replaced_list(will):
- out=[]
- for wid,w in will.items():
- wi = w
- if wi.get_status('VALID') or wi.get_status('REPLACED'):
- out.append(wid)
- return out
-
-def check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,check_date,tx_fees):
- _logger.debug("check willexecutors heirs")
- no_willexecutor = 0
- willexecutors_found = {}
- heirs_found = {}
- will_only_valid = only_valid_list(will)
- if len(will_only_valid)<1:
- return False
- for wid in only_valid_list(will):
- w = will[wid]
- if w.tx_fees != tx_fees:
- #w.set_status('VALID',False)
- raise TxFeesChangedException(f"{tx_fees}:",w.tx_fees)
- for wheir in w.heirs:
- if not 'w!ll3x3c"' == wheir[:9]:
- their = will[wid].heirs[wheir]
- if heir := heirs.get(wheir,None):
-
- if heir[0] == their[0] and heir[1] == their[1] and Util.parse_locktime_string(heir[2]) >= Util.parse_locktime_string(their[2]):
- count = heirs_found.get(wheir,0)
- heirs_found[wheir]=count + 1
- else:
- _logger.debug("heir not present transaction is not valid:",wid,w)
- continue
- if willexecutor := w.we:
- count = willexecutors_found.get(willexecutor['url'],0)
- if Util.cmp_willexecutor(willexecutor,willexecutors.get(willexecutor['url'],None)):
- willexecutors_found[willexecutor['url']]=count+1
-
+ else:
+ _logger.debug("balance - fee <=0")
+ pass
else:
- no_willexecutor += 1
- count_heirs = 0
- for h in heirs:
- if Util.parse_locktime_string(heirs[h][2])>=check_date:
- count_heirs +=1
- if not h in heirs_found:
- _logger.debug(f"heir: {h} not found")
- raise HeirNotFoundException(h)
- if not count_heirs:
- raise NoHeirsException("there are not valid heirs")
- if self_willexecutor and no_willexecutor ==0:
- raise NoWillExecutorNotPresent("Backup tx")
+ _logger.debug("len utxo_to_spend <=0")
+ pass
- for url,we in willexecutors.items():
- if Willexecutors.is_selected(we):
- if not url in willexecutors_found:
- _logger.debug(f"will-executor: {url} not fount")
- raise WillExecutorNotPresent(url)
- _logger.info("will is coherent with heirs and will-executors")
- return True
+
+ def is_new(will):
+ for wid,w in will.items():
+ if w.get_status('VALID') and not w.get_status('COMPLETE'):
+ return True
+
+ def search_rai (all_inputs,all_utxos,will,wallet):
+ will_only_valid = Will.only_valid_or_replaced_list(will)
+ for inp,ws in all_inputs.items():
+ inutxo = Util.in_utxo(inp,all_utxos)
+ for w in ws:
+ wi=w[1]
+ if wi.get_status('VALID') or wi.get_status('CONFIRMED') or wi.get_status('PENDING'):
+ prevout_id=w[2].prevout.txid.hex()
+ if not inutxo:
+ if prevout_id in will:
+ wo=will[prevout_id]
+ if wo.get_status('REPLACED'):
+ wi.set_status('REPLACED',True)
+ if wo.get_status("INVALIDATED"):
+ wi.set_status('INVALIDATED',True)
+
+ else:
+ if wallet.db.get_transaction(wi._id):
+ wi.set_status('CONFIRMED',True)
+ else:
+ wi.set_status('INVALIDATED',True)
+
+ for child in wi.search(all_inputs):
+ if child.tx.locktime < wi.tx.locktime:
+ _logger.debug("a child was found")
+ wi.set_status('REPLACED',True)
+ else:
+ pass
+
+ def utxos_strs(utxos):
+ return [Util.utxo_to_str(u) for u in utxos]
+
+
+ def set_invalidate(wid,will=[]):
+ will[wid].set_status("INVALIDATED",True)
+ if will[wid].children:
+ for c in self.children.items():
+ Will.set_invalidate(c[0],will)
+
+ def check_tx_height(tx, wallet):
+ info=wallet.get_tx_info(tx)
+ return info.tx_mined_status.height
+
+ #check if transactions are stil valid tecnically valid
+ def check_invalidated(willtree,utxos_list,wallet):
+ for wid,w in willtree.items():
+ if not w.father:
+ for inp in w.tx.inputs():
+ inp_str = Util.utxo_to_str(inp)
+ if not inp_str in utxos_list:
+ if wallet:
+ height= Will.check_tx_height(w.tx,wallet)
+
+ if height < 0:
+ Will.set_invalidate(wid,willtree)
+ elif height == 0:
+ w.set_status("PENDING",True)
+ else:
+ w.set_status('CONFIRMED',True)
+
+ def reflect_to_children(treeitem):
+ if not treeitem.get_status("VALID"):
+ _logger.debug(f"{tree:item._id} status not valid looking for children")
+ for child in treeitem.children:
+ wc = willtree[child]
+ if wc.get_status("VALID"):
+ if treeitem.get_status("INVALIDATED"):
+ wc.set_status("INVALIDATED",True)
+ if treeitem.get_status("REPLACED"):
+ wc.set_status("REPLACED",True)
+ if wc.children:
+ Will.reflect_to_children(wc)
+
+ def check_amounts(heirs,willexecutors,all_utxos,timestamp_to_check,dust):
+ fixed_heirs,fixed_amount,perc_heirs,perc_amount = heirs.fixed_percent_lists_amount(timestamp_to_check,dust,reverse=True)
+ wallet_balance = 0
+ for utxo in all_utxos:
+ wallet_balance += utxo.value_sats()
+
+ if fixed_amount >= wallet_balance:
+ raise FixedAmountException(f"Fixed amount({fixed_amount}) >= {wallet_balance}")
+ if perc_amount != 100:
+ raise PercAmountException(f"Perc amount({perc_amount}) =! 100%")
+
+ for url,wex in willexecutors.items():
+ if Willexecutors.is_selected(wex):
+ temp_balance = wallet_balance - int(wex['base_fee'])
+ if fixed_amount >= temp_balance:
+ raise FixedAmountException(f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}")
+
+
+ def check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check):
+ Will.add_willtree(will)
+ utxos_list= Will.utxos_strs(all_utxos)
+
+ Will.check_invalidated(will,utxos_list,wallet)
+
+ all_inputs=Will.get_all_inputs(will,only_valid = True)
+
+ all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_inputs)
+
+ Will.check_will_expired(all_inputs_min_locktime,block_to_check,timestamp_to_check)
+
+ all_inputs=Will.get_all_inputs(will,only_valid = True)
+
+ Will.search_rai(all_inputs,all_utxos,will,wallet)
+
+ def is_will_valid(will, block_to_check, timestamp_to_check, tx_fees, all_utxos,heirs={},willexecutors={},self_willexecutor=False, wallet=False, callback_not_valid_tx=None):
+
+ Will.check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check)
+
+
+ if heirs:
+ if not Will.check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,timestamp_to_check,tx_fees):
+ raise NotCompleteWillException()
+
+
+ all_inputs=Will.get_all_inputs(will,only_valid = True)
+
+ _logger.info('check all utxo in wallet are spent')
+ if all_inputs:
+ for utxo in all_utxos:
+ if utxo.value_sats() > 68 * tx_fees:
+ if not Util.in_utxo(utxo,all_inputs.keys()):
+ _logger.info("utxo is not spent",utxo.to_json())
+ _logger.debug(all_inputs.keys())
+ raise NotCompleteWillException("Some utxo in the wallet is not included")
+
+ _logger.info('will ok')
+ return True
+
+ def check_will_expired(all_inputs_min_locktime,block_to_check,timestamp_to_check):
+ _logger.info("check if some transaction is expired")
+ for prevout_str, wid in all_inputs_min_locktime.items():
+ for w in wid:
+ if w[1].get_status('VALID'):
+ locktime = int(wid[0][1].tx.locktime)
+ if locktime <= NLOCKTIME_BLOCKHEIGHT_MAX:
+ if locktime < int(block_to_check):
+ raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{block_to_check}")
+ else:
+ if locktime < int(timestamp_to_check):
+ raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}")
+
+ def check_all_input_spent_are_in_wallet():
+ _logger.info("check all input spent are in wallet or valid txs")
+ for inp,ws in all_inputs.items():
+ if not Util.in_utxo(inp,all_utxos):
+ for w in ws:
+ if w[1].get_status('VALID'):
+ prevout_id = w[2].prevout.txid.hex()
+ parentwill = will.get(prevout_id,False)
+ if not parentwill or not parentwill.get_status('VALID'):
+ w[1].set_status('INVALIDATED',True)
+
+
+ def only_valid_list(will):
+ out={}
+ for wid,w in will.items():
+ if w.get_status('VALID'):
+ out[wid]=w
+ return out
+
+ def only_valid_or_replaced_list(will):
+ out=[]
+ for wid,w in will.items():
+ wi = w
+ if wi.get_status('VALID') or wi.get_status('REPLACED'):
+ out.append(wid)
+ return out
+
+ def check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,check_date,tx_fees):
+ _logger.debug("check willexecutors heirs")
+ no_willexecutor = 0
+ willexecutors_found = {}
+ heirs_found = {}
+ will_only_valid = Will.only_valid_list(will)
+ if len(will_only_valid)<1:
+ return False
+ for wid in Will.only_valid_list(will):
+ w = will[wid]
+ if w.tx_fees != tx_fees:
+ raise TxFeesChangedException(f"{tx_fees}:",w.tx_fees)
+ for wheir in w.heirs:
+ if not 'w!ll3x3c"' == wheir[:9]:
+ their = will[wid].heirs[wheir]
+ if heir := heirs.get(wheir,None):
+
+ if heir[0] == their[0] and heir[1] == their[1] and Util.parse_locktime_string(heir[2]) >= Util.parse_locktime_string(their[2]):
+ count = heirs_found.get(wheir,0)
+ heirs_found[wheir]=count + 1
+ else:
+ _logger.debug("heir not present transaction is not valid:",wid,w)
+ continue
+ if willexecutor := w.we:
+ count = willexecutors_found.get(willexecutor['url'],0)
+ if Util.cmp_willexecutor(willexecutor,willexecutors.get(willexecutor['url'],None)):
+ willexecutors_found[willexecutor['url']]=count+1
+
+ else:
+ no_willexecutor += 1
+ count_heirs = 0
+ for h in heirs:
+ if Util.parse_locktime_string(heirs[h][2])>=check_date:
+ count_heirs +=1
+ if not h in heirs_found:
+ _logger.debug(f"heir: {h} not found")
+ raise HeirNotFoundException(h)
+ if not count_heirs:
+ raise NoHeirsException("there are not valid heirs")
+ if self_willexecutor and no_willexecutor ==0:
+ raise NoWillExecutorNotPresent("Backup tx")
+
+ for url,we in willexecutors.items():
+ if Willexecutors.is_selected(we):
+ if not url in willexecutors_found:
+ _logger.debug(f"will-executor: {url} not fount")
+ raise WillExecutorNotPresent(url)
+ _logger.info("will is coherent with heirs and will-executors")
+ return True
class WillItem(Logger):
@@ -630,8 +602,6 @@ class WillItem(Logger):
self.STATUS['PUSH_FAIL'][1] = False
self.STATUS['CHECK_FAIL'][1] = False
- #if status in ['CHECK_FAIL']:
- # self.STATUS['PUSHED'][1] = False
if status in ['CHECKED']:
self.STATUS['PUSHED'][1] = True
@@ -646,7 +616,7 @@ class WillItem(Logger):
if isinstance(w,WillItem,):
self.__dict__ = w.__dict__.copy()
else:
- self.tx = get_tx_from_any(w['tx'])
+ self.tx = Will.get_tx_from_any(w['tx'])
self.heirs = w.get('heirs',None)
self.we = w.get('willexecutor',None)
self.status = w.get('status',None)
@@ -700,13 +670,11 @@ class WillItem(Logger):
return str(self.to_dict())
def set_anticipate(self, ow:'WillItem'):
- nl = min(ow.tx.locktime,check_anticipate(ow,self))
+ nl = min(ow.tx.locktime,Will.check_anticipate(ow,self))
if int(nl) < self.tx.locktime:
- #_logger.debug("actually anticipating")
self.tx.locktime = int(nl)
return True
else:
- #_logger.debug("keeping the same locktime")
return False
diff --git a/willexecutors.py b/willexecutors.py
index b18a5f0..6620397 100644
--- a/willexecutors.py
+++ b/willexecutors.py
@@ -9,195 +9,214 @@ from electrum.logging import get_logger
from electrum.gui.qt.util import WaitingDialog
from electrum.i18n import _
-from .balqt.baldialog import BalWaitingDialog
-from . import util as Util
+from .util import Util
DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__)
+class Willexecutors:
+ def get_willexecutors(bal_plugin, update = False,bal_window=False,force=False,task=True):
+ willexecutors = bal_plugin.config_get(bal_plugin.WILLEXECUTORS)
+ for w in willexecutors:
+ Willexecutors.initialize_willexecutor(willexecutors[w],w)
-def get_willexecutors(bal_plugin, update = False,bal_window=False,force=False,task=True):
- willexecutors = bal_plugin.config_get(bal_plugin.WILLEXECUTORS)
- for w in willexecutors:
- initialize_willexecutor(willexecutors[w],w)
+ bal=bal_plugin.DEFAULT_SETTINGS[bal_plugin.WILLEXECUTORS]
+ for bal_url,bal_executor in bal.items():
+ if not bal_url in willexecutors:
+ _logger.debug("replace bal")
+ willexecutors[bal_url]=bal_executor
+ if update:
+ found = False
+ for url,we in willexecutors.items():
+ if Willexecutors.is_selected(we):
+ found = True
+ if found or force:
+ if bal_plugin.config_get(bal_plugin.PING_WILLEXECUTORS) or force:
+ ping_willexecutors = True
+ if bal_plugin.config_get(bal_plugin.ASK_PING_WILLEXECUTORS) and not force:
+ ping_willexecutors = bal_window.window.question(_("Contact willexecutors servers to update payment informations?"))
+ if ping_willexecutors:
+ if task:
+ bal_window.ping_willexecutors(willexecutors)
+ else:
+ bal_window.ping_willexecutors_task(willexecutors)
+ return willexecutors
- bal=bal_plugin.DEFAULT_SETTINGS[bal_plugin.WILLEXECUTORS]
- for bal_url,bal_executor in bal.items():
- if not bal_url in willexecutors:
- _logger.debug("replace bal")
- willexecutors[bal_url]=bal_executor
- if update:
- found = False
+ def is_selected(willexecutor,value=None):
+ if not willexecutor:
+ return False
+ if not value is None:
+ willexecutor['selected']=value
+ try:
+ return willexecutor['selected']
+ except:
+ willexecutor['selected']=False
+ return False
+
+ def get_willexecutor_transactions(will, force=False):
+ willexecutors ={}
+ for wid,willitem in will.items():
+ if willitem.get_status('VALID'):
+ if willitem.get_status('COMPLETE'):
+ if not willitem.get_status('PUSHED') or force:
+ if willexecutor := willitem.we:
+ url=willexecutor['url']
+ if willexecutor and Willexecutors.is_selected(willexecutor):
+ if not url in willexecutors:
+ willexecutor['txs']=""
+ willexecutor['txsids']=[]
+ willexecutor['broadcast_status']= _("Waiting...")
+ willexecutors[url]=willexecutor
+ willexecutors[url]['txs']+=str(willitem.tx)+"\n"
+ willexecutors[url]['txsids'].append(wid)
+
+ return willexecutors
+
+ def only_selected_list(willexecutors):
+ out = {}
+ for url,v in willexecutors.items():
+ if Willexecutors.is_selected(willexecutor):
+ out[url]=v
+ def push_transactions_to_willexecutors(will):
+ willexecutors = get_transactions_to_be_pushed()
+ for url in willexecutors:
+ willexecutor = willexecutors[url]
+ if Willexecutors.is_selected(willexecutor):
+ if 'txs' in willexecutor:
+ Willexecutors.push_transactions_to_willexecutor(willexecutors[url]['txs'],url)
+
+ def send_request(method, url, data=None, *, timeout=10):
+ network = Network.get_instance()
+ if not network:
+ raise ErrorConnectingServer('You are offline.')
+ _logger.debug(f'<-- {method} {url} {data}')
+ headers = {}
+ headers['user-agent'] = 'BalPlugin'
+ headers['Content-Type']='text/plain'
+
+ try:
+ if method == 'get':
+ response = Network.send_http_on_proxy(method, url,
+ params=data,
+ headers=headers,
+ on_finish=Willexecutors.handle_response,
+ timeout=timeout)
+ elif method == 'post':
+ response = Network.send_http_on_proxy(method, url,
+ body=data,
+ headers=headers,
+ on_finish=Willexecutors.handle_response,
+ timeout=timeout)
+ else:
+ raise Exception(f"unexpected {method=!r}")
+ except Exception as e:
+ _logger.error(f"exception sending request {e}")
+ raise e
+ else:
+ _logger.debug(f'--> {response}')
+ return response
+ async def handle_response(resp:ClientResponse):
+ r=await resp.text()
+ try:
+ r=json.loads(r)
+ r['status'] = resp.status
+ r['selected']=Willexecutors.is_selected(willexecutor)
+ r['url']=url
+ except:
+ pass
+ return r
+
+ class AlreadyPresentException(Exception):
+ pass
+ def push_transactions_to_willexecutor(willexecutor):
+ out=True
+ try:
+
+ _logger.debug(f"willexecutor['txs']")
+ if w:=Willexecutors.send_request('post', willexecutor['url']+"/"+constants.net.NET_NAME+"/pushtxs", data=willexecutor['txs'].encode('ascii')):
+ willexecutor['broadcast_status'] = _("Success")
+ _logger.debug(f"pushed: {w}")
+ if w !='thx':
+ _logger.debug(f"error: {w}")
+ raise Exception(w)
+ else:
+ raise Exception("empty reply from:{willexecutor['url']}")
+ except Exception as e:
+ _logger.debug(f"error:{e}")
+ if str(e) == "already present":
+ raise Willexecutors.AlreadyPresentException()
+ out=False
+ willexecutor['broadcast_status'] = _("Failed")
+
+ return out
+
+ def ping_servers(willexecutors):
for url,we in willexecutors.items():
- if is_selected(we):
- found = True
- if found or force:
- if bal_plugin.config_get(bal_plugin.PING_WILLEXECUTORS) or force:
- ping_willexecutors = True
- if bal_plugin.config_get(bal_plugin.ASK_PING_WILLEXECUTORS) and not force:
- ping_willexecutors = bal_window.window.question(_("Contact willexecutors servers to update payment informations?"))
- if ping_willexecutors:
- if task:
- bal_window.ping_willexecutors(willexecutors)
- else:
- bal_window.ping_willexecutors_task(willexecutors)
- return willexecutors
-
-def is_selected(willexecutor,value=None):
- if not willexecutor:
- return False
- if not value is None:
- willexecutor['selected']=value
- try:
- return willexecutor['selected']
- except:
- willexecutor['selected']=False
- return False
-
-def get_willexecutor_transactions(will, force=False):
- willexecutors ={}
- for wid,willitem in will.items():
- if willitem.get_status('VALID'):
- if willitem.get_status('COMPLETE'):
- if not willitem.get_status('PUSHED') or force:
- if willexecutor := willitem.we:
- url=willexecutor['url']
- if willexecutor and is_selected(willexecutor):
- if not url in willexecutors:
- willexecutor['txs']=""
- willexecutor['txsids']=[]
- willexecutor['broadcast_status']= _("Waiting...")
- willexecutors[url]=willexecutor
- willexecutors[url]['txs']+=str(willitem.tx)+"\n"
- willexecutors[url]['txsids'].append(wid)
-
- return willexecutors
-
-def only_selected_list(willexecutors):
- out = {}
- for url,v in willexectors.items():
- if is_selected(willexecutor):
- out[url]=v
-def push_transactions_to_willexecutors(will):
- willexecutors = get_transactions_to_be_pushed()
- for url in willexecutors:
- willexecutor = willexecutors[url]
- if is_selected(willexecutor):
- if 'txs' in willexecutor:
- push_transactions_to_willexecutor(willexecutors[url]['txs'],url)
-
-def send_request(method, url, data=None, *, timeout=10):
- network = Network.get_instance()
- if not network:
- raise ErrorConnectingServer('You are offline.')
- _logger.debug(f'<-- {method} {url} {data}')
- headers = {}
- headers['user-agent'] = 'BalPlugin'
- headers['Content-Type']='text/plain'
-
- try:
- if method == 'get':
- response = Network.send_http_on_proxy(method, url,
- params=data,
- headers=headers,
- on_finish=handle_response,
- timeout=timeout)
- elif method == 'post':
- response = Network.send_http_on_proxy(method, url,
- body=data,
- headers=headers,
- on_finish=handle_response,
- timeout=timeout)
- else:
- raise Exception(f"unexpected {method=!r}")
- except Exception as e:
- _logger.error(f"exception sending request {e}")
- raise e
- else:
- _logger.debug(f'--> {response}')
- return response
-async def handle_response(resp:ClientResponse):
- r=await resp.text()
- try:
- r=json.loads(r)
- r['status'] = resp.status
- r['selected']=is_selected(willexecutor)
- r['url']=url
- except:
- pass
- return r
-
-class AlreadyPresentException(Exception):
- pass
-def push_transactions_to_willexecutor(willexecutor):
- out=True
- try:
- _logger.debug(f"willexecutor['txs']")
- if w:=send_request('post', willexecutor['url']+"/"+constants.net.NET_NAME+"/pushtxs", data=willexecutor['txs'].encode('ascii')):
- willexecutor['broadcast_status'] = _("Success")
- _logger.debug(f"pushed: {w}")
- if w !='thx':
- _logger.debug(f"error: {w}")
- raise Exception(w)
- else:
- raise Exception("empty reply from:{willexecutor['url']}")
- except Exception as e:
- _logger.debug(f"error:{e}")
- if str(e) == "already present":
- raise AlreadyPresentException()
- out=False
- willexecutor['broadcast_status'] = _("Failed")
-
- return out
-
-def ping_servers(willexecutors):
- for url,we in willexecutors.items():
- get_info_task(url,we)
+ Willexecutors.get_info_task(url,we)
-def get_info_task(url,willexecutor):
- w=None
- try:
- _logger.info("GETINFO_WILLEXECUTOR")
- _logger.debug(url)
- w = send_request('get',url+"/"+constants.net.NET_NAME+"/info")
+ def get_info_task(url,willexecutor):
+ w=None
+ try:
+ _logger.info("GETINFO_WILLEXECUTOR")
+ _logger.debug(url)
+ netname="bitcoin"
+ if constants.net.NET_NAME!="mainnet":
+ netname=constants.net.NET_NAME
+ w = Willexecutors.send_request('get',url+"/"+netname+"/info")
+ willexecutor['url']=url
+ willexecutor['status'] = w['status']
+ willexecutor['base_fee'] = w['base_fee']
+ willexecutor['address'] = w['address']
+ if not willexecutor['info']:
+ willexecutor['info'] = w['info']
+ _logger.debug(f"response_data {w['address']}")
+ except Exception as e:
+ _logger.error(f"error {e} contacting {url}: {w}")
+ willexecutor['status']="KO"
+
+ willexecutor['last_update'] = datetime.now().timestamp()
+ return willexecutor
+
+ def initialize_willexecutor(willexecutor,url,status=None,selected=None):
+ print("1",willexecutor)
willexecutor['url']=url
- willexecutor['status'] = w['status']
- willexecutor['base_fee'] = w['base_fee']
- willexecutor['address'] = w['address']
- if not willexecutor['info']:
- willexecutor['info'] = w['info']
- _logger.debug(f"response_data {w['address']}")
- except Exception as e:
- _logger.error(f"error {e} contacting {url}: {w}")
- willexecutor['status']="KO"
+ print("2",status)
+ if not status is None:
+ willexecutor['status'] = status
+ willexecutor['selected'] = Willexecutors.is_selected(willexecutor,selected)
+ def download_list(bal_plugin):
+ try:
+ l = Willexecutors.send_request('get',"https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100")
+ del l['status']
+ from pprint import pprint
+ pprint(l)
+ for w in l:
+ print("----")
+ willexecutor=l[w]
+ Willexecutors.initialize_willexecutor(willexecutor,w,'New',False)
+ bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True)
+ return l
+ except Exception as e:
+ _logger.error(f"error downloading willexecutors list:{e}")
+ return {}
+ def get_willexecutors_list_from_json(bal_plugin):
+ try:
+ with open("willexecutors.json") as f:
+ willexecutors = json.load(f)
+ for w in willexecutors:
+ willexecutor=willexecutors[w]
+ Willexecutors.initialize_willexecutor(willexecutor,w,'New',False)
+ bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,willexecutors,save=True)
+ return h
+ except Exception as e:
+ _logger.error(f"errore aprendo willexecutors.json: {e}")
+ return {}
- willexecutor['last_update'] = datetime.now().timestamp()
- return willexecutor
-
-def initialize_willexecutor(willexecutor,url,status=None,selected=None):
- willexecutor['url']=url
- if not status is None:
- willexecutor['status'] = status
- willexecutor['selected'] = is_selected(willexecutor,selected)
-
-def get_willexecutors_list_from_json(bal_plugin):
- try:
- with open("willexecutors.json") as f:
- willexecutors = json.load(f)
- for w in willexecutors:
- willexecutor=willexecutors[w]
- willexecutors.initialize_willexecutor(willexecutor,w,'New',False)
- bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,willexecutors,save=True)
- return h
- except Exception as e:
- _logger.error(f"errore aprendo willexecutors.json: {e}")
- return {}
-
-def check_transaction(txid,url):
- _logger.debug(f"{url}:{txid}")
- try:
- w = send_request('post',url+"/searchtx",data=txid.encode('ascii'))
- return w
- except Exception as e:
- _logger.error(f"error contacting {url} for checking txs {e}")
- raise e
+ def check_transaction(txid,url):
+ _logger.debug(f"{url}:{txid}")
+ try:
+ w = Willexecutors.send_request('post',url+"/searchtx",data=txid.encode('ascii'))
+ return w
+ except Exception as e:
+ _logger.error(f"error contacting {url} for checking txs {e}")
+ raise e