@@ -44,6 +44,10 @@ COINBASE_MATURITY = 100
|
||||
COIN = 100000000
|
||||
TOTAL_COIN_SUPPLY_LIMIT_IN_BTC = 21000000
|
||||
|
||||
NLOCKTIME_MIN = 0
|
||||
NLOCKTIME_BLOCKHEIGHT_MAX = 500_000_000 - 1
|
||||
NLOCKTIME_MAX = 2 ** 32 - 1
|
||||
|
||||
# supported types of transaction outputs
|
||||
# TODO kill these with fire
|
||||
TYPE_ADDRESS = 0
|
||||
|
||||
173
electrum/gui/qt/locktimeedit.py
Normal file
173
electrum/gui/qt/locktimeedit.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# Copyright (C) 2020 The Electrum developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any
|
||||
|
||||
from PyQt5.QtCore import Qt, QDateTime
|
||||
from PyQt5.QtGui import QPalette, QPainter
|
||||
from PyQt5.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox,
|
||||
QHBoxLayout, QDateTimeEdit)
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.bitcoin import NLOCKTIME_MIN, NLOCKTIME_MAX, NLOCKTIME_BLOCKHEIGHT_MAX
|
||||
|
||||
from .util import char_width_in_lineedit
|
||||
|
||||
|
||||
class LockTimeEdit(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
|
||||
hbox = QHBoxLayout()
|
||||
self.setLayout(hbox)
|
||||
hbox.setContentsMargins(0, 0, 0, 0)
|
||||
hbox.setSpacing(0)
|
||||
|
||||
self.locktime_raw_e = LockTimeRawEdit()
|
||||
self.locktime_height_e = LockTimeHeightEdit()
|
||||
self.locktime_date_e = LockTimeDateEdit()
|
||||
self.editors = [self.locktime_raw_e, self.locktime_height_e, self.locktime_date_e]
|
||||
|
||||
self.combo = QComboBox()
|
||||
options = [_("Raw"), _("Block height"), _("Date")]
|
||||
option_index_to_editor_map = {
|
||||
0: self.locktime_raw_e,
|
||||
1: self.locktime_height_e,
|
||||
2: self.locktime_date_e,
|
||||
}
|
||||
default_index = 1
|
||||
self.combo.addItems(options)
|
||||
|
||||
def on_current_index_changed(i):
|
||||
for w in self.editors:
|
||||
w.setVisible(False)
|
||||
w.setEnabled(False)
|
||||
prev_locktime = self.editor.get_locktime()
|
||||
self.editor = option_index_to_editor_map[i]
|
||||
if self.editor.is_acceptable_locktime(prev_locktime):
|
||||
self.editor.set_locktime(prev_locktime)
|
||||
self.editor.setVisible(True)
|
||||
self.editor.setEnabled(True)
|
||||
|
||||
self.editor = option_index_to_editor_map[default_index]
|
||||
self.combo.currentIndexChanged.connect(on_current_index_changed)
|
||||
self.combo.setCurrentIndex(default_index)
|
||||
on_current_index_changed(default_index)
|
||||
|
||||
hbox.addWidget(self.combo)
|
||||
for w in self.editors:
|
||||
hbox.addWidget(w)
|
||||
hbox.addStretch(1)
|
||||
|
||||
def get_locktime(self) -> Optional[int]:
|
||||
return self.editor.get_locktime()
|
||||
|
||||
def set_locktime(self, x: Any) -> None:
|
||||
self.editor.set_locktime(x)
|
||||
|
||||
|
||||
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) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def is_acceptable_locktime(cls, x: Any) -> bool:
|
||||
if not x: # e.g. empty string
|
||||
return True
|
||||
try:
|
||||
x = int(x)
|
||||
except:
|
||||
return False
|
||||
return cls.min_allowed_value <= x <= cls.max_allowed_value
|
||||
|
||||
|
||||
class LockTimeRawEdit(QLineEdit, _LockTimeEditor):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QLineEdit.__init__(self, parent)
|
||||
self.setFixedWidth(14 * char_width_in_lineedit())
|
||||
self.textChanged.connect(self.numbify)
|
||||
|
||||
def numbify(self):
|
||||
text = self.text().strip()
|
||||
chars = '0123456789'
|
||||
pos = 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.set_locktime(s)
|
||||
# setText sets Modified to False. Instead we want to remember
|
||||
# if updates were because of user modification.
|
||||
self.setModified(self.hasFocus())
|
||||
self.setCursorPosition(pos)
|
||||
|
||||
def get_locktime(self) -> Optional[int]:
|
||||
try:
|
||||
return int(str(self.text()))
|
||||
except:
|
||||
return None
|
||||
|
||||
def set_locktime(self, x: Any) -> None:
|
||||
try:
|
||||
x = int(x)
|
||||
except:
|
||||
self.setText('')
|
||||
return
|
||||
x = max(x, self.min_allowed_value)
|
||||
x = min(x, self.max_allowed_value)
|
||||
self.setText(str(x))
|
||||
|
||||
|
||||
class LockTimeHeightEdit(LockTimeRawEdit):
|
||||
max_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX
|
||||
|
||||
def __init__(self, parent=None):
|
||||
LockTimeRawEdit.__init__(self, parent)
|
||||
self.setFixedWidth(20 * char_width_in_lineedit())
|
||||
self.help_palette = QPalette()
|
||||
|
||||
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(self.help_palette.brush(QPalette.Disabled, QPalette.Text).color())
|
||||
painter.drawText(textRect, Qt.AlignRight | Qt.AlignVCenter, "height")
|
||||
|
||||
|
||||
class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor):
|
||||
min_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX + 1
|
||||
|
||||
def __init__(self, parent=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())
|
||||
|
||||
def get_locktime(self) -> Optional[int]:
|
||||
dt = self.dateTime().toPyDateTime()
|
||||
locktime = int(time.mktime(dt.timetuple()))
|
||||
return locktime
|
||||
|
||||
def set_locktime(self, x: Any) -> None:
|
||||
if not self.is_acceptable_locktime(x):
|
||||
self.setDateTime(QDateTime.currentDateTime())
|
||||
return
|
||||
try:
|
||||
x = int(x)
|
||||
except:
|
||||
self.setDateTime(QDateTime.currentDateTime())
|
||||
return
|
||||
dt = datetime.fromtimestamp(x)
|
||||
self.setDateTime(dt)
|
||||
@@ -41,7 +41,7 @@ from qrcode import exceptions
|
||||
|
||||
from electrum.simple_config import SimpleConfig
|
||||
from electrum.util import quantize_feerate
|
||||
from electrum.bitcoin import base_encode
|
||||
from electrum.bitcoin import base_encode, NLOCKTIME_BLOCKHEIGHT_MAX
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import run_hook
|
||||
from electrum import simple_config
|
||||
@@ -58,6 +58,7 @@ from .util import (MessageBoxMixin, read_QIcon, Buttons, icon_path,
|
||||
from .fee_slider import FeeSlider
|
||||
from .confirm_tx_dialog import TxEditor
|
||||
from .amountedit import FeerateEdit, BTCAmountEdit
|
||||
from .locktimeedit import LockTimeEdit
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .main_window import ElectrumWindow
|
||||
@@ -434,7 +435,13 @@ class BaseTxDialog(QDialog, MessageBoxMixin):
|
||||
self.date_label.show()
|
||||
else:
|
||||
self.date_label.hide()
|
||||
self.locktime_label.setText(f"LockTime: {self.tx.locktime}")
|
||||
if self.tx.locktime <= NLOCKTIME_BLOCKHEIGHT_MAX:
|
||||
locktime_final_str = f"LockTime: {self.tx.locktime} (height)"
|
||||
else:
|
||||
locktime_final_str = f"LockTime: {self.tx.locktime} ({datetime.datetime.fromtimestamp(self.tx.locktime)})"
|
||||
self.locktime_final_label.setText(locktime_final_str)
|
||||
if self.locktime_e.get_locktime() is None:
|
||||
self.locktime_e.set_locktime(self.tx.locktime)
|
||||
self.rbf_label.setText(_('Replace by fee') + f": {not self.tx.is_final()}")
|
||||
|
||||
if tx_mined_status.header_hash:
|
||||
@@ -611,8 +618,22 @@ class BaseTxDialog(QDialog, MessageBoxMixin):
|
||||
self.rbf_cb.setChecked(bool(self.config.get('use_rbf', True)))
|
||||
vbox_right.addWidget(self.rbf_cb)
|
||||
|
||||
self.locktime_label = TxDetailLabel()
|
||||
vbox_right.addWidget(self.locktime_label)
|
||||
self.locktime_final_label = TxDetailLabel()
|
||||
vbox_right.addWidget(self.locktime_final_label)
|
||||
|
||||
locktime_setter_hbox = QHBoxLayout()
|
||||
locktime_setter_hbox.setContentsMargins(0, 0, 0, 0)
|
||||
locktime_setter_hbox.setSpacing(0)
|
||||
locktime_setter_label = TxDetailLabel()
|
||||
locktime_setter_label.setText("LockTime: ")
|
||||
self.locktime_e = LockTimeEdit()
|
||||
locktime_setter_hbox.addWidget(locktime_setter_label)
|
||||
locktime_setter_hbox.addWidget(self.locktime_e)
|
||||
locktime_setter_hbox.addStretch(1)
|
||||
self.locktime_setter_widget = QWidget()
|
||||
self.locktime_setter_widget.setLayout(locktime_setter_hbox)
|
||||
vbox_right.addWidget(self.locktime_setter_widget)
|
||||
|
||||
self.block_height_label = TxDetailLabel()
|
||||
vbox_right.addWidget(self.block_height_label)
|
||||
vbox_right.addStretch(1)
|
||||
@@ -620,12 +641,15 @@ class BaseTxDialog(QDialog, MessageBoxMixin):
|
||||
|
||||
vbox.addLayout(hbox_stats)
|
||||
|
||||
# below columns
|
||||
self.block_hash_label = TxDetailLabel(word_wrap=True)
|
||||
vbox.addWidget(self.block_hash_label)
|
||||
|
||||
# set visibility after parenting can be determined by Qt
|
||||
self.rbf_label.setVisible(self.finalized)
|
||||
self.rbf_cb.setVisible(not self.finalized)
|
||||
self.locktime_final_label.setVisible(self.finalized)
|
||||
self.locktime_setter_widget.setVisible(not self.finalized)
|
||||
|
||||
def set_title(self):
|
||||
self.setWindowTitle(_("Create transaction") if not self.finalized else _("Transaction"))
|
||||
@@ -838,10 +862,12 @@ class PreviewTxDialog(BaseTxDialog, TxEditor):
|
||||
return
|
||||
self.finalized = True
|
||||
self.tx.set_rbf(self.rbf_cb.isChecked())
|
||||
for widget in [self.fee_slider, self.feecontrol_fields, self.rbf_cb]:
|
||||
self.tx.locktime = self.locktime_e.get_locktime()
|
||||
for widget in [self.fee_slider, self.feecontrol_fields, self.rbf_cb,
|
||||
self.locktime_setter_widget, self.locktime_e]:
|
||||
widget.setEnabled(False)
|
||||
widget.setVisible(False)
|
||||
for widget in [self.rbf_label]:
|
||||
for widget in [self.rbf_label, self.locktime_final_label]:
|
||||
widget.setVisible(True)
|
||||
self.set_title()
|
||||
self.set_buttons_visibility()
|
||||
|
||||
@@ -1135,9 +1135,9 @@ class Peer(Logger):
|
||||
processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey)
|
||||
if chan.get_state() != channel_states.OPEN:
|
||||
raise RemoteMisbehaving(f"received update_add_htlc while chan.get_state() != OPEN. state was {chan.get_state()}")
|
||||
if cltv_expiry >= 500_000_000:
|
||||
if cltv_expiry > bitcoin.NLOCKTIME_BLOCKHEIGHT_MAX:
|
||||
asyncio.ensure_future(self.lnworker.force_close_channel(channel_id))
|
||||
raise RemoteMisbehaving(f"received update_add_htlc with cltv_expiry >= 500_000_000. value was {cltv_expiry}")
|
||||
raise RemoteMisbehaving(f"received update_add_htlc with cltv_expiry > BLOCKHEIGHT_MAX. value was {cltv_expiry}")
|
||||
# add htlc
|
||||
htlc = UpdateAddHtlc(amount_msat=amount_msat_htlc,
|
||||
payment_hash=payment_hash,
|
||||
|
||||
Reference in New Issue
Block a user