URI: 
       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)