Skip to content

Commit

Permalink
add future transactions to wallet, in order to provide user feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
ecdsa committed Dec 10, 2024
1 parent daadbb9 commit 3ad4406
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 27 deletions.
6 changes: 5 additions & 1 deletion electrum/lnwatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ def inspect_tx_candidate(self, outpoint, n: int) -> Dict[str, str]:
"""
prev_txid, index = outpoint.split(':')
spender_txid = self.adb.db.get_spent_outpoint(prev_txid, int(index))
# discard local spenders
tx_mined_status = self.adb.get_tx_height(spender_txid)
if tx_mined_status.height in [TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE]:
spender_txid = None
result = {outpoint:spender_txid}
if n == 0:
if spender_txid is None:
Expand All @@ -263,7 +267,7 @@ def inspect_tx_candidate(self, outpoint, n: int) -> Dict[str, str]:
# if tx input is not a first-stage HTLC, we can stop recursion
if len(spender_tx.inputs()) != 1:
return result
o = spender_tx.inputs()[0]
o = spender_tx.inputs()[0] # fixme?
witness = o.witness_elements()
if not witness:
# This can happen if spender_tx is a local unsigned tx in the wallet history, e.g.:
Expand Down
22 changes: 5 additions & 17 deletions electrum/submarine_swaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from .lnaddr import lndecode
from .json_db import StoredObject, stored_in
from . import constants
from .address_synchronizer import TX_HEIGHT_LOCAL
from .address_synchronizer import TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE
from .i18n import _

from .bitcoin import construct_script
Expand Down Expand Up @@ -346,29 +346,17 @@ async def _claim_swap(self, swap: SwapData) -> None:
self._add_or_reindex_swap(swap) # to update _swaps_by_funding_outpoint
funding_height = self.lnwatcher.adb.get_tx_height(txin.prevout.txid.hex())
spent_height = txin.spent_height
# discard local spenders
if spent_height in [TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE]:
spent_height = None

if spent_height is not None:
swap.spending_txid = txin.spent_txid
if spent_height > 0:
if current_height - spent_height > REDEEM_AFTER_DOUBLE_SPENT_DELAY:
self.logger.info(f'stop watching swap {swap.lockup_address}')
self.lnwatcher.remove_callback(swap.lockup_address)
swap.is_redeemed = True
elif spent_height == TX_HEIGHT_LOCAL:
if funding_height.conf > 0 or (swap.is_reverse and self.wallet.config.LIGHTNING_ALLOW_INSTANT_SWAPS):
tx = self.lnwatcher.adb.get_transaction(txin.spent_txid)
try:
await self.network.broadcast_transaction(tx)
except TxBroadcastError:
self.logger.info(f'error broadcasting claim tx {txin.spent_txid}')
elif funding_height.height == TX_HEIGHT_LOCAL:
# the funding tx was double spent.
# this will remove both funding and child (spending tx) from adb
self.lnwatcher.adb.remove_transaction(swap.funding_txid)
swap.funding_txid = None
swap.spending_txid = None
else:
# spending tx is in mempool
pass

if not swap.is_reverse:
if swap.preimage is None and spent_height is not None:
Expand Down
69 changes: 60 additions & 9 deletions electrum/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -1151,9 +1151,8 @@ def get_onchain_history(self, *, domain=None):
'date': timestamp_to_datetime(hist_item.tx_mined_status.timestamp),
'label': self.get_label_for_txid(hist_item.txid),
'txpos_in_block': hist_item.tx_mined_status.txpos,
'wanted_height': hist_item.tx_mined_status.wanted_height,
}
if wanted_height := hist_item.tx_mined_status.wanted_height:
d['wanted_height'] = wanted_height
yield d

def create_invoice(self, *, outputs: List[PartialTxOutput], message, pr, URI) -> Invoice:
Expand Down Expand Up @@ -1412,6 +1411,7 @@ def sort_key(x):
parent['date'] = timestamp_to_datetime(tx_item['timestamp'])
parent['height'] = tx_item['height']
parent['confirmations'] = tx_item['confirmations']
parent['wanted_height'] = tx_item.get('wanted_height')
parent['children'].append(tx_item)

now = time.time()
Expand Down Expand Up @@ -3320,8 +3320,10 @@ def add_sweep_info(self, sweep_info: 'SweepInfo'):
# early return if it is spent, because self.processing is not persisted
prevout = txin.prevout.to_str()
prev_txid, index = prevout.split(':')
if self.adb.db.get_spent_outpoint(prev_txid, int(index)):
return
if spender_txid := self.adb.db.get_spent_outpoint(prev_txid, int(index)):
tx_mined_status = self.adb.get_tx_height(spender_txid)
if tx_mined_status.height not in [TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE]:
return
self.processing.add(txin.prevout)
self.logger.info(f'add_sweep_info: {sweep_info.name} {sweep_info.txin.prevout.to_str()}')
self.batch_inputs[txin.prevout] = sweep_info
Expand All @@ -3338,10 +3340,21 @@ def to_pay_after(self, tx):
return [x for x in self.batch_payments if x not in tx.outputs()]

def to_sweep_after(self, tx):
if not tx:
return self.batch_inputs
tx_prevouts = set(txin.prevout for txin in tx.inputs())
return dict((k,v) for k,v in self.batch_inputs.items() if k not in tx_prevouts)
tx_prevouts = set(txin.prevout for txin in tx.inputs()) if tx else set()
result = []
for k,v in self.batch_inputs.items():
prevout = v.txin.prevout
prev_txid, index = prevout.to_str().split(':')
if not self.adb.db.get_transaction(prev_txid):
continue
if spender_txid := self.adb.db.get_spent_outpoint(prev_txid, int(index)):
tx_mined_status = self.adb.get_tx_height(spender_txid)
if tx_mined_status.height not in [TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE]:
continue
if prevout in tx_prevouts:
continue
result.append((k,v))
return dict(result)

def should_bump_fee(self, base_tx):
# fixme: since batch_txs is not persisted, we do not bump after a restart
Expand Down Expand Up @@ -3401,7 +3414,13 @@ async def manage_batch_payments(self):
base_tx = self.batch_txs[-1] if self.batch_txs else None
to_pay = self.to_pay_after(base_tx)
to_sweep = self.to_sweep_after(base_tx)
to_sweep_now = dict([(k,v) for k,v in to_sweep.items() if self.can_broadcast(v)[0] is True])
to_sweep_now = {}
for k, v in to_sweep.items():
can_broadcast, wanted_height = self.can_broadcast(v)
if can_broadcast:
to_sweep_now[k] = v
else:
self.add_future_tx(v, wanted_height)
if not to_pay and not to_sweep_now and not self.should_bump_fee(base_tx):
continue
try:
Expand Down Expand Up @@ -3505,6 +3524,38 @@ async def maybe_broadcast_legacy_htlc_txs(self):
self.adb.add_transaction(tx)
self.batch_inputs.pop(sweep_info.txin.prevout)

def add_future_tx(self, sweep_info, wanted_height):
""" add local tx to provide user feedback """
txin = copy.deepcopy(sweep_info.txin)
prevout = txin.prevout.to_str()
prev_txid, index = prevout.split(':')
if self.adb.db.get_spent_outpoint(prev_txid, int(index)):
return
name = sweep_info.name
prevout = txin.prevout.to_str()
new_tx = self.create_transaction(
inputs=[txin],
outputs=[],
password=None,
fee=0,
)
# we may have a tx with a different fee, in which case it will be replaced
try:
tx_was_added = self.adb.add_transaction(new_tx)#, is_new=(old_tx is None))
except Exception as e:
self.logger.info(f'could not add future tx: {name}. prevout: {prevout} {str(e)}')
tx_was_added = False
if tx_was_added:
self.logger.info(f'added future tx: {name}. prevout: {prevout}')

# set future tx regardless of tx_was_added, because it is not persisted
# (and wanted_height can change if input of CSV was not mined before)
self.adb.set_future_tx(new_tx.txid(), wanted_height=wanted_height)
if tx_was_added:
self.set_label(new_tx.txid(), name)
#if old_tx and old_tx.txid() != new_tx.txid():
# self.lnworker.wallet.set_label(old_tx.txid(), None)
util.trigger_callback('wallet_updated', self.lnworker.wallet)

class Simple_Wallet(Abstract_Wallet):
# wallet with a single keystore
Expand Down

0 comments on commit 3ad4406

Please sign in to comment.