Skip to content

Commit f11c2d7

Browse files
committed
wallet_db: handle pending mpp sets during upgrade
Upgrade the pending mpp sets in the wallet db to new structure
1 parent 737a30c commit f11c2d7

File tree

1 file changed

+94
-10
lines changed

1 file changed

+94
-10
lines changed

electrum/wallet_db.py

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,29 @@
2222
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
2323
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2424
# SOFTWARE.
25-
import os
26-
import ast
2725
import datetime
2826
import json
2927
import copy
30-
import threading
3128
from collections import defaultdict
3229
from typing import (Dict, Optional, List, Tuple, Set, Iterable, NamedTuple, Sequence, TYPE_CHECKING,
3330
Union, AbstractSet)
34-
import binascii
3531
import time
3632
from functools import partial
3733

3834
import attr
3935

40-
from . import util, bitcoin
41-
from .util import profiler, WalletFileException, multisig_type, TxMinedInfo, bfh, MyEncoder
42-
from .invoices import Invoice, Request
36+
from . import bitcoin
37+
from .util import profiler, WalletFileException, multisig_type, TxMinedInfo, MyEncoder
4338
from .keystore import bip44_derivation
4439
from .transaction import Transaction, TxOutpoint, tx_from_any, PartialTransaction, PartialTxOutput, BadHeaderMagic
4540
from .logging import Logger
4641

47-
from .lnutil import HTLCOwner, ChannelType
42+
from .lnutil import HTLCOwner, ChannelType, RecvMPPResolution
4843
from . import json_db
49-
from .json_db import StoredDict, JsonDB, locked, modifier, StoredObject, stored_in, stored_as
44+
from .json_db import JsonDB, locked, modifier, StoredObject, stored_in, stored_as
5045
from .plugin import run_hook, plugin_loaders
5146
from .version import ELECTRUM_VERSION
47+
from .i18n import _
5248

5349
if TYPE_CHECKING:
5450
from .storage import WalletStorage
@@ -73,7 +69,7 @@ def __init__(self, wallet_db: 'WalletDB'):
7369
# seed_version is now used for the version of the wallet file
7470
OLD_SEED_VERSION = 4 # electrum versions < 2.0
7571
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
76-
FINAL_SEED_VERSION = 61 # electrum >= 2.7 will set this to prevent
72+
FINAL_SEED_VERSION = 62 # electrum >= 2.7 will set this to prevent
7773
# old versions from overwriting new format
7874

7975

@@ -237,6 +233,7 @@ def upgrade(self):
237233
self._convert_version_59()
238234
self._convert_version_60()
239235
self._convert_version_61()
236+
self._convert_version_62()
240237
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
241238

