mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-01 09:45:18 +00:00
Pass make_tx function to ConfirmTxDialog
- allow 'spend max' when opening a channel (fixes #5698) - display amount minus fee when 'max' buttons are pressed - estimate fee of channel funding using a template with dummy address
This commit is contained in:
parent
970bd4e95f
commit
78813dcb7d
11 changed files with 138 additions and 76 deletions
|
|
@ -52,6 +52,7 @@ from .wallet import Abstract_Wallet, create_new_wallet, restore_wallet_from_text
|
|||
from .address_synchronizer import TX_HEIGHT_LOCAL
|
||||
from .mnemonic import Mnemonic
|
||||
from .lnutil import SENT, RECEIVED
|
||||
from .lnutil import ln_dummy_address
|
||||
from .lnpeer import channel_id_from_funding_tx
|
||||
from .plugin import run_hook
|
||||
from .version import ELECTRUM_VERSION
|
||||
|
|
@ -922,8 +923,12 @@ class Commands:
|
|||
return True
|
||||
|
||||
@command('wpn')
|
||||
async def open_channel(self, connection_string, amount, channel_push=0, password=None, wallet: Abstract_Wallet = None):
|
||||
chan = await wallet.lnworker._open_channel_coroutine(connection_string, satoshis(amount), satoshis(channel_push), password)
|
||||
async def open_channel(self, connection_string, amount, push_amount=0, password=None, wallet: Abstract_Wallet = None):
|
||||
funding_sat = satoshis(amount)
|
||||
push_sat = satoshis(push_amount)
|
||||
dummy_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), funding_sat)
|
||||
funding_tx = wallet.mktx(outputs = [dummy_output], rbf=False, sign=False, nonlocal_only=True)
|
||||
chan = await wallet.lnworker._open_channel_coroutine(connection_string, funding_tx, funding_sat, push_sat, password)
|
||||
return chan.funding_outpoint.to_str()
|
||||
|
||||
@command('wn')
|
||||
|
|
@ -1037,7 +1042,7 @@ command_options = {
|
|||
'timeout': (None, "Timeout in seconds"),
|
||||
'force': (None, "Create new address beyond gap limit, if no more addresses are available."),
|
||||
'pending': (None, "Show only pending requests."),
|
||||
'channel_push':(None, 'Push initial amount (in BTC)'),
|
||||
'push_amount': (None, 'Push initial amount (in BTC)'),
|
||||
'expired': (None, "Show only expired requests."),
|
||||
'paid': (None, "Show only paid requests."),
|
||||
'show_addresses': (None, "Show input and output addresses"),
|
||||
|
|
|
|||
|
|
@ -5,15 +5,15 @@ from enum import IntEnum
|
|||
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit
|
||||
from PyQt5.QtWidgets import QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QPushButton
|
||||
|
||||
from electrum.util import inv_dict, bh2u, bfh
|
||||
from electrum.i18n import _
|
||||
from electrum.lnchannel import Channel
|
||||
from electrum.wallet import Abstract_Wallet
|
||||
from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError, format_short_channel_id
|
||||
from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError, format_short_channel_id, LN_MAX_FUNDING_SAT
|
||||
|
||||
from .util import MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton, WWLabel, WaitingDialog
|
||||
from .util import MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton, WWLabel, WaitingDialog, HelpLabel
|
||||
from .amountedit import BTCAmountEdit
|
||||
from .channel_details import ChannelDetailsDialog
|
||||
|
||||
|
|
@ -162,26 +162,38 @@ class ChannelsList(MyTreeView):
|
|||
def new_channel_dialog(self):
|
||||
lnworker = self.parent.wallet.lnworker
|
||||
d = WindowModalDialog(self.parent, _('Open Channel'))
|
||||
d.setMinimumWidth(700)
|
||||
vbox = QVBoxLayout(d)
|
||||
h = QGridLayout()
|
||||
vbox.addWidget(QLabel(_('Enter Remote Node ID or connection string or invoice')))
|
||||
local_nodeid = QLineEdit()
|
||||
local_nodeid.setMinimumWidth(700)
|
||||
local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey))
|
||||
local_nodeid.setReadOnly(True)
|
||||
local_nodeid.setCursorPosition(0)
|
||||
remote_nodeid = QLineEdit()
|
||||
local_amt_inp = BTCAmountEdit(self.parent.get_decimal_point)
|
||||
local_amt_inp.setAmount(200000)
|
||||
push_amt_inp = BTCAmountEdit(self.parent.get_decimal_point)
|
||||
push_amt_inp.setAmount(0)
|
||||
remote_nodeid.setMinimumWidth(700)
|
||||
amount_e = BTCAmountEdit(self.parent.get_decimal_point)
|
||||
# max button
|
||||
def spend_max():
|
||||
make_tx = self.parent.mktx_for_open_channel('!')
|
||||
tx = make_tx(None)
|
||||
amount = tx.output_value()
|
||||
amount = min(amount, LN_MAX_FUNDING_SAT)
|
||||
amount_e.setAmount(amount)
|
||||
amount_e.setFrozen(True)
|
||||
max_button = EnterButton(_("Max"), spend_max)
|
||||
max_button.setFixedWidth(100)
|
||||
max_button.setCheckable(True)
|
||||
h = QGridLayout()
|
||||
h.addWidget(QLabel(_('Your Node ID')), 0, 0)
|
||||
h.addWidget(local_nodeid, 0, 1)
|
||||
h.addWidget(QLabel(_('Remote Node ID or connection string or invoice')), 1, 0)
|
||||
h.addWidget(QLabel(_('Remote Node ID')), 1, 0)
|
||||
h.addWidget(remote_nodeid, 1, 1)
|
||||
h.addWidget(QLabel('Local amount'), 2, 0)
|
||||
h.addWidget(local_amt_inp, 2, 1)
|
||||
h.addWidget(QLabel('Push amount'), 3, 0)
|
||||
h.addWidget(push_amt_inp, 3, 1)
|
||||
h.addWidget(QLabel('Amount'), 2, 0)
|
||||
hbox = QHBoxLayout()
|
||||
hbox.addWidget(amount_e)
|
||||
hbox.addWidget(max_button)
|
||||
hbox.addStretch(1)
|
||||
h.addLayout(hbox, 2, 1)
|
||||
vbox.addLayout(h)
|
||||
ok_button = OkButton(d)
|
||||
ok_button.setDefault(True)
|
||||
|
|
@ -191,7 +203,6 @@ class ChannelsList(MyTreeView):
|
|||
remote_nodeid.setCursorPosition(0)
|
||||
if not d.exec_():
|
||||
return
|
||||
local_amt = local_amt_inp.get_amount()
|
||||
push_amt = push_amt_inp.get_amount()
|
||||
connect_contents = str(remote_nodeid.text()).strip()
|
||||
self.parent.open_channel(connect_contents, local_amt, push_amt)
|
||||
funding_sat = '!' if max_button.isChecked() else amount_e.get_amount()
|
||||
connect_str = str(remote_nodeid.text()).strip()
|
||||
self.parent.open_channel(connect_str, funding_sat, 0)
|
||||
|
|
|
|||
|
|
@ -51,18 +51,17 @@ if TYPE_CHECKING:
|
|||
|
||||
class TxEditor:
|
||||
|
||||
def __init__(self, window: 'ElectrumWindow', inputs, outputs, external_keypairs):
|
||||
def __init__(self, window: 'ElectrumWindow', make_tx, output_value, is_sweep):
|
||||
self.main_window = window
|
||||
self.outputs = outputs
|
||||
self.get_coins = inputs
|
||||
self.make_tx = make_tx
|
||||
self.output_value = output_value
|
||||
self.tx = None # type: Optional[Transaction]
|
||||
self.config = window.config
|
||||
self.wallet = window.wallet
|
||||
self.external_keypairs = external_keypairs
|
||||
self.not_enough_funds = False
|
||||
self.no_dynfee_estimates = False
|
||||
self.needs_update = False
|
||||
self.password_required = self.wallet.has_keystore_encryption() and not external_keypairs
|
||||
self.password_required = self.wallet.has_keystore_encryption() and not is_sweep
|
||||
self.main_window.gui_object.timer.timeout.connect(self.timer_actions)
|
||||
|
||||
def timer_actions(self):
|
||||
|
|
@ -86,17 +85,8 @@ class TxEditor:
|
|||
|
||||
def update_tx(self):
|
||||
fee_estimator = self.get_fee_estimator()
|
||||
is_sweep = bool(self.external_keypairs)
|
||||
coins = self.get_coins()
|
||||
# deepcopy outputs because '!' is converted to number
|
||||
outputs = copy.deepcopy(self.outputs)
|
||||
make_tx = lambda fee_est: self.wallet.make_unsigned_transaction(
|
||||
coins=coins,
|
||||
outputs=outputs,
|
||||
fee=fee_est,
|
||||
is_sweep=is_sweep)
|
||||
try:
|
||||
self.tx = make_tx(fee_estimator)
|
||||
self.tx = self.make_tx(fee_estimator)
|
||||
self.not_enough_funds = False
|
||||
self.no_dynfee_estimates = False
|
||||
except NotEnoughFunds:
|
||||
|
|
@ -107,7 +97,7 @@ class TxEditor:
|
|||
self.no_dynfee_estimates = True
|
||||
self.tx = None
|
||||
try:
|
||||
self.tx = make_tx(0)
|
||||
self.tx = self.make_tx(0)
|
||||
except BaseException:
|
||||
return
|
||||
except InternalAddressCorruption as e:
|
||||
|
|
@ -131,9 +121,9 @@ class TxEditor:
|
|||
class ConfirmTxDialog(TxEditor, WindowModalDialog):
|
||||
# set fee and return password (after pw check)
|
||||
|
||||
def __init__(self, window: 'ElectrumWindow', inputs, outputs, external_keypairs):
|
||||
def __init__(self, window: 'ElectrumWindow', make_tx, output_value, is_sweep):
|
||||
|
||||
TxEditor.__init__(self, window, inputs, outputs, external_keypairs)
|
||||
TxEditor.__init__(self, window, make_tx, output_value, is_sweep)
|
||||
WindowModalDialog.__init__(self, window, _("Confirm Transaction"))
|
||||
vbox = QVBoxLayout()
|
||||
self.setLayout(vbox)
|
||||
|
|
@ -218,9 +208,7 @@ class ConfirmTxDialog(TxEditor, WindowModalDialog):
|
|||
|
||||
def update(self):
|
||||
tx = self.tx
|
||||
output_values = [x.value for x in self.outputs]
|
||||
is_max = '!' in output_values
|
||||
amount = tx.output_value() if is_max else sum(output_values)
|
||||
amount = tx.output_value() if self.output_value == '!' else self.output_value
|
||||
self.amount_label.setText(self.main_window.format_amount_and_units(amount))
|
||||
|
||||
if self.not_enough_funds:
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ from electrum.simple_config import SimpleConfig
|
|||
from electrum.logging import Logger
|
||||
from electrum.util import PR_PAID, PR_UNPAID, PR_INFLIGHT, PR_FAILED
|
||||
from electrum.util import pr_expiration_values
|
||||
from electrum.lnutil import ln_dummy_address
|
||||
|
||||
from .exception_window import Exception_Hook
|
||||
from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit
|
||||
|
|
@ -1282,8 +1283,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||
def spend_max(self):
|
||||
if run_hook('abort_send', self):
|
||||
return
|
||||
outputs = self.payto_e.get_outputs(True)
|
||||
if not outputs:
|
||||
return
|
||||
self.max_button.setChecked(True)
|
||||
amount = sum(x.value_sats() for x in self.get_coins())
|
||||
make_tx = lambda fee_est: self.wallet.make_unsigned_transaction(
|
||||
coins=self.get_coins(),
|
||||
outputs=outputs,
|
||||
fee=fee_est,
|
||||
is_sweep=False)
|
||||
|
||||
tx = make_tx(None)
|
||||
amount = tx.output_value()#sum(x.value_sats() for x in self.get_coins())
|
||||
self.amount_e.setAmount(amount)
|
||||
## substract extra fee
|
||||
#__, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
|
||||
|
|
@ -1448,20 +1459,20 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||
outputs = []
|
||||
for invoice in invoices:
|
||||
outputs += invoice['outputs']
|
||||
self.pay_onchain_dialog(self.get_coins, outputs)
|
||||
self.pay_onchain_dialog(self.get_coins(), outputs)
|
||||
|
||||
def do_pay_invoice(self, invoice):
|
||||
if invoice['type'] == PR_TYPE_LN:
|
||||
self.pay_lightning_invoice(invoice['invoice'])
|
||||
elif invoice['type'] == PR_TYPE_ONCHAIN:
|
||||
outputs = invoice['outputs']
|
||||
self.pay_onchain_dialog(self.get_coins, outputs, invoice=invoice)
|
||||
self.pay_onchain_dialog(self.get_coins(), outputs, invoice=invoice)
|
||||
else:
|
||||
raise Exception('unknown invoice type')
|
||||
|
||||
def get_coins(self):
|
||||
def get_coins(self, nonlocal_only=False):
|
||||
coins = self.get_manually_selected_coins()
|
||||
return coins or self.wallet.get_spendable_coins(None)
|
||||
return coins or self.wallet.get_spendable_coins(None, nonlocal_only=nonlocal_only)
|
||||
|
||||
def get_manually_selected_coins(self) -> Sequence[PartialTxInput]:
|
||||
return self.utxo_list.get_spend_list()
|
||||
|
|
@ -1470,10 +1481,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||
# trustedcoin requires this
|
||||
if run_hook('abort_send', self):
|
||||
return
|
||||
is_sweep = bool(external_keypairs)
|
||||
make_tx = lambda fee_est: self.wallet.make_unsigned_transaction(
|
||||
coins=inputs,
|
||||
outputs=outputs,
|
||||
fee=fee_est,
|
||||
is_sweep=is_sweep)
|
||||
if self.config.get('advanced_preview'):
|
||||
self.preview_tx_dialog(inputs, outputs, invoice=invoice)
|
||||
self.preview_tx_dialog(make_tx, outputs, is_sweep=is_sweep, invoice=invoice)
|
||||
return
|
||||
d = ConfirmTxDialog(self, inputs, outputs, external_keypairs)
|
||||
|
||||
output_values = [x.value for x in outputs]
|
||||
output_value = '!' if '!' in output_values else sum(output_values)
|
||||
d = ConfirmTxDialog(self, make_tx, output_value, is_sweep)
|
||||
d.update_tx()
|
||||
if d.not_enough_funds:
|
||||
self.show_message(_('Not Enough Funds'))
|
||||
|
|
@ -1487,10 +1507,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||
self.broadcast_or_show(tx, invoice=invoice)
|
||||
self.sign_tx_with_password(tx, sign_done, password, external_keypairs)
|
||||
else:
|
||||
self.preview_tx_dialog(inputs, outputs, external_keypairs=external_keypairs, invoice=invoice)
|
||||
self.preview_tx_dialog(make_tx, outputs, is_sweep=is_sweep, invoice=invoice)
|
||||
|
||||
def preview_tx_dialog(self, inputs, outputs, external_keypairs=None, invoice=None):
|
||||
d = PreviewTxDialog(inputs, outputs, external_keypairs, window=self, invoice=invoice)
|
||||
def preview_tx_dialog(self, make_tx, outputs, is_sweep=False, invoice=None):
|
||||
d = PreviewTxDialog(make_tx, outputs, is_sweep, window=self, invoice=invoice)
|
||||
d.show()
|
||||
|
||||
def broadcast_or_show(self, tx, invoice=None):
|
||||
|
|
@ -1572,21 +1592,26 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||
WaitingDialog(self, _('Broadcasting transaction...'),
|
||||
broadcast_thread, broadcast_done, self.on_error)
|
||||
|
||||
def open_channel(self, connect_str, local_amt, push_amt):
|
||||
def mktx_for_open_channel(self, funding_sat):
|
||||
coins = self.get_coins(nonlocal_only=True)
|
||||
make_tx = partial(self.wallet.lnworker.mktx_for_open_channel, coins, funding_sat)
|
||||
return make_tx
|
||||
|
||||
def open_channel(self, connect_str, funding_sat, push_amt):
|
||||
# use ConfirmTxDialog
|
||||
# we need to know the fee before we broadcast, because the txid is required
|
||||
# however, the user must not be allowed to broadcast early
|
||||
funding_sat = local_amt + push_amt
|
||||
inputs = self.get_coins
|
||||
outputs = [PartialTxOutput.from_address_and_value(self.wallet.dummy_address(), funding_sat)]
|
||||
d = ConfirmTxDialog(self, inputs, outputs, None)
|
||||
cancelled, is_send, password, tx = d.run()
|
||||
make_tx = self.mktx_for_open_channel(funding_sat)
|
||||
d = ConfirmTxDialog(self, make_tx, funding_sat, False)
|
||||
cancelled, is_send, password, funding_tx = d.run()
|
||||
if not is_send:
|
||||
return
|
||||
if cancelled:
|
||||
return
|
||||
# read funding_sat from tx; converts '!' to int value
|
||||
funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
|
||||
def task():
|
||||
return self.wallet.lnworker.open_channel(connect_str, local_amt, push_amt, password)
|
||||
return self.wallet.lnworker.open_channel(connect_str, funding_tx, funding_sat, push_amt, password)
|
||||
def on_success(chan):
|
||||
n = chan.constraints.funding_txn_minimum_depth
|
||||
message = '\n'.join([
|
||||
|
|
@ -2647,7 +2672,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||
scriptpubkey = bfh(bitcoin.address_to_script(addr))
|
||||
outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value='!')]
|
||||
self.warn_if_watching_only()
|
||||
self.pay_onchain_dialog(lambda: coins, outputs, invoice=None, external_keypairs=keypairs)
|
||||
self.pay_onchain_dialog(coins, outputs, invoice=None, external_keypairs=keypairs)
|
||||
|
||||
def _do_import(self, title, header_layout, func):
|
||||
text = text_dialog(self, title, header_layout, _('Import'), allow_multi=True)
|
||||
|
|
|
|||
|
|
@ -602,8 +602,8 @@ class TxDialog(BaseTxDialog):
|
|||
|
||||
class PreviewTxDialog(BaseTxDialog, TxEditor):
|
||||
|
||||
def __init__(self, inputs, outputs, external_keypairs, *, window: 'ElectrumWindow', invoice):
|
||||
TxEditor.__init__(self, window, inputs, outputs, external_keypairs)
|
||||
def __init__(self, make_tx, outputs, is_sweep, *, window: 'ElectrumWindow', invoice):
|
||||
TxEditor.__init__(self, window, make_tx, outputs, is_sweep)
|
||||
BaseTxDialog.__init__(self, parent=window, invoice=invoice, desc='', prompt_if_unsaved=False, finalized=False)
|
||||
self.update_tx()
|
||||
self.update()
|
||||
|
|
|
|||
|
|
@ -46,10 +46,12 @@ from .lntransport import LNTransport, LNTransportBase
|
|||
from .lnmsg import encode_msg, decode_msg
|
||||
from .interface import GracefulDisconnect, NetworkException
|
||||
from .lnrouter import fee_for_edge_msat
|
||||
from .lnutil import ln_dummy_address
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .lnworker import LNWorker, LNGossip, LNWallet
|
||||
from .lnrouter import RouteEdge
|
||||
from .transaction import PartialTransaction
|
||||
|
||||
|
||||
LN_P2P_NETWORK_TIMEOUT = 20
|
||||
|
|
@ -479,12 +481,8 @@ class Peer(Logger):
|
|||
return local_config
|
||||
|
||||
@log_exceptions
|
||||
async def channel_establishment_flow(self, password: Optional[str], funding_sat: int,
|
||||
async def channel_establishment_flow(self, password: Optional[str], funding_tx: 'PartialTransaction', funding_sat: int,
|
||||
push_msat: int, temp_channel_id: bytes) -> Channel:
|
||||
wallet = self.lnworker.wallet
|
||||
# dry run creating funding tx to see if we even have enough funds
|
||||
funding_tx_test = wallet.mktx(outputs=[PartialTxOutput.from_address_and_value(wallet.dummy_address(), funding_sat)],
|
||||
password=password, nonlocal_only=True)
|
||||
await asyncio.wait_for(self.initialized.wait(), LN_P2P_NETWORK_TIMEOUT)
|
||||
feerate = self.lnworker.current_feerate_per_kw()
|
||||
local_config = self.make_local_config(funding_sat, push_msat, LOCAL)
|
||||
|
|
@ -555,16 +553,19 @@ class Peer(Logger):
|
|||
initial_msat=push_msat,
|
||||
reserve_sat = remote_reserve_sat,
|
||||
htlc_minimum_msat = htlc_min,
|
||||
|
||||
next_per_commitment_point=remote_per_commitment_point,
|
||||
current_per_commitment_point=None,
|
||||
revocation_store=their_revocation_store,
|
||||
)
|
||||
# create funding tx
|
||||
# replace dummy output in funding tx
|
||||
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 = wallet.mktx(outputs=[funding_output], password=password, nonlocal_only=True)
|
||||
dummy_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), funding_sat)
|
||||
funding_tx.outputs().remove(dummy_output)
|
||||
funding_tx.add_outputs([funding_output])
|
||||
funding_tx.set_rbf(False)
|
||||
self.lnworker.wallet.sign_transaction(funding_tx, password)
|
||||
funding_txid = funding_tx.txid()
|
||||
funding_index = funding_tx.outputs().index(funding_output)
|
||||
# remote commitment transaction
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ if TYPE_CHECKING:
|
|||
HTLC_TIMEOUT_WEIGHT = 663
|
||||
HTLC_SUCCESS_WEIGHT = 703
|
||||
|
||||
LN_MAX_FUNDING_SAT = pow(2, 24)
|
||||
|
||||
# dummy address for fee estimation of funding tx
|
||||
def ln_dummy_address():
|
||||
return redeem_script_to_address('p2wsh', '')
|
||||
|
||||
class Keypair(NamedTuple):
|
||||
pubkey: bytes
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from . import keystore
|
|||
from .util import profiler
|
||||
from .util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED
|
||||
from .util import PR_TYPE_LN
|
||||
from .lnutil import LN_MAX_FUNDING_SAT
|
||||
from .keystore import BIP32_KeyStore
|
||||
from .bitcoin import COIN
|
||||
from .transaction import Transaction
|
||||
|
|
@ -48,6 +49,8 @@ from .lnutil import (Outpoint, LNPeerAddr,
|
|||
NUM_MAX_EDGES_IN_PAYMENT_PATH, SENT, RECEIVED, HTLCOwner,
|
||||
UpdateAddHtlc, Direction, LnLocalFeatures, format_short_channel_id,
|
||||
ShortChannelID)
|
||||
from .lnutil import ln_dummy_address
|
||||
from .transaction import PartialTxOutput
|
||||
from .lnonion import OnionFailureCode
|
||||
from .lnmsg import decode_msg
|
||||
from .i18n import _
|
||||
|
|
@ -768,13 +771,14 @@ class LNWallet(LNWorker):
|
|||
await self.force_close_channel(chan.channel_id)
|
||||
|
||||
@log_exceptions
|
||||
async def _open_channel_coroutine(self, connect_str, local_amount_sat, push_sat, password):
|
||||
async def _open_channel_coroutine(self, connect_str, funding_tx, funding_sat, push_sat, password):
|
||||
peer = await self.add_peer(connect_str)
|
||||
# peer might just have been connected to
|
||||
await asyncio.wait_for(peer.initialized.wait(), LN_P2P_NETWORK_TIMEOUT)
|
||||
chan = await peer.channel_establishment_flow(
|
||||
password,
|
||||
funding_sat=local_amount_sat + push_sat,
|
||||
funding_tx=funding_tx,
|
||||
funding_sat=funding_sat,
|
||||
push_msat=push_sat * 1000,
|
||||
temp_channel_id=os.urandom(32))
|
||||
self.save_channel(chan)
|
||||
|
|
@ -805,8 +809,19 @@ class LNWallet(LNWorker):
|
|||
peer = await self._add_peer(host, port, node_id)
|
||||
return peer
|
||||
|
||||
def open_channel(self, connect_str, local_amt_sat, push_amt_sat, password=None, timeout=20):
|
||||
coro = self._open_channel_coroutine(connect_str, local_amt_sat, push_amt_sat, password)
|
||||
def mktx_for_open_channel(self, coins, funding_sat, fee_est):
|
||||
dummy_address = ln_dummy_address()
|
||||
outputs = [PartialTxOutput.from_address_and_value(dummy_address, funding_sat)]
|
||||
tx = self.wallet.make_unsigned_transaction(
|
||||
coins=coins,
|
||||
outputs=outputs,
|
||||
fee=fee_est)
|
||||
tx.set_rbf(False)
|
||||
return tx
|
||||
|
||||
def open_channel(self, connect_str, funding_tx, funding_sat, push_amt_sat, password=None, timeout=20):
|
||||
assert funding_sat <= LN_MAX_FUNDING_SAT
|
||||
coro = self._open_channel_coroutine(connect_str, funding_tx, funding_sat, push_amt_sat, password)
|
||||
fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
|
||||
try:
|
||||
chan = fut.result(timeout=timeout)
|
||||
|
|
|
|||
|
|
@ -109,8 +109,8 @@ fi
|
|||
|
||||
if [[ $1 == "open" ]]; then
|
||||
bob_node=$($bob nodeid)
|
||||
channel_id1=$($alice open_channel $bob_node 0.001 --channel_push 0.001)
|
||||
channel_id2=$($carol open_channel $bob_node 0.001 --channel_push 0.001)
|
||||
channel_id1=$($alice open_channel $bob_node 0.002 --push_amount 0.001)
|
||||
channel_id2=$($carol open_channel $bob_node 0.002 --push_amount 0.001)
|
||||
echo "mining 3 blocks"
|
||||
new_blocks 3
|
||||
sleep 10 # time for channelDB
|
||||
|
|
|
|||
|
|
@ -879,6 +879,14 @@ class Transaction:
|
|||
script = bitcoin.address_to_script(addr)
|
||||
return self.get_output_idxs_from_scriptpubkey(script)
|
||||
|
||||
def output_value_for_address(self, addr):
|
||||
# assumes exactly one output has that address
|
||||
for o in self.outputs():
|
||||
if o.address == addr:
|
||||
return o.value
|
||||
else:
|
||||
raise Exception('output not found', addr)
|
||||
|
||||
|
||||
def convert_raw_tx_to_hex(raw: Union[str, bytes]) -> str:
|
||||
"""Sanitizes tx-describing input (hex/base43/base64) into
|
||||
|
|
|
|||
|
|
@ -917,6 +917,10 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
def make_unsigned_transaction(self, *, coins: Sequence[PartialTxInput],
|
||||
outputs: List[PartialTxOutput], fee=None,
|
||||
change_addr: str = None, is_sweep=False) -> PartialTransaction:
|
||||
|
||||
# prevent side-effect with '!'
|
||||
outputs = copy.deepcopy(outputs)
|
||||
|
||||
# check outputs
|
||||
i_max = None
|
||||
for i, o in enumerate(outputs):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue