tUse attr.s for Feeupdates and Outpoints Storage upgrade to version 23 - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit aa51df0a1a49115ddc82b9ef7c92b3d2a846a8a6 DIR parent 7472eba78c228ed4ef9e1310c3c8e15e781440be HTML Author: ThomasV <thomasv@electrum.org> Date: Sat, 1 Feb 2020 16:30:02 +0100 Use attr.s for Feeupdates and Outpoints Storage upgrade to version 23 Diffstat: M electrum/json_db.py | 30 +++++++++++++++++++++++++++++- M electrum/lnchannel.py | 3 ++- M electrum/lnhtlc.py | 47 +++++++++++++++++-------------- M electrum/lnutil.py | 40 ++++++++++++------------------- 4 files changed, 72 insertions(+), 48 deletions(-) --- DIR diff --git a/electrum/json_db.py b/electrum/json_db.py t@@ -40,7 +40,7 @@ from .logging import Logger OLD_SEED_VERSION = 4 # electrum versions < 2.0 NEW_SEED_VERSION = 11 # electrum versions >= 2.0 -FINAL_SEED_VERSION = 22 # electrum >= 2.7 will set this to prevent +FINAL_SEED_VERSION = 23 # electrum >= 2.7 will set this to prevent # old versions from overwriting new format t@@ -216,6 +216,7 @@ class JsonDB(Logger): self._convert_version_20() self._convert_version_21() self._convert_version_22() + self._convert_version_23() self.put('seed_version', FINAL_SEED_VERSION) # just to be sure self._after_upgrade_tasks() t@@ -515,6 +516,33 @@ class JsonDB(Logger): self.put('seed_version', 22) + def _convert_version_23(self): + if not self._is_upgrade_method_needed(22, 22): + return + channels = self.get('channels', []) + LOCAL = 1 + REMOTE = -1 + for c in channels: + # move revocation store from remote_config + r = c['remote_config'].pop('revocation_store') + c['revocation_store'] = r + # convert fee updates + log = c.get('log', {}) + for sub in LOCAL, REMOTE: + l = log[str(sub)]['fee_updates'] + d = {} + for i, fu in enumerate(l): + d[str(i)] = { + 'rate':fu['rate'], + 'ctn_local':fu['ctns'][str(LOCAL)], + 'ctn_remote':fu['ctns'][str(REMOTE)] + } + log[str(int(sub))]['fee_updates'] = d + self.data['channels'] = channels + + self.data['seed_version'] = 23 + + def _convert_imported(self): if not self._is_upgrade_method_needed(0, 13): return DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py t@@ -752,7 +752,8 @@ class Channel(Logger): other_revocation_pubkey, derive_pubkey(this_config.delayed_basepoint.pubkey, this_point), other_config.to_self_delay, - *self.funding_outpoint, + self.funding_outpoint.txid, + self.funding_outpoint.output_index, self.constraints.capacity, local_msat, remote_msat, DIR diff --git a/electrum/lnhtlc.py b/electrum/lnhtlc.py t@@ -14,7 +14,7 @@ class HTLCManager: 'locked_in': {}, 'settles': {}, 'fails': {}, - 'fee_updates': [], + 'fee_updates': {}, # "side who initiated fee update" -> action -> list of FeeUpdates 'revack_pending': False, 'next_htlc_id': 0, 'ctn': -1, # oldest unrevoked ctx of sub t@@ -32,7 +32,8 @@ class HTLCManager: log[sub]['settles'] = {int(htlc_id): coerceHtlcOwner2IntMap(ctns) for htlc_id, ctns in log[sub]['settles'].items()} log[sub]['fails'] = {int(htlc_id): coerceHtlcOwner2IntMap(ctns) for htlc_id, ctns in log[sub]['fails'].items()} # "side who initiated fee update" -> action -> list of FeeUpdates - log[sub]['fee_updates'] = [FeeUpdate.from_dict(fee_upd) for fee_upd in log[sub]['fee_updates']] + log[sub]['fee_updates'] = { int(x): FeeUpdate(**fee_upd) for x,fee_upd in log[sub]['fee_updates'].items() } + if 'unacked_local_updates2' not in log: log['unacked_local_updates2'] = {} log['unacked_local_updates2'] = {int(ctn): [bfh(msg) for msg in messages] t@@ -42,7 +43,7 @@ class HTLCManager: assert type(initial_feerate) is int for sub in (LOCAL, REMOTE): if not log[sub]['fee_updates']: - log[sub]['fee_updates'].append(FeeUpdate(initial_feerate, ctns={LOCAL:0, REMOTE:0})) + log[sub]['fee_updates'][0] = FeeUpdate(initial_feerate, ctn_local=0, ctn_remote=0) self.log = log def ctn_latest(self, sub: HTLCOwner) -> int: t@@ -74,7 +75,7 @@ class HTLCManager: d[htlc_id] = (htlc[0], bh2u(htlc[1])) + htlc[2:] log[sub]['adds'] = d # fee_updates - log[sub]['fee_updates'] = [FeeUpdate.to_dict(fee_upd) for fee_upd in log[sub]['fee_updates']] + log[sub]['fee_updates'] = { x:fee_upd.to_json() for x, fee_upd in self.log[sub]['fee_updates'].items() } log['unacked_local_updates2'] = {ctn: [bh2u(msg) for msg in messages] for ctn, messages in log['unacked_local_updates2'].items()} return log t@@ -120,22 +121,25 @@ class HTLCManager: def send_update_fee(self, feerate: int) -> None: fee_update = FeeUpdate(rate=feerate, - ctns={LOCAL: None, REMOTE: self.ctn_latest(REMOTE) + 1}) + ctn_local=None, ctn_remote=self.ctn_latest(REMOTE) + 1) self._new_feeupdate(fee_update, subject=LOCAL) def recv_update_fee(self, feerate: int) -> None: fee_update = FeeUpdate(rate=feerate, - ctns={LOCAL: self.ctn_latest(LOCAL) + 1, REMOTE: None}) + ctn_local=self.ctn_latest(LOCAL) + 1, ctn_remote=None) self._new_feeupdate(fee_update, subject=REMOTE) def _new_feeupdate(self, fee_update: FeeUpdate, subject: HTLCOwner) -> None: # overwrite last fee update if not yet committed to by anyone; otherwise append - last_fee_update = self.log[subject]['fee_updates'][-1] - if (last_fee_update.ctns[LOCAL] is None or last_fee_update.ctns[LOCAL] > self.ctn_latest(LOCAL)) \ - and (last_fee_update.ctns[REMOTE] is None or last_fee_update.ctns[REMOTE] > self.ctn_latest(REMOTE)): - self.log[subject]['fee_updates'][-1] = fee_update + d = self.log[subject]['fee_updates'] + assert type(d) is dict + n = len(d) + last_fee_update = d[n-1] + if (last_fee_update.ctn_local is None or last_fee_update.ctn_local > self.ctn_latest(LOCAL)) \ + and (last_fee_update.ctn_remote is None or last_fee_update.ctn_remote > self.ctn_latest(REMOTE)): + d[n-1] = fee_update else: - self.log[subject]['fee_updates'].append(fee_update) + d[n] = fee_update def send_ctx(self) -> None: assert self.ctn_latest(REMOTE) == self.ctn_oldest_unrevoked(REMOTE), (self.ctn_latest(REMOTE), self.ctn_oldest_unrevoked(REMOTE)) t@@ -157,9 +161,9 @@ class HTLCManager: if ctns[REMOTE] is None and ctns[LOCAL] <= self.ctn_latest(LOCAL): ctns[REMOTE] = self.ctn_latest(REMOTE) + 1 # fee updates - for fee_update in self.log[REMOTE]['fee_updates']: - if fee_update.ctns[REMOTE] is None and fee_update.ctns[LOCAL] <= self.ctn_latest(LOCAL): - fee_update.ctns[REMOTE] = self.ctn_latest(REMOTE) + 1 + for k, fee_update in list(self.log[REMOTE]['fee_updates'].items()): + if fee_update.ctn_remote is None and fee_update.ctn_local <= self.ctn_latest(LOCAL): + fee_update.ctn_remote = self.ctn_latest(REMOTE) + 1 def recv_rev(self) -> None: self.log[REMOTE]['ctn'] += 1 t@@ -173,9 +177,10 @@ class HTLCManager: if ctns[LOCAL] is None and ctns[REMOTE] <= self.ctn_latest(REMOTE): ctns[LOCAL] = self.ctn_latest(LOCAL) + 1 # fee updates - for fee_update in self.log[LOCAL]['fee_updates']: - if fee_update.ctns[LOCAL] is None and fee_update.ctns[REMOTE] <= self.ctn_latest(REMOTE): - fee_update.ctns[LOCAL] = self.ctn_latest(LOCAL) + 1 + for k, fee_update in list(self.log[LOCAL]['fee_updates'].items()): + if fee_update.ctn_local is None and fee_update.ctn_remote <= self.ctn_latest(REMOTE): + fee_update.ctn_local = self.ctn_latest(LOCAL) + 1 + # no need to keep local update raw msgs anymore, they have just been ACKed. self.log['unacked_local_updates2'].pop(self.log[REMOTE]['ctn'], None) t@@ -198,9 +203,9 @@ class HTLCManager: if ctns[LOCAL] > self.ctn_latest(LOCAL): del self.log[LOCAL][log_action][htlc_id] # fee updates - for i, fee_update in enumerate(list(self.log[REMOTE]['fee_updates'])): - if fee_update.ctns[LOCAL] > self.ctn_latest(LOCAL): - del self.log[REMOTE]['fee_updates'][i] + for k, fee_update in list(self.log[REMOTE]['fee_updates'].items()): + if fee_update.ctn_local > self.ctn_latest(LOCAL): + self.log[REMOTE]['fee_updates'].pop(k) def store_local_update_raw_msg(self, raw_update_msg: bytes, *, is_commitment_signed: bool) -> None: """We need to be able to replay unacknowledged updates we sent to the remote t@@ -331,7 +336,7 @@ class HTLCManager: right = len(fee_log) while True: i = (left + right) // 2 - ctn_at_i = fee_log[i].ctns[subject] + ctn_at_i = fee_log[i].ctn_local if subject==LOCAL else fee_log[i].ctn_remote if right - left <= 1: break if ctn_at_i is None: # Nones can only be on the right end DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py t@@ -82,31 +82,17 @@ class RemoteConfig(Config): next_per_commitment_point = attr.ib(type=bytes) current_per_commitment_point = attr.ib(default=None, type=bytes) -#@attr.s -#class FeeUpdate(StoredAttr): -# rate = attr.ib(type=int) # in sat/kw -# ctn_local = attr.ib(default=None, type=int) -# ctn_remote = attr.ib(default=None, type=int) - - - -class FeeUpdate(NamedTuple): - rate: int # in sat/kw - ctns: Dict['HTLCOwner', Optional[int]] - - @classmethod - def from_dict(cls, d: dict) -> 'FeeUpdate': - return FeeUpdate(rate=d['rate'], - ctns={LOCAL: d['ctns'][str(int(LOCAL))], - REMOTE: d['ctns'][str(int(REMOTE))]}) - - def to_dict(self) -> dict: - return {'rate': self.rate, - 'ctns': {int(LOCAL): self.ctns[LOCAL], - int(REMOTE): self.ctns[REMOTE]}} - +@attr.s +class FeeUpdate(StoredAttr): + rate = attr.ib(type=int) # in sat/kw + ctn_local = attr.ib(default=None, type=int) + ctn_remote = attr.ib(default=None, type=int) -ChannelConstraints = namedtuple("ChannelConstraints", ["capacity", "is_initiator", "funding_txn_minimum_depth"]) +@attr.s +class ChannelConstraints(StoredAttr): + capacity = attr.ib(type=int) + is_initiator = attr.ib(type=bool) + funding_txn_minimum_depth = attr.ib(type=int) class ScriptHtlc(NamedTuple): t@@ -115,7 +101,11 @@ class ScriptHtlc(NamedTuple): # FIXME duplicate of TxOutpoint in transaction.py?? -class Outpoint(NamedTuple("Outpoint", [('txid', str), ('output_index', int)])): +@attr.s +class Outpoint(StoredAttr): + txid = attr.ib(type=str) + output_index = attr.ib(type=int) + def to_str(self): return "{}:{}".format(self.txid, self.output_index)