Skip to content

Commit

Permalink
Merge branch 'spesmilo:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
xavierfiechter committed Sep 18, 2023
2 parents e96d945 + cffbe44 commit 2898445
Show file tree
Hide file tree
Showing 20 changed files with 165 additions and 98 deletions.
29 changes: 26 additions & 3 deletions electrum/bitcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import enum
from enum import IntEnum, Enum

from .util import bfh, BitcoinException, assert_bytes, to_bytes, inv_dict, is_hex_str
from .util import bfh, BitcoinException, assert_bytes, to_bytes, inv_dict, is_hex_str, classproperty
from . import version
from . import segwit_addr
from . import constants
Expand Down Expand Up @@ -754,6 +754,29 @@ def is_minikey(text: str) -> bool:
def minikey_to_private_key(text: str) -> bytes:
return sha256(text)

# dummy address for fee estimation of funding tx
def get_dummy_address(purpose):

def _get_dummy_address(purpose: str) -> str:
return redeem_script_to_address('p2wsh', sha256(bytes(purpose, "utf8")).hex())

_dummy_addr_funcs = set()
class DummyAddress:
"""dummy address for fee estimation of funding tx
Use e.g. as: DummyAddress.CHANNEL
"""
def purpose(func):
_dummy_addr_funcs.add(func)
return classproperty(func)

@purpose
def CHANNEL(self) -> str:
return _get_dummy_address("channel")
@purpose
def SWAP(self) -> str:
return _get_dummy_address("swap")

@classmethod
def is_dummy_address(cls, addr: str) -> bool:
return addr in (f(cls) for f in _dummy_addr_funcs)


