Repository: spesmilo/electrum

Issue:
Electrum returns wrong value for settled hodl invoice
### Description

Today I was testing hodl invoices in the latest version of electrum and I think I discovered a bug. I created a hodl invoice like this:

![](https://i.ibb.co/23s8W977/problem-with-electrum-2.png)

\[**EDIT: the screenshot isn't fully showing for me; you can view it here: https://i.ibb.co/23s8W977/problem-with-electrum-2.png \]

I then checked its status before and after paying it, observing, as expected, that its status changed from "unpaid" to "paid":

![](https://i.ibb.co/PsDwNWZK/electrum-error-3.png)

But something seems off already. I don't think it should have said, the first time, that the amount_sat was 0. The invoice was for 6000 sats, as is clearly indicated the second time.

Anyway, I continued my test and actually settled the invoice, then checked it again as seen below:

![](https://i.ibb.co/RGBh8B3W/problem-with-electrum-4.png)

And as you can see, there are two strange errors: the invoice's status is once again listed as "unpaid." Per the check_hold_invoice help guide, it should say "settled." Moreover, the amount_sat field once again reports a value of 0, but the invoice definitely had a value of 6000 sats, as shown previously.


Discussion:

Patch:
diff --git a/electrum/commands.py b/electrum/commands.py
index ff925d68de3b..f85d15eee20f 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -1427,6 +1427,8 @@ async def settle_hold_invoice(self, preimage: str, wallet: Abstract_Wallet = Non
         assert payment_hash in wallet.lnworker.dont_settle_htlcs, f"Invoice {payment_hash=} not a hold invoice?"
         assert wallet.lnworker.is_accepted_mpp(bfh(payment_hash)), \
             f"MPP incomplete, cannot settle hold invoice {payment_hash} yet"
+        info: Optional['PaymentInfo'] = wallet.lnworker.get_payment_info(bfh(payment_hash))
+        assert (wallet.lnworker.get_payment_mpp_amount_msat(bfh(payment_hash)) or 0) >= (info.amount_msat or 0)
         del wallet.lnworker.dont_settle_htlcs[payment_hash]
         wallet.lnworker.save_preimage(bfh(payment_hash), bfh(preimage))
         util.trigger_callback('wallet_updated', wallet)
@@ -1474,18 +1476,23 @@ async def check_hold_invoice(self, payment_hash: str, wallet: Abstract_Wallet =
         status = "unknown"
         if info is None:
             pass
-        elif not is_accepted_mpp:
+        elif not is_accepted_mpp and not wallet.lnworker.get_preimage_hex(payment_hash):
+            # is_accepted_mpp is False for settled payments
             status = "unpaid"
         elif is_accepted_mpp and payment_hash in wallet.lnworker.dont_settle_htlcs:
             status = "paid"
-        elif (payment_hash in wallet.lnworker._preimages
-                and payment_hash not in wallet.lnworker.dont_settle_htlcs
-                and is_accepted_mpp):
+        elif wallet.lnworker.get_preimage_hex(payment_hash) is not None \
+                and payment_hash not in wallet.lnworker.dont_settle_htlcs:
             status = "settled"
+            plist = wallet.lnworker.get_payments(status='settled')[bfh(payment_hash)]
+            _dir, amount_msat, _fee, _ts = wallet.lnworker.get_payment_value(info, plist)
+            amount_sat = amount_msat // 1000
         result = {
             "status": status,
-            "amount_sat": amount_sat
+            "received_amount_sat": amount_sat,
         }
+        if info is not None:
+            result["invoice_amount_sat"] = (info.amount_msat or 0) // 1000
         return result
 
     @command('w')


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
index c142bb57d83c..6ef5d1dad6a1 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -495,7 +495,7 @@ async def test_hold_invoice_commands(self, mock_save_db):
                 mock.patch.object(wallet.lnworker, 'get_payment_mpp_amount_msat', return_value=10_000 * 1000):
             status: dict = await cmds.check_hold_invoice(payment_hash=payment_hash, wallet=wallet)
             assert status['status'] == 'paid'
-            assert status['amount_sat'] == 10000
+            assert status['received_amount_sat'] == 10000
 
             settle_result = await cmds.settle_hold_invoice(
                 preimage=preimage.hex(),
@@ -504,6 +504,15 @@ async def test_hold_invoice_commands(self, mock_save_db):
         assert settle_result['settled'] == payment_hash
         assert wallet.lnworker._preimages[payment_hash] == preimage.hex()
         assert payment_hash not in wallet.lnworker.dont_settle_htlcs
+        with (mock.patch.object(
+            wallet.lnworker,
+            'get_payment_value',
+            return_value=(None, 10000*1000, None, None),
+        )):
+            settled_status: dict = await cmds.check_hold_invoice(payment_hash=payment_hash, wallet=wallet)
+            assert settled_status['status'] == 'settled'
+            assert settled_status['received_amount_sat'] == 10000
+            assert settled_status['invoice_amount_sat'] == 10000
 
         with self.assertRaises(AssertionError):
             # cancelling a settled invoice should raise