Qt: show relative locktime in tx details
This commit is contained in:
@@ -47,7 +47,7 @@ from electrum.i18n import _
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.transaction import SerializationError, Transaction, PartialTransaction, TxOutpoint, TxinDataFetchProgress
|
||||
from electrum.logging import get_logger
|
||||
from electrum.util import ShortID, get_asyncio_loop, UI_UNIT_NAME_TXSIZE_VBYTES
|
||||
from electrum.util import ShortID, get_asyncio_loop, UI_UNIT_NAME_TXSIZE_VBYTES, delta_time_str
|
||||
from electrum.network import Network
|
||||
from electrum.wallet import TxSighashRiskLevel, TxSighashDanger
|
||||
|
||||
@@ -862,6 +862,19 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
locktime_final_str = _("LockTime: {} ({})").format(self.tx.locktime, locktime_str)
|
||||
self.locktime_final_label.setText(locktime_final_str)
|
||||
|
||||
nsequence_time = self.tx.get_time_based_relative_locktime()
|
||||
nsequence_blocks = self.tx.get_block_based_relative_locktime()
|
||||
if nsequence_time or nsequence_blocks:
|
||||
if nsequence_time:
|
||||
seconds = nsequence_time * 512
|
||||
time_str = delta_time_str(datetime.timedelta(seconds=seconds))
|
||||
else:
|
||||
time_str = '{} blocks'.format(nsequence_blocks)
|
||||
nsequence_str = _("Relative locktime: {}").format(time_str)
|
||||
self.nsequence_label.setText(nsequence_str)
|
||||
else:
|
||||
self.nsequence_label.hide()
|
||||
|
||||
# TODO: 'Yes'/'No' might be better translatable than 'True'/'False'?
|
||||
self.rbf_label.setText(_('Replace by fee: {}').format(_('True') if self.tx.is_rbf_enabled() else _('False')))
|
||||
|
||||
@@ -1001,6 +1014,9 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
self.locktime_final_label = TxDetailLabel()
|
||||
vbox_right.addWidget(self.locktime_final_label)
|
||||
|
||||
self.nsequence_label = TxDetailLabel()
|
||||
vbox_right.addWidget(self.nsequence_label)
|
||||
|
||||
self.block_height_label = TxDetailLabel()
|
||||
vbox_right.addWidget(self.block_height_label)
|
||||
vbox_right.addStretch(1)
|
||||
|
||||
@@ -349,6 +349,19 @@ class TxInput:
|
||||
self.__address = None # type: Optional[str]
|
||||
self.__value_sats = None # type: Optional[int]
|
||||
|
||||
def get_time_based_relative_locktime(self) -> Optional[int]:
|
||||
# see bip 68
|
||||
if self.nsequence & (1<<31):
|
||||
return
|
||||
if self.nsequence & (1<<22):
|
||||
return self.nsequence & 0xffff
|
||||
|
||||
def get_block_based_relative_locktime(self) -> Optional[int]:
|
||||
if self.nsequence & (1<<31):
|
||||
return
|
||||
if not self.nsequence & (1<<22):
|
||||
return self.nsequence & 0xffff
|
||||
|
||||
@property
|
||||
def short_id(self):
|
||||
if self.block_txpos is not None and self.block_txpos >= 0:
|
||||
@@ -1136,6 +1149,14 @@ class Transaction:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_time_based_relative_locktime(self) -> Optional[int]:
|
||||
locktimes = list(filter(None, [txin.get_time_based_relative_locktime() for txin in self.inputs()]))
|
||||
return max(locktimes) if locktimes else None
|
||||
|
||||
def get_block_based_relative_locktime(self) -> Optional[int]:
|
||||
locktimes = list(filter(None, [txin.get_block_based_relative_locktime() for txin in self.inputs()]))
|
||||
return max(locktimes) if locktimes else None
|
||||
|
||||
def is_rbf_enabled(self) -> bool:
|
||||
"""Whether the tx explicitly signals BIP-0125 replace-by-fee."""
|
||||
return any([txin.nsequence < 0xffffffff - 1 for txin in self.inputs()])
|
||||
|
||||
@@ -28,7 +28,7 @@ from collections import defaultdict, OrderedDict
|
||||
from concurrent.futures.process import ProcessPoolExecutor
|
||||
from typing import (NamedTuple, Union, TYPE_CHECKING, Tuple, Optional, Callable, Any,
|
||||
Sequence, Dict, Generic, TypeVar, List, Iterable, Set, Awaitable)
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import decimal
|
||||
from decimal import Decimal
|
||||
import urllib
|
||||
@@ -875,72 +875,41 @@ def age(
|
||||
"""Takes a timestamp and returns a string with the approximation of the age"""
|
||||
if from_date is None:
|
||||
return _("Unknown")
|
||||
|
||||
from_date = datetime.fromtimestamp(from_date)
|
||||
if since_date is None:
|
||||
since_date = datetime.now(target_tz)
|
||||
|
||||
distance_in_time = from_date - since_date
|
||||
is_in_past = from_date < since_date
|
||||
s = delta_time_str(distance_in_time)
|
||||
return _("{} ago").format(s) if is_in_past else _("in {}").format(s)
|
||||
|
||||
|
||||
def delta_time_str(distance_in_time: timedelta) -> str:
|
||||
distance_in_seconds = int(round(abs(distance_in_time.days * 86400 + distance_in_time.seconds)))
|
||||
distance_in_minutes = int(round(distance_in_seconds / 60))
|
||||
|
||||
if distance_in_minutes == 0:
|
||||
if include_seconds:
|
||||
if is_in_past:
|
||||
return _("{} seconds ago").format(distance_in_seconds)
|
||||
else:
|
||||
return _("in {} seconds").format(distance_in_seconds)
|
||||
return _("{} seconds").format(distance_in_seconds)
|
||||
else:
|
||||
if is_in_past:
|
||||
return _("less than a minute ago")
|
||||
else:
|
||||
return _("in less than a minute")
|
||||
return _("less than a minute")
|
||||
elif distance_in_minutes < 45:
|
||||
if is_in_past:
|
||||
return _("about {} minutes ago").format(distance_in_minutes)
|
||||
else:
|
||||
return _("in about {} minutes").format(distance_in_minutes)
|
||||
return _("about {} minutes").format(distance_in_minutes)
|
||||
elif distance_in_minutes < 90:
|
||||
if is_in_past:
|
||||
return _("about 1 hour ago")
|
||||
else:
|
||||
return _("in about 1 hour")
|
||||
return _("about 1 hour")
|
||||
elif distance_in_minutes < 1440:
|
||||
if is_in_past:
|
||||
return _("about {} hours ago").format(round(distance_in_minutes / 60.0))
|
||||
else:
|
||||
return _("in about {} hours").format(round(distance_in_minutes / 60.0))
|
||||
return _("about {} hours").format(round(distance_in_minutes / 60.0))
|
||||
elif distance_in_minutes < 2880:
|
||||
if is_in_past:
|
||||
return _("about 1 day ago")
|
||||
else:
|
||||
return _("in about 1 day")
|
||||
return _("about 1 day")
|
||||
elif distance_in_minutes < 43220:
|
||||
if is_in_past:
|
||||
return _("about {} days ago").format(round(distance_in_minutes / 1440))
|
||||
else:
|
||||
return _("in about {} days").format(round(distance_in_minutes / 1440))
|
||||
return _("about {} days").format(round(distance_in_minutes / 1440))
|
||||
elif distance_in_minutes < 86400:
|
||||
if is_in_past:
|
||||
return _("about 1 month ago")
|
||||
else:
|
||||
return _("in about 1 month")
|
||||
return _("about 1 month")
|
||||
elif distance_in_minutes < 525600:
|
||||
if is_in_past:
|
||||
return _("about {} months ago").format(round(distance_in_minutes / 43200))
|
||||
else:
|
||||
return _("in about {} months").format(round(distance_in_minutes / 43200))
|
||||
return _("about {} months").format(round(distance_in_minutes / 43200))
|
||||
elif distance_in_minutes < 1051200:
|
||||
if is_in_past:
|
||||
return _("about 1 year ago")
|
||||
else:
|
||||
return _("in about 1 year")
|
||||
return _("about 1 year")
|
||||
else:
|
||||
if is_in_past:
|
||||
return _("over {} years ago").format(round(distance_in_minutes / 525600))
|
||||
else:
|
||||
return _("in over {} years").format(round(distance_in_minutes / 525600))
|
||||
return _("over {} years").format(round(distance_in_minutes / 525600))
|
||||
|
||||
mainnet_block_explorers = {
|
||||
'3xpl.com': ('https://3xpl.com/bitcoin/',
|
||||
|
||||
Reference in New Issue
Block a user