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
2725import datetime
2826import json
2927import copy
30- import threading
3128from collections import defaultdict
3229from typing import (Dict , Optional , List , Tuple , Set , Iterable , NamedTuple , Sequence , TYPE_CHECKING ,
3330 Union , AbstractSet )
34- import binascii
3531import time
3632from functools import partial
3733
3834import 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
4338from .keystore import bip44_derivation
4439from .transaction import Transaction , TxOutpoint , tx_from_any , PartialTransaction , PartialTxOutput , BadHeaderMagic
4540from .logging import Logger
4641
47- from .lnutil import HTLCOwner , ChannelType
42+ from .lnutil import HTLCOwner , ChannelType , RecvMPPResolution
4843from . 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
5045from .plugin import run_hook , plugin_loaders
5146from .version import ELECTRUM_VERSION
47+ from .i18n import _
5248
5349if 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
7470OLD_SEED_VERSION = 4 # electrum versions < 2.0
7571NEW_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