2
2
# Distributed under the MIT software license, see the accompanying
3
3
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
4
4
5
- from typing import NamedTuple , Iterable , TYPE_CHECKING
6
- import copy
7
- import asyncio
5
+ from typing import TYPE_CHECKING
8
6
from enum import IntEnum , auto
9
- from typing import NamedTuple , Dict
10
7
11
- from . import util
12
- from .util import log_exceptions , ignore_exceptions , TxMinedInfo
8
+ from .util import log_exceptions , ignore_exceptions , TxMinedInfo , BelowDustLimit
13
9
from .util import EventListener , event_listener
14
10
from .address_synchronizer import AddressSynchronizer , TX_HEIGHT_LOCAL , TX_HEIGHT_UNCONF_PARENT , TX_HEIGHT_UNCONFIRMED , TX_HEIGHT_FUTURE
15
- from .transaction import Transaction , TxOutpoint , PartialTransaction
11
+ from .transaction import Transaction , TxOutpoint
16
12
from .logging import Logger
17
- from .bitcoin import dust_threshold
18
13
from .fee_policy import FeePolicy
19
14
20
15
@@ -75,6 +70,13 @@ def add_callback(self, address, callback):
75
70
async def on_event_blockchain_updated (self , * args ):
76
71
await self .trigger_callbacks ()
77
72
73
+ @event_listener
74
+ async def on_event_wallet_updated (self , wallet ):
75
+ # called if we add local tx
76
+ if wallet .adb != self .adb :
77
+ return
78
+ await self .trigger_callbacks ()
79
+
78
80
@event_listener
79
81
async def on_event_adb_added_verified_tx (self , adb , tx_hash ):
80
82
if adb != self .adb :
@@ -141,6 +143,10 @@ def get_spender(self, outpoint) -> str:
141
143
"""
142
144
prev_txid , index = outpoint .split (':' )
143
145
spender_txid = self .adb .db .get_spent_outpoint (prev_txid , int (index ))
146
+ # discard local spenders
147
+ tx_mined_status = self .adb .get_tx_height (spender_txid )
148
+ if tx_mined_status .height in [TX_HEIGHT_LOCAL , TX_HEIGHT_FUTURE ]:
149
+ spender_txid = None
144
150
if not spender_txid :
145
151
return
146
152
spender_tx = self .adb .get_transaction (spender_txid )
@@ -212,18 +218,6 @@ async def update_channel_state(self, *, funding_outpoint: str, funding_txid: str
212
218
keep_watching = keep_watching )
213
219
await self .lnworker .handle_onchain_state (chan )
214
220
215
- def is_dust (self , sweep_info ):
216
- if sweep_info .name in ['local_anchor' , 'remote_anchor' ]:
217
- return False
218
- if sweep_info .txout is not None :
219
- return False
220
- value = sweep_info .txin ._trusted_value_sats
221
- witness_size = len (sweep_info .txin .make_witness (71 * b'\x00 ' ))
222
- tx_size_vbytes = 84 + witness_size // 4 # assumes no batching, sweep to p2wpkh
223
- self .logger .info (f'{ sweep_info .name } size = { tx_size_vbytes } ' )
224
- fee = self .fee_policy .estimate_fee (tx_size_vbytes , network = self .network , allow_fallback_to_static_rates = True )
225
- return value - fee <= dust_threshold ()
226
-
227
221
@log_exceptions
228
222
async def sweep_commitment_transaction (self , funding_outpoint , closing_tx ) -> bool :
229
223
"""This function is called when a channel was closed. In this case
@@ -236,19 +230,16 @@ async def sweep_commitment_transaction(self, funding_outpoint, closing_tx) -> bo
236
230
return False
237
231
# detect who closed and get information about how to claim outputs
238
232
sweep_info_dict = chan .sweep_ctx (closing_tx )
239
- self .logger .info (f"do_breach_remedy: { [x .name for x in sweep_info_dict .values ()]} " )
233
+ # self.logger.info(f"do_breach_remedy: {[x.name for x in sweep_info_dict.values()]}")
240
234
keep_watching = False if sweep_info_dict else not self .is_deeply_mined (closing_tx .txid ())
241
-
242
235
# create and broadcast transactions
243
236
for prevout , sweep_info in sweep_info_dict .items ():
244
- if self .is_dust (sweep_info ):
245
- continue
246
237
prev_txid , prev_index = prevout .split (':' )
247
238
name = sweep_info .name + ' ' + chan .get_id_for_log ()
248
239
self .lnworker .wallet .set_default_label (prevout , name )
249
240
if not self .adb .get_transaction (prev_txid ):
250
241
# do not keep watching if prevout does not exist
251
- self .logger .info (f'prevout does not exist for { name } : { prev_txid } ' )
242
+ self .logger .info (f'prevout does not exist for { name } : { prevout } ' )
252
243
continue
253
244
spender_txid = self .get_spender (prevout )
254
245
spender_tx = self .adb .get_transaction (spender_txid ) if spender_txid else None
@@ -261,116 +252,21 @@ async def sweep_commitment_transaction(self, funding_outpoint, closing_tx) -> bo
261
252
if htlc_tx_spender :
262
253
keep_watching |= not self .is_deeply_mined (htlc_tx_spender )
263
254
else :
264
- keep_watching = True
265
- await self .maybe_redeem (prevout2 , htlc_sweep_info , name )
255
+ keep_watching |= self .maybe_redeem (htlc_sweep_info )
266
256
# extract preimage
267
257
keep_watching |= not self .is_deeply_mined (spender_txid )
268
258
txin_idx = spender_tx .get_input_idx_that_spent_prevout (TxOutpoint .from_str (prevout ))
269
259
assert txin_idx is not None
270
260
spender_txin = spender_tx .inputs ()[txin_idx ]
271
261
chan .extract_preimage_from_htlc_txin (spender_txin )
272
262
else :
273
- keep_watching = True
274
- # broadcast or maybe update our own tx
275
- await self .maybe_redeem (prevout , sweep_info , name )
276
-
263
+ keep_watching |= self .maybe_redeem (sweep_info )
277
264
return keep_watching
278
265
279
- def get_redeem_tx (self , prevout : str , sweep_info : 'SweepInfo' , name : str ):
280
- # check if redeem tx needs to be updated
281
- # if it is in the mempool, we need to check fee rise
282
- txid = self .get_spender (prevout )
283
- old_tx = self .adb .get_transaction (txid )
284
- assert old_tx is not None or txid is None
285
- tx_depth = self .get_tx_mined_depth (txid ) if txid else None
286
- if txid and tx_depth not in [TxMinedDepth .FREE , TxMinedDepth .MEMPOOL ]:
287
- assert old_tx is not None
288
- return old_tx , None
289
- # fixme: deepcopy is needed because tx.serialize() is destructive
290
- inputs = [copy .deepcopy (sweep_info .txin )]
291
- outputs = [sweep_info .txout ] if sweep_info .txout else []
292
- if sweep_info .name == 'first-stage-htlc' :
293
- new_tx = PartialTransaction .from_io (inputs , outputs , locktime = sweep_info .cltv_abs , version = 2 )
294
- self .lnworker .wallet .sign_transaction (new_tx , password = None , ignore_warnings = True )
295
- else :
296
- # password is needed for 1st stage htlc tx with anchors because we add inputs
297
- password = self .lnworker .wallet .get_unlocked_password ()
298
- new_tx = self .lnworker .wallet .create_transaction (
299
- fee_policy = self .fee_policy ,
300
- inputs = inputs ,
301
- outputs = outputs ,
302
- password = password ,
303
- locktime = sweep_info .cltv_abs ,
304
- BIP69_sort = False ,
305
- )
306
- if new_tx is None :
307
- self .logger .info (f'{ name } could not claim output: { prevout } , dust' )
308
- assert old_tx is not None
309
- return old_tx , None
310
- if txid is None :
311
- return None , new_tx
312
- elif tx_depth == TxMinedDepth .MEMPOOL :
313
- delta = new_tx .get_fee () - self .adb .get_tx_fee (txid )
314
- if delta > 1 :
315
- self .logger .info (f'increasing fee of mempool tx { name } : { prevout } ' )
316
- return old_tx , new_tx
317
- else :
318
- assert old_tx is not None
319
- return old_tx , None
320
- elif tx_depth == TxMinedDepth .FREE :
321
- # return new tx, even if it is equal to old_tx,
322
- # because we need to test if it can be broadcast
323
- return old_tx , new_tx
324
- else :
325
- assert old_tx is not None
326
- return old_tx , None
327
-
328
- async def maybe_redeem (self , prevout , sweep_info : 'SweepInfo' , name : str ) -> None :
329
- old_tx , new_tx = self .get_redeem_tx (prevout , sweep_info , name )
330
- if new_tx is None :
331
- return
332
- prev_txid , prev_index = prevout .split (':' )
333
- can_broadcast = True
334
- local_height = self .network .get_local_height ()
335
- if sweep_info .cltv_abs :
336
- wanted_height = sweep_info .cltv_abs
337
- if wanted_height - local_height > 0 :
338
- can_broadcast = False
339
- # self.logger.debug(f"pending redeem for {prevout}. waiting for {name}: CLTV ({local_height=}, {wanted_height=})")
340
- if sweep_info .csv_delay :
341
- prev_height = self .adb .get_tx_height (prev_txid )
342
- if prev_height .height > 0 :
343
- wanted_height = prev_height .height + sweep_info .csv_delay - 1
344
- else :
345
- wanted_height = local_height + sweep_info .csv_delay
346
- if wanted_height - local_height > 0 :
347
- can_broadcast = False
348
- # self.logger.debug(
349
- # f"pending redeem for {prevout}. waiting for {name}: CSV "
350
- # f"({local_height=}, {wanted_height=}, {prev_height.height=}, {sweep_info.csv_delay=})")
351
- if can_broadcast :
352
- self .logger .info (f'we can broadcast: { name } ' )
353
- if await self .network .try_broadcasting (new_tx , name ):
354
- tx_was_added = self .adb .add_transaction (new_tx , is_new = (old_tx is None ))
355
- else :
356
- tx_was_added = False
357
- else :
358
- # we may have a tx with a different fee, in which case it will be replaced
359
- if not old_tx or (old_tx and old_tx .txid () != new_tx .txid ()):
360
- try :
361
- tx_was_added = self .adb .add_transaction (new_tx , is_new = (old_tx is None ))
362
- except Exception as e :
363
- self .logger .info (f'could not add future tx: { name } . prevout: { prevout } { str (e )} ' )
364
- tx_was_added = False
365
- if tx_was_added :
366
- self .logger .info (f'added redeem tx: { name } . prevout: { prevout } ' )
367
- else :
368
- tx_was_added = False
369
- # set future tx regardless of tx_was_added, because it is not persisted
370
- # (and wanted_height can change if input of CSV was not mined before)
371
- self .adb .set_future_tx (new_tx .txid (), wanted_height = wanted_height )
372
- if tx_was_added :
373
- self .lnworker .wallet .set_label (new_tx .txid (), name )
374
- if old_tx and old_tx .txid () != new_tx .txid ():
375
- self .lnworker .wallet .set_label (old_tx .txid (), None )
376
- util .trigger_callback ('wallet_updated' , self .lnworker .wallet )
266
+ def maybe_redeem (self , sweep_info : 'SweepInfo' ) -> bool :
267
+ """ returns whether it was added """
268
+ try :
269
+ self .lnworker .wallet .txbatcher .add_sweep_input ('lnwatcher' , sweep_info , self .fee_policy )
270
+ except BelowDustLimit :
271
+ return False
272
+ return True
0 commit comments