commit 8ba7e6806428a90ffe01293e2b0b8bd340d07c6b
parent f8019d9b6ca9d1e1e656f057aad1435975616ad5
Author: ThomasV <thomasv@electrum.org>
Date: Sat, 2 May 2020 11:39:21 +0200
fix #6122: extract preimage from on-chain htlc_tx
Diffstat:
5 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
@@ -956,6 +956,21 @@ class Channel(AbstractChannel):
payment_attempt = self._receive_fail_reasons.get(htlc.htlc_id)
self.lnworker.payment_failed(self, htlc.payment_hash, payment_attempt)
+ def extract_preimage_from_htlc_tx(self, tx):
+ witness = tx.inputs()[0].witness_elements()
+ if len(witness) != 5:
+ return
+ preimage = witness[3]
+ payment_hash = sha256(preimage)
+ for direction, htlc in self.hm.get_htlcs_in_oldest_unrevoked_ctx(REMOTE):
+ if htlc.payment_hash == payment_hash:
+ self.logger.info(f'found preimage for {payment_hash.hex()} in tx witness')
+ self.lnworker.save_preimage(payment_hash, preimage)
+ if direction == RECEIVED:
+ self.lnworker.payment_sent(self, payment_hash)
+ else:
+ self.lnworker.payment_received(self, payment_hash)
+
def balance(self, whose: HTLCOwner, *, ctx_owner=HTLCOwner.LOCAL, ctn: int = None) -> int:
assert type(whose) is HTLCOwner
initial = self.config[whose].initial_msat
diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py
@@ -364,7 +364,7 @@ class LNWalletWatcher(LNWatcher):
# detect who closed and set sweep_info
sweep_info_dict = chan.sweep_ctx(closing_tx)
keep_watching = False if sweep_info_dict else not self.is_deeply_mined(closing_tx.txid())
- self.logger.info(f'(chan {chan.get_id_for_log()}) sweep_info_dict length: {len(sweep_info_dict)}')
+ self.logger.info(f'(chan {chan.get_id_for_log()}) sweep_info_dict {[x.name for x in sweep_info_dict.values()]}')
# create and broadcast transaction
for prevout, sweep_info in sweep_info_dict.items():
name = sweep_info.name + ' ' + chan.get_id_for_log()
@@ -387,6 +387,7 @@ class LNWalletWatcher(LNWatcher):
else:
self.logger.info(f'(chan {chan.get_id_for_log()}) outpoint already spent {name}: {prevout}')
keep_watching |= not self.is_deeply_mined(spender_txid)
+ chan.extract_preimage_from_htlc_tx(spender_tx)
else:
self.logger.info(f'(chan {chan.get_id_for_log()}) trying to redeem {name}: {prevout}')
await self.try_redeem(prevout, sweep_info, name)
diff --git a/electrum/tests/regtest.py b/electrum/tests/regtest.py
@@ -39,6 +39,9 @@ class TestLightningAB(TestLightning):
def test_breach(self):
self.run_shell(['breach'])
+ def test_extract_preimage(self):
+ self.run_shell(['extract_preimage'])
+
def test_redeem_htlcs(self):
self.run_shell(['redeem_htlcs'])
diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh
@@ -162,6 +162,39 @@ if [[ $1 == "breach" ]]; then
$bob getbalance
fi
+
+if [[ $1 == "extract_preimage" ]]; then
+ # instead of settling bob will broadcast
+ $bob enable_htlc_settle false
+ wait_for_balance alice 1
+ echo "alice opens channel"
+ bob_node=$($bob nodeid)
+ $alice open_channel $bob_node 0.15
+ new_blocks 3
+ wait_until_channel_open alice
+ chan_id=$($alice list_channels | jq -r ".[0].channel_point")
+ # alice pays bob
+ invoice=$($bob add_lightning_request 0.04 -m "test")
+ screen -S alice_payment -dm -L -Logfile /tmp/alice/screen.log $alice lnpay $invoice --timeout=600
+ sleep 1
+ unsettled=$($alice list_channels | jq '.[] | .local_unsettled_sent')
+ if [[ "$unsettled" == "0" ]]; then
+ echo 'enable_htlc_settle did not work'
+ exit 1
+ fi
+ # bob force closes
+ $bob close_channel $chan_id --force
+ new_blocks 1
+ wait_until_channel_closed bob
+ sleep 5
+ success=$(cat /tmp/alice/screen.log | jq -r ".success")
+ if [[ "$success" != "true" ]]; then
+ exit 1
+ fi
+ cat /tmp/alice/screen.log
+fi
+
+
if [[ $1 == "redeem_htlcs" ]]; then
$bob enable_htlc_settle false
wait_for_balance alice 1
diff --git a/electrum/transaction.py b/electrum/transaction.py
@@ -235,6 +235,12 @@ class TxInput:
d['witness'] = self.witness.hex()
return d
+ def witness_elements(self)-> Sequence[bytes]:
+ vds = BCDataStream()
+ vds.write(self.witness)
+ n = vds.read_compact_size()
+ return list(vds.read_bytes(vds.read_compact_size()) for i in range(n))
+
class BCDataStream(object):
"""Workalike python implementation of Bitcoin's CDataStream class."""