242239
def _convert_wallet_type(self):
@@ -1161,6 +1158,7 @@ def _convert_version_60(self):
11611158
def _convert_version_61(self):
11621159
if not self._is_upgrade_method_needed(60, 60):
11631160
return
1161+
# conversion of PaymentInformation
11641162
lightning_payments = self.data.get('lightning_payments', {})
11651163
for rhash, (amount_msat, direction, is_paid) in list(lightning_payments.items()):
11661164
new_dataclass_type = {
@@ -1175,6 +1173,92 @@ def _convert_version_61(self):
11751173
lightning_payments[rhash] = new_dataclass_type
11761174
self.data['seed_version'] = 61
11771175

1176+
def _convert_version_62(self):
1177+
if not self._is_upgrade_method_needed(61, 61):
1178+
return
1179+
# Old ReceivedMPPStatus:
1180+
# class ReceivedMPPStatus(NamedTuple):
1181+
# resolution: RecvMPPResolution
1182+
# expected_msat: int
1183+
# htlc_set: Set[Tuple[ShortChannelID, UpdateAddHtlc]]
1184+
#
1185+
# New ReceivedMPPStatus:
1186+
# class ReceivedMPPStatus(NamedTuple):
1187+
# resolution: RecvMPPResolution
1188+
# htlcs: set[ReceivedMPPHtlc]
1189+
#
1190+
# class ReceivedMPPHtlc(NamedTuple):
1191+
# scid: ShortChannelID
1192+
# htlc: UpdateAddHtlc
1193+
# unprocessed_onion: str
1194+
1195+
# previously chan.unfulfilled_htlcs went through 4 stages:
1196+
# - 1. not forwarded yet: (onion_packet_hex, None)
1197+
# - 2. forwarded: (onion_packet_hex, forwarding_key)
1198+
# - 3. processed: (None, forwarding_key), not irrevocably removed yet
1199+
# - 4. done: (None, forwarding_key), irrevocably removed
1200+
channels = self.data.get('channels', {})
1201+
def _move_unprocessed_onion(short_channel_id: str, htlc_id: Optional[int]) -> Optional[Tuple[str, Optional[str]]]:
1202+
if htlc_id is None:
1203+
return None
1204+
for chan_ in channels.values():
1205+
if chan_['short_channel_id'] != short_channel_id:
1206+
continue
1207+
unfulfilled_htlcs_ = chan_.get('unfulfilled_htlcs', {})
1208+
htlc_data = unfulfilled_htlcs_.get(str(htlc_id))
1209+
if htlc_data is None:
1210+
return None
1211+
stored_onion_packet, htlc_forwarding_key = htlc_data
1212+
if stored_onion_packet is not None:
1213+
htlc_data[0] = None # overwrite the onion so it is not processed again in htlc_switch
1214+
return stored_onion_packet, htlc_forwarding_key
1215+
return None
1216+
1217+
mpp_sets = self.data.get('received_mpp_htlcs', {})
1218+
for payment_key, recv_mpp_status in list(mpp_sets.items()):
1219+
assert isinstance(recv_mpp_status, list), f"{recv_mpp_status=}"
1220+
del recv_mpp_status[1] # remove expected_msat
1221+
1222+
new_type_htlcs = []
1223+
forwarding_key = None
1224+
for scid, update_add_htlc in recv_mpp_status[1]: # htlc_set
1225+
htlc_info_from_chan = _move_unprocessed_onion(scid, update_add_htlc[3])
1226+
if htlc_info_from_chan is None:
1227+
# if there is no onion packet for the htlc it is dropped as it was already
1228+
# processed in the old htlc_switch
1229+
continue
1230+
onion_packet_hex = htlc_info_from_chan[0]
1231+
forwarding_key = htlc_info_from_chan[1] if htlc_info_from_chan[1] else forwarding_key
1232+
new_type_htlcs.append([
1233+
scid,
1234+
update_add_htlc,
1235+
onion_packet_hex,
1236+
])
1237+
1238+
if len(new_type_htlcs) == 0:
1239+
self.logger.debug(f"_convert_version_62: dropping mpp set {payment_key=}.")
1240+
del mpp_sets[payment_key]
1241+
else:
1242+
recv_mpp_status[1] = new_type_htlcs
1243+
self.logger.debug(f"_convert_version_62: migrated mpp set {payment_key=}")
1244+
if forwarding_key is not None:
1245+
# if the forwarding key is set for the old mpp set it was either a forwarding
1246+
# or a swap hold invoice. Assuming users of 4.6.2 don't use forwarding this update
1247+
# most likely happens during a swap waiting for the preimage. Setting the mpp set
1248+
# to SETTLING prevents us from accidentally failing the htlc set after the update,
1249+
# however it carries the risk of the channel getting force closed if the swap fails
1250+
# as the htlcs won't get failed due to the new SETTLING state
1251+
# unless a forwarding error is set.
1252+
recv_mpp_status[0] = RecvMPPResolution.SETTLING
1253+
1254+
# replace Tuple[onion, forwarding_key] with just the onion in chan['unfulfilled_htlcs']
1255+
for chan in channels.values():
1256+
unfulfilled_htlcs = chan.get('unfulfilled_htlcs', {})
1257+
for htlc_id, (unprocessed_onion, forwarding_key) in list(unfulfilled_htlcs.items()):
1258+
unfulfilled_htlcs[htlc_id] = unprocessed_onion
1259+
1260+
self.data['seed_version'] = 62
1261+
11781262
def _convert_imported(self):
11791263
if not self._is_upgrade_method_needed(0, 13):
11801264
return

0 commit comments

Comments
 (0)