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