class DummyAddressUsedInTxException(Exception): pass
4 changes: 2 additions & 2 deletions electrum/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1282,8 +1282,8 @@ async def rebalance_channels(self, from_scid, dest_scid, amount, wallet: Abstrac
from .lnutil import ShortChannelID
from_scid = ShortChannelID.from_str(from_scid)
dest_scid = ShortChannelID.from_str(dest_scid)
from_channel = wallet.lnworker.get_channel_by_scid(from_scid)
dest_channel = wallet.lnworker.get_channel_by_scid(dest_scid)
from_channel = wallet.lnworker.get_channel_by_short_id(from_scid)
dest_channel = wallet.lnworker.get_channel_by_short_id(dest_scid)
amount_sat = satoshis(amount)
success, log = await wallet.lnworker.rebalance_channels(
from_channel,
Expand Down
16 changes: 12 additions & 4 deletions electrum/gui/qml/components/TxDetails.qml
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,21 @@ Pane {
}

Label {
visible: !txdetails.isUnrelated && txdetails.lnAmount.satsInt == 0
visible: !txdetails.isUnrelated && txdetails.amount.satsInt != 0
text: txdetails.amount.satsInt > 0
? qsTr('Amount received')
: qsTr('Amount sent')
? qsTr('Amount received onchain')
: qsTr('Amount sent onchain')
color: Material.accentColor
}

FormattedAmount {
visible: !txdetails.isUnrelated && txdetails.amount.satsInt != 0
Layout.preferredWidth: 1
Layout.fillWidth: true
amount: txdetails.amount
timestamp: txdetails.timestamp
}

Label {
Layout.fillWidth: true
visible: !txdetails.isUnrelated && txdetails.lnAmount.satsInt != 0
Expand All @@ -86,7 +94,7 @@ Pane {
}

FormattedAmount {
visible: !txdetails.isUnrelated
visible: !txdetails.isUnrelated && txdetails.lnAmount.satsInt != 0
Layout.preferredWidth: 1
Layout.fillWidth: true
amount: txdetails.lnAmount.isEmpty ? txdetails.amount : txdetails.lnAmount
Expand Down
4 changes: 2 additions & 2 deletions electrum/gui/qml/qechannelopener.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from electrum.gui import messages
from electrum.util import bfh
from electrum.lnutil import extract_nodeid, ConnStringFormatError
from electrum.bitcoin import get_dummy_address
from electrum.bitcoin import DummyAddress
from electrum.lnworker import hardcoded_trampoline_nodes
from electrum.logging import get_logger

Expand Down Expand Up @@ -182,7 +182,7 @@ def do_open_channel(self, funding_tx, conn_str, password):
"""
self._logger.debug('opening channel')
# read funding_sat from tx; converts '!' to int value
funding_sat = funding_tx.output_value_for_address(get_dummy_address('channel'))
funding_sat = funding_tx.output_value_for_address(DummyAddress.CHANNEL)
lnworker = self._wallet.wallet.lnworker

def open_thread():
Expand Down
6 changes: 3 additions & 3 deletions electrum/gui/qml/qeswaphelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, Q_ENUMS

from electrum.i18n import _
from electrum.bitcoin import get_dummy_address
from electrum.bitcoin import DummyAddress
from electrum.logging import get_logger
from electrum.transaction import PartialTxOutput
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, profiler, get_asyncio_loop
Expand Down Expand Up @@ -245,7 +245,7 @@ def init_swap_slider_range(self):
# this is just to estimate the maximal spendable onchain amount for HTLC
self.update_tx('!')
try:
max_onchain_spend = self._tx.output_value_for_address(get_dummy_address('swap'))
max_onchain_spend = self._tx.output_value_for_address(DummyAddress.SWAP)
except AttributeError: # happens if there are no utxos
max_onchain_spend = 0
reverse = int(min(lnworker.num_sats_can_send(),
Expand Down Expand Up @@ -283,7 +283,7 @@ def update_tx(self, onchain_amount: Union[int, str]):
self._tx = None
self.valid = False
return
outputs = [PartialTxOutput.from_address_and_value(get_dummy_address('swap'), onchain_amount)]
outputs = [PartialTxOutput.from_address_and_value(DummyAddress.SWAP, onchain_amount)]
coins = self._wallet.wallet.get_spendable_coins(None)
try:
self._tx = self._wallet.wallet.make_unsigned_transaction(
Expand Down
10 changes: 9 additions & 1 deletion electrum/gui/qml/qetxdetails.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,18 @@ def update(self, from_txid: bool = False):
self._mempool_depth = self._wallet.wallet.config.depth_tooltip(txinfo.mempool_depth_bytes)

if self._wallet.wallet.lnworker:
# Calling lnworker.get_onchain_history and wallet.get_full_history here
# is inefficient. We should probably pass the tx_item to the constructor.
lnworker_history = self._wallet.wallet.lnworker.get_onchain_history()
if self._txid in lnworker_history:
item = lnworker_history[self._txid]
self._lnamount.satsInt = int(item['amount_msat'] / 1000)
group_id = item.get('group_id')
if group_id:
full_history = self._wallet.wallet.get_full_history()
group_item = full_history['group:'+ group_id]
self._lnamount.satsInt = int(group_item['ln_value'].value)
else:
self._lnamount.satsInt = int(item['amount_msat'] / 1000)
else:
self._lnamount.satsInt = 0

Expand Down
3 changes: 2 additions & 1 deletion electrum/gui/qt/confirm_tx_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from electrum.transaction import Transaction, PartialTransaction
from electrum.wallet import InternalAddressCorruption
from electrum.simple_config import SimpleConfig
from electrum.bitcoin import DummyAddress

from .util import (WindowModalDialog, ColorScheme, HelpLabel, Buttons, CancelButton,
BlockingWaitingDialog, PasswordLineEdit, WWLabel, read_QIcon)
Expand Down Expand Up @@ -574,7 +575,7 @@ def get_messages(self):
self.error = long_warning
else:
messages.append(long_warning)
if self.tx.has_dummy_output('swap'):
if self.tx.has_dummy_output(DummyAddress.SWAP):
messages.append(_('This transaction will send funds to a submarine swap.'))
# warn if spending unconf
if any((txin.block_height is not None and txin.block_height<=0) for txin in self.tx.inputs()):
Expand Down
41 changes: 5 additions & 36 deletions electrum/gui/qt/history_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,42 +301,11 @@ def refresh(self, reason: str):
parents = {}
for tx_item in transactions.values():
node = HistoryNode(self, tx_item)
group_id = tx_item.get('group_id')
if group_id is None:
self._root.addChild(node)
else:
parent = parents.get(group_id)
if parent is None:
# create parent if it does not exist
self._root.addChild(node)
parents[group_id] = node
else:
# if parent has no children, create two children
if parent.childCount() == 0:
child_data = dict(parent.get_data())
node1 = HistoryNode(self, child_data)
parent.addChild(node1)
parent._data['label'] = child_data.get('group_label')
parent._data['bc_value'] = child_data.get('bc_value', Satoshis(0))
parent._data['ln_value'] = child_data.get('ln_value', Satoshis(0))
# add child to parent
parent.addChild(node)
# update parent data
parent._data['value'] += tx_item['value']
if 'group_label' in tx_item:
parent._data['label'] = tx_item['group_label']
if 'bc_value' in tx_item:
parent._data['bc_value'] += tx_item['bc_value']
if 'ln_value' in tx_item:
parent._data['ln_value'] += tx_item['ln_value']
if 'fiat_value' in tx_item:
parent._data['fiat_value'] += tx_item['fiat_value']
if tx_item.get('txid') == group_id:
parent._data['lightning'] = False
parent._data['txid'] = tx_item['txid']
parent._data['timestamp'] = tx_item['timestamp']
parent._data['height'] = tx_item['height']
parent._data['confirmations'] = tx_item['confirmations']
self._root.addChild(node)
for child_item in tx_item.get('children', []):
child_node = HistoryNode(self, child_item)
# add child to parent
node.addChild(child_node)

# compute balance once all children have beed added
balance = 0
Expand Down
4 changes: 2 additions & 2 deletions electrum/gui/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
from electrum.gui import messages
from electrum import (keystore, ecc, constants, util, bitcoin, commands,
paymentrequest, lnutil)
from electrum.bitcoin import COIN, is_address, get_dummy_address
from electrum.bitcoin import COIN, is_address, DummyAddress
from electrum.plugin import run_hook, BasePlugin
from electrum.i18n import _
from electrum.util import (format_time, UserCancelled, profiler, bfh, InvalidPassword,
Expand Down Expand Up @@ -1303,7 +1303,7 @@ def open_channel(self, connect_str, funding_sat, push_amt):
@protected
def _open_channel(self, connect_str, funding_sat, push_amt, funding_tx, password):
# read funding_sat from tx; converts '!' to int value
funding_sat = funding_tx.output_value_for_address(get_dummy_address('channel'))
funding_sat = funding_tx.output_value_for_address(DummyAddress.CHANNEL)
def task():
return self.wallet.lnworker.open_channel(
connect_str=connect_str,
Expand Down
6 changes: 3 additions & 3 deletions electrum/gui/qt/send_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from electrum.i18n import _
from electrum.logging import Logger

from electrum.bitcoin import DummyAddress
from electrum.plugin import run_hook
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, parse_max_spend
from electrum.invoices import PR_PAID, Invoice, PR_BROADCASTING, PR_BROADCAST
Expand Down Expand Up @@ -326,11 +326,11 @@ def pay_onchain_dialog(
return
is_preview = conf_dlg.is_preview

if tx.has_dummy_output('swap'):
if tx.has_dummy_output(DummyAddress.SWAP):
sm = self.wallet.lnworker.swap_manager
coro = sm.request_swap_for_tx(tx)
swap, invoice, tx = self.network.run_from_another_thread(coro)
assert not tx.has_dummy_output('swap')
assert not tx.has_dummy_output(DummyAddress.SWAP)
tx.swap_invoice = invoice
tx.swap_payment_hash = swap.payment_hash

Expand Down
6 changes: 3 additions & 3 deletions electrum/gui/qt/swap_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from electrum.i18n import _
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
from electrum.bitcoin import get_dummy_address
from electrum.bitcoin import DummyAddress
from electrum.transaction import PartialTxOutput, PartialTransaction

from electrum.gui import messages
Expand Down Expand Up @@ -173,7 +173,7 @@ def uncheck_max(self):

def _spend_max_forward_swap(self, tx: Optional[PartialTransaction]) -> None:
if tx:
amount = tx.output_value_for_address(get_dummy_address('swap'))
amount = tx.output_value_for_address(DummyAddress.SWAP)
self.send_amount_e.setAmount(amount)
else:
self.send_amount_e.setAmount(None)
Expand Down Expand Up @@ -295,7 +295,7 @@ def _create_tx(self, onchain_amount: Union[int, str, None]) -> PartialTransactio
if max_amount > max_swap_amount:
onchain_amount = max_swap_amount
self.config.WALLET_SEND_CHANGE_TO_LIGHTNING = False
outputs = [PartialTxOutput.from_address_and_value(get_dummy_address('swap'), onchain_amount)]
outputs = [PartialTxOutput.from_address_and_value(DummyAddress.SWAP, onchain_amount)]
try:
tx = self.window.wallet.make_unsigned_transaction(
coins=coins,
Expand Down
4 changes: 2 additions & 2 deletions electrum/gui/qt/transaction_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from electrum.util import quantize_feerate
from electrum import bitcoin

from electrum.bitcoin import base_encode, NLOCKTIME_BLOCKHEIGHT_MAX, get_dummy_address
from electrum.bitcoin import base_encode, NLOCKTIME_BLOCKHEIGHT_MAX, DummyAddress
from electrum.i18n import _
from electrum.plugin import run_hook
from electrum import simple_config
Expand Down Expand Up @@ -183,7 +183,7 @@ def addr_text_format(addr: str) -> QTextCharFormat:
fmt.setAnchor(True)
fmt.setUnderlineStyle(QTextCharFormat.SingleUnderline)
return fmt
elif sm and sm.is_lockup_address_for_a_swap(addr) or addr==get_dummy_address('swap'):
elif sm and sm.is_lockup_address_for_a_swap(addr) or addr == DummyAddress.SWAP:
tf_used_swap = True
return self.txo_color_swap.text_char_format
elif self.wallet.is_billing_address(addr):
Expand Down
4 changes: 2 additions & 2 deletions electrum/lnpeer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from .util import (bfh, log_exceptions, ignore_exceptions, chunks, OldTaskGroup,
UnrelatedTransactionException, error_text_bytes_to_safe_str)
from . import transaction
from .bitcoin import make_op_return, get_dummy_address
from .bitcoin import make_op_return, DummyAddress
from .transaction import PartialTxOutput, match_script_against_template, Sighash
from .logging import Logger
from .lnonion import (new_onion_packet, OnionFailureCode, calc_hops_data_for_payment,
Expand Down Expand Up @@ -812,7 +812,7 @@ async def channel_establishment_flow(
redeem_script = funding_output_script(local_config, remote_config)
funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
funding_output = PartialTxOutput.from_address_and_value(funding_address, funding_sat)
funding_tx.replace_dummy_output('channel', funding_address)
funding_tx.replace_output_address(DummyAddress.CHANNEL, funding_address)
# find and encrypt op_return data associated to funding_address
has_onchain_backup = self.lnworker and self.lnworker.has_recoverable_channels()
if has_onchain_backup:
Expand Down
13 changes: 5 additions & 8 deletions electrum/lnworker.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
from .lnrater import LNRater
from . import lnutil
from .lnutil import funding_output_script
from .bitcoin import redeem_script_to_address, get_dummy_address
from .bitcoin import redeem_script_to_address, DummyAddress
from .lnutil import (Outpoint, LNPeerAddr,
get_compressed_pubkey_from_bech32, extract_nodeid,
PaymentFailure, split_host_port, ConnStringFormatError,
Expand Down Expand Up @@ -882,11 +882,6 @@ def get_channel_objects(self) -> Mapping[bytes, AbstractChannel]:
def get_channel_by_id(self, channel_id: bytes) -> Optional[Channel]:
return self._channels.get(channel_id, None)

def get_channel_by_scid(self, scid: bytes) -> Optional[Channel]:
for chan in self._channels.values():
if chan.short_channel_id == scid:
return chan

def diagnostic_name(self):
return self.wallet.diagnostic_name()

Expand Down Expand Up @@ -1281,7 +1276,7 @@ def mktx_for_open_channel(
funding_sat: int,
node_id: bytes,
fee_est=None) -> PartialTransaction:
outputs = [PartialTxOutput.from_address_and_value(get_dummy_address('channel'), funding_sat)]
outputs = [PartialTxOutput.from_address_and_value(DummyAddress.CHANNEL, funding_sat)]
if self.has_recoverable_channels():
dummy_scriptpubkey = make_op_return(self.cb_data(node_id))
outputs.append(PartialTxOutput(scriptpubkey=dummy_scriptpubkey, value=0))
Expand Down Expand Up @@ -1334,6 +1329,8 @@ def get_channel_by_short_id(self, short_channel_id: bytes) -> Optional[Channel]:
for chan in self.channels.values():
if chan.short_channel_id == short_channel_id:
return chan
if chan.get_remote_scid_alias() == short_channel_id:
return chan

def can_pay_invoice(self, invoice: Invoice) -> bool:
assert invoice.is_lightning()
Expand Down Expand Up @@ -2562,7 +2559,7 @@ def suggest_swap_to_send(self, amount_sat, coins):
# check that we can send onchain
swap_server_mining_fee = 10000 # guessing, because we have not called get_pairs yet
swap_funding_sat = swap_recv_amount + swap_server_mining_fee
swap_output = PartialTxOutput.from_address_and_value(get_dummy_address('swap'), int(swap_funding_sat))
swap_output = PartialTxOutput.from_address_and_value(DummyAddress.SWAP, int(swap_funding_sat))
if not self.wallet.can_pay_onchain([swap_output], coins=coins):
continue
return (chan, swap_recv_amount)
Expand Down
4 changes: 3 additions & 1 deletion electrum/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
bfh, make_aiohttp_session, send_exception_to_crash_reporter,
is_hash256_str, is_non_negative_integer, MyEncoder, NetworkRetryManager,
nullcontext, error_text_str_to_safe_str)
from .bitcoin import COIN
from .bitcoin import COIN, DummyAddress, DummyAddressUsedInTxException
from . import constants
from . import blockchain
from . import bitcoin
Expand Down Expand Up @@ -923,6 +923,8 @@ async def broadcast_transaction(self, tx: 'Transaction', *, timeout=None) -> Non
raise RequestTimedOut()
if timeout is None:
timeout = self.get_network_timeout_seconds(NetworkTimeout.Urgent)
if any(DummyAddress.is_dummy_address(txout.address) for txout in tx.outputs()):
raise DummyAddressUsedInTxException("tried to broadcast tx with dummy address!")
try:
out = await self.interface.session.send_request('blockchain.transaction.broadcast', [tx.serialize()], timeout=timeout)
# note: both 'out' and exception messages are untrusted input from the server
Expand Down

0 comments on commit 2898445

Please sign in to comment.