URI: 
       tlnhtlc.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tlnhtlc.py (29661B)
       ---
            1 from copy import deepcopy
            2 from typing import Optional, Sequence, Tuple, List, Dict, TYPE_CHECKING, Set
            3 import threading
            4 
            5 from .lnutil import SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, UpdateAddHtlc, Direction, FeeUpdate
            6 from .util import bh2u, bfh
            7 
            8 if TYPE_CHECKING:
            9     from .json_db import StoredDict
           10 
           11 
           12 class HTLCManager:
           13 
           14     def __init__(self, log:'StoredDict', *, initial_feerate=None):
           15 
           16         if len(log) == 0:
           17             initial = {
           18                 'adds': {},              # "side who offered htlc" -> htlc_id -> htlc
           19                 'locked_in': {},         # "side who offered htlc" -> action -> htlc_id -> whose ctx -> ctn
           20                 'settles': {},           # "side who offered htlc" -> action -> htlc_id -> whose ctx -> ctn
           21                 'fails': {},             # "side who offered htlc" -> action -> htlc_id -> whose ctx -> ctn
           22                 'fee_updates': {},       # "side who initiated fee update" -> action -> list of FeeUpdates
           23                 'revack_pending': False,
           24                 'next_htlc_id': 0,
           25                 'ctn': -1,               # oldest unrevoked ctx of sub
           26             }
           27             # note: "htlc_id" keys in dict are str! but due to json_db magic they can *almost* be treated as int...
           28             log[LOCAL] = deepcopy(initial)
           29             log[REMOTE] = deepcopy(initial)
           30             log['unacked_local_updates2'] = {}
           31 
           32         if 'unfulfilled_htlcs' not in log:
           33             log['unfulfilled_htlcs'] = {}  # htlc_id -> onion_packet
           34         if 'fail_htlc_reasons' not in log:
           35             log['fail_htlc_reasons'] = {}  # htlc_id -> error_bytes, failure_message
           36 
           37         # maybe bootstrap fee_updates if initial_feerate was provided
           38         if initial_feerate is not None:
           39             assert type(initial_feerate) is int
           40             for sub in (LOCAL, REMOTE):
           41                 if not log[sub]['fee_updates']:
           42                     log[sub]['fee_updates'][0] = FeeUpdate(rate=initial_feerate, ctn_local=0, ctn_remote=0)
           43         self.log = log
           44 
           45         # We need a lock as many methods of HTLCManager are accessed by both the asyncio thread and the GUI.
           46         # lnchannel sometimes calls us with Channel.db_lock (== log.lock) already taken,
           47         # and we ourselves often take log.lock (via StoredDict.__getitem__).
           48         # Hence, to avoid deadlocks, we reuse this same lock.
           49         self.lock = log.lock
           50 
           51         self._init_maybe_active_htlc_ids()
           52 
           53     def with_lock(func):
           54         def func_wrapper(self, *args, **kwargs):
           55             with self.lock:
           56                 return func(self, *args, **kwargs)
           57         return func_wrapper
           58 
           59     @with_lock
           60     def ctn_latest(self, sub: HTLCOwner) -> int:
           61         """Return the ctn for the latest (newest that has a valid sig) ctx of sub"""
           62         return self.ctn_oldest_unrevoked(sub) + int(self.is_revack_pending(sub))
           63 
           64     def ctn_oldest_unrevoked(self, sub: HTLCOwner) -> int:
           65         """Return the ctn for the oldest unrevoked ctx of sub"""
           66         return self.log[sub]['ctn']
           67 
           68     def is_revack_pending(self, sub: HTLCOwner) -> bool:
           69         """Returns True iff sub was sent commitment_signed but they did not
           70         send revoke_and_ack yet (sub has multiple unrevoked ctxs)
           71         """
           72         return self.log[sub]['revack_pending']
           73 
           74     def _set_revack_pending(self, sub: HTLCOwner, pending: bool) -> None:
           75         self.log[sub]['revack_pending'] = pending
           76 
           77     def get_next_htlc_id(self, sub: HTLCOwner) -> int:
           78         return self.log[sub]['next_htlc_id']
           79 
           80     ##### Actions on channel:
           81 
           82     @with_lock
           83     def channel_open_finished(self):
           84         self.log[LOCAL]['ctn'] = 0
           85         self.log[REMOTE]['ctn'] = 0
           86         self._set_revack_pending(LOCAL, False)
           87         self._set_revack_pending(REMOTE, False)
           88 
           89     @with_lock
           90     def send_htlc(self, htlc: UpdateAddHtlc) -> UpdateAddHtlc:
           91         htlc_id = htlc.htlc_id
           92         if htlc_id != self.get_next_htlc_id(LOCAL):
           93             raise Exception(f"unexpected local htlc_id. next should be "
           94                             f"{self.get_next_htlc_id(LOCAL)} but got {htlc_id}")
           95         self.log[LOCAL]['adds'][htlc_id] = htlc
           96         self.log[LOCAL]['locked_in'][htlc_id] = {LOCAL: None, REMOTE: self.ctn_latest(REMOTE)+1}
           97         self.log[LOCAL]['next_htlc_id'] += 1
           98         self._maybe_active_htlc_ids[LOCAL].add(htlc_id)
           99         return htlc
          100 
          101     @with_lock
          102     def recv_htlc(self, htlc: UpdateAddHtlc) -> None:
          103         htlc_id = htlc.htlc_id
          104         if htlc_id != self.get_next_htlc_id(REMOTE):
          105             raise Exception(f"unexpected remote htlc_id. next should be "
          106                             f"{self.get_next_htlc_id(REMOTE)} but got {htlc_id}")
          107         self.log[REMOTE]['adds'][htlc_id] = htlc
          108         self.log[REMOTE]['locked_in'][htlc_id] = {LOCAL: self.ctn_latest(LOCAL)+1, REMOTE: None}
          109         self.log[REMOTE]['next_htlc_id'] += 1
          110         self._maybe_active_htlc_ids[REMOTE].add(htlc_id)
          111 
          112     @with_lock
          113     def send_settle(self, htlc_id: int) -> None:
          114         next_ctn = self.ctn_latest(REMOTE) + 1
          115         if not self.is_htlc_active_at_ctn(ctx_owner=REMOTE, ctn=next_ctn, htlc_proposer=REMOTE, htlc_id=htlc_id):
          116             raise Exception(f"(local) cannot remove htlc that is not there...")
          117         self.log[REMOTE]['settles'][htlc_id] = {LOCAL: None, REMOTE: next_ctn}
          118 
          119     @with_lock
          120     def recv_settle(self, htlc_id: int) -> None:
          121         next_ctn = self.ctn_latest(LOCAL) + 1
          122         if not self.is_htlc_active_at_ctn(ctx_owner=LOCAL, ctn=next_ctn, htlc_proposer=LOCAL, htlc_id=htlc_id):
          123             raise Exception(f"(remote) cannot remove htlc that is not there...")
          124         self.log[LOCAL]['settles'][htlc_id] = {LOCAL: next_ctn, REMOTE: None}
          125 
          126     @with_lock
          127     def send_fail(self, htlc_id: int) -> None:
          128         next_ctn = self.ctn_latest(REMOTE) + 1
          129         if not self.is_htlc_active_at_ctn(ctx_owner=REMOTE, ctn=next_ctn, htlc_proposer=REMOTE, htlc_id=htlc_id):
          130             raise Exception(f"(local) cannot remove htlc that is not there...")
          131         self.log[REMOTE]['fails'][htlc_id] = {LOCAL: None, REMOTE: next_ctn}
          132 
          133     @with_lock
          134     def recv_fail(self, htlc_id: int) -> None:
          135         next_ctn = self.ctn_latest(LOCAL) + 1
          136         if not self.is_htlc_active_at_ctn(ctx_owner=LOCAL, ctn=next_ctn, htlc_proposer=LOCAL, htlc_id=htlc_id):
          137             raise Exception(f"(remote) cannot remove htlc that is not there...")
          138         self.log[LOCAL]['fails'][htlc_id] = {LOCAL: next_ctn, REMOTE: None}
          139 
          140     @with_lock
          141     def send_update_fee(self, feerate: int) -> None:
          142         fee_update = FeeUpdate(rate=feerate,
          143                                ctn_local=None, ctn_remote=self.ctn_latest(REMOTE) + 1)
          144         self._new_feeupdate(fee_update, subject=LOCAL)
          145 
          146     @with_lock
          147     def recv_update_fee(self, feerate: int) -> None:
          148         fee_update = FeeUpdate(rate=feerate,
          149                                ctn_local=self.ctn_latest(LOCAL) + 1, ctn_remote=None)
          150         self._new_feeupdate(fee_update, subject=REMOTE)
          151 
          152     @with_lock
          153     def _new_feeupdate(self, fee_update: FeeUpdate, subject: HTLCOwner) -> None:
          154         # overwrite last fee update if not yet committed to by anyone; otherwise append
          155         d = self.log[subject]['fee_updates']
          156         #assert type(d) is StoredDict
          157         n = len(d)
          158         last_fee_update = d[n-1]
          159         if (last_fee_update.ctn_local is None or last_fee_update.ctn_local > self.ctn_latest(LOCAL)) \
          160                 and (last_fee_update.ctn_remote is None or last_fee_update.ctn_remote > self.ctn_latest(REMOTE)):
          161             d[n-1] = fee_update
          162         else:
          163             d[n] = fee_update
          164 
          165     @with_lock
          166     def send_ctx(self) -> None:
          167         assert self.ctn_latest(REMOTE) == self.ctn_oldest_unrevoked(REMOTE), (self.ctn_latest(REMOTE), self.ctn_oldest_unrevoked(REMOTE))
          168         self._set_revack_pending(REMOTE, True)
          169 
          170     @with_lock
          171     def recv_ctx(self) -> None:
          172         assert self.ctn_latest(LOCAL) == self.ctn_oldest_unrevoked(LOCAL), (self.ctn_latest(LOCAL), self.ctn_oldest_unrevoked(LOCAL))
          173         self._set_revack_pending(LOCAL, True)
          174 
          175     @with_lock
          176     def send_rev(self) -> None:
          177         self.log[LOCAL]['ctn'] += 1
          178         self._set_revack_pending(LOCAL, False)
          179         # htlcs
          180         for htlc_id in self._maybe_active_htlc_ids[REMOTE]:
          181             ctns = self.log[REMOTE]['locked_in'][htlc_id]
          182             if ctns[REMOTE] is None and ctns[LOCAL] <= self.ctn_latest(LOCAL):
          183                 ctns[REMOTE] = self.ctn_latest(REMOTE) + 1
          184         for log_action in ('settles', 'fails'):
          185             for htlc_id in self._maybe_active_htlc_ids[LOCAL]:
          186                 ctns = self.log[LOCAL][log_action].get(htlc_id, None)
          187                 if ctns is None: continue
          188                 if ctns[REMOTE] is None and ctns[LOCAL] <= self.ctn_latest(LOCAL):
          189                     ctns[REMOTE] = self.ctn_latest(REMOTE) + 1
          190         self._update_maybe_active_htlc_ids()
          191         # fee updates
          192         for k, fee_update in list(self.log[REMOTE]['fee_updates'].items()):
          193             if fee_update.ctn_remote is None and fee_update.ctn_local <= self.ctn_latest(LOCAL):
          194                 fee_update.ctn_remote = self.ctn_latest(REMOTE) + 1
          195 
          196     @with_lock
          197     def recv_rev(self) -> None:
          198         self.log[REMOTE]['ctn'] += 1
          199         self._set_revack_pending(REMOTE, False)
          200         # htlcs
          201         for htlc_id in self._maybe_active_htlc_ids[LOCAL]:
          202             ctns = self.log[LOCAL]['locked_in'][htlc_id]
          203             if ctns[LOCAL] is None and ctns[REMOTE] <= self.ctn_latest(REMOTE):
          204                 ctns[LOCAL] = self.ctn_latest(LOCAL) + 1
          205         for log_action in ('settles', 'fails'):
          206             for htlc_id in self._maybe_active_htlc_ids[REMOTE]:
          207                 ctns = self.log[REMOTE][log_action].get(htlc_id, None)
          208                 if ctns is None: continue
          209                 if ctns[LOCAL] is None and ctns[REMOTE] <= self.ctn_latest(REMOTE):
          210                     ctns[LOCAL] = self.ctn_latest(LOCAL) + 1
          211         self._update_maybe_active_htlc_ids()
          212         # fee updates
          213         for k, fee_update in list(self.log[LOCAL]['fee_updates'].items()):
          214             if fee_update.ctn_local is None and fee_update.ctn_remote <= self.ctn_latest(REMOTE):
          215                 fee_update.ctn_local = self.ctn_latest(LOCAL) + 1
          216 
          217         # no need to keep local update raw msgs anymore, they have just been ACKed.
          218         self.log['unacked_local_updates2'].pop(self.log[REMOTE]['ctn'], None)
          219 
          220     @with_lock
          221     def _update_maybe_active_htlc_ids(self) -> None:
          222         # - Loosely, we want a set that contains the htlcs that are
          223         #   not "removed and revoked from all ctxs of both parties". (self._maybe_active_htlc_ids)
          224         #   It is guaranteed that those htlcs are in the set, but older htlcs might be there too:
          225         #   there is a sanity margin of 1 ctn -- this relaxes the care needed re order of method calls.
          226         # - balance_delta is in sync with maybe_active_htlc_ids. When htlcs are removed from the latter,
          227         #   balance_delta is updated to reflect that htlc.
          228         sanity_margin = 1
          229         for htlc_proposer in (LOCAL, REMOTE):
          230             for log_action in ('settles', 'fails'):
          231                 for htlc_id in list(self._maybe_active_htlc_ids[htlc_proposer]):
          232                     ctns = self.log[htlc_proposer][log_action].get(htlc_id, None)
          233                     if ctns is None: continue
          234                     if (ctns[LOCAL] is not None
          235                             and ctns[LOCAL] <= self.ctn_oldest_unrevoked(LOCAL) - sanity_margin
          236                             and ctns[REMOTE] is not None
          237                             and ctns[REMOTE] <= self.ctn_oldest_unrevoked(REMOTE) - sanity_margin):
          238                         self._maybe_active_htlc_ids[htlc_proposer].remove(htlc_id)
          239                         if log_action == 'settles':
          240                             htlc = self.log[htlc_proposer]['adds'][htlc_id]  # type: UpdateAddHtlc
          241                             self._balance_delta -= htlc.amount_msat * htlc_proposer
          242 
          243     @with_lock
          244     def _init_maybe_active_htlc_ids(self):
          245         # first idx is "side who offered htlc":
          246         self._maybe_active_htlc_ids = {LOCAL: set(), REMOTE: set()}  # type: Dict[HTLCOwner, Set[int]]
          247         # add all htlcs
          248         self._balance_delta = 0  # the balance delta of LOCAL since channel open
          249         for htlc_proposer in (LOCAL, REMOTE):
          250             for htlc_id in self.log[htlc_proposer]['adds']:
          251                 self._maybe_active_htlc_ids[htlc_proposer].add(htlc_id)
          252         # remove old htlcs
          253         self._update_maybe_active_htlc_ids()
          254 
          255     @with_lock
          256     def discard_unsigned_remote_updates(self):
          257         """Discard updates sent by the remote, that the remote itself
          258         did not yet sign (i.e. there was no corresponding commitment_signed msg)
          259         """
          260         # htlcs added
          261         for htlc_id, ctns in list(self.log[REMOTE]['locked_in'].items()):
          262             if ctns[LOCAL] > self.ctn_latest(LOCAL):
          263                 del self.log[REMOTE]['locked_in'][htlc_id]
          264                 del self.log[REMOTE]['adds'][htlc_id]
          265                 self._maybe_active_htlc_ids[REMOTE].discard(htlc_id)
          266         if self.log[REMOTE]['locked_in']:
          267             self.log[REMOTE]['next_htlc_id'] = max([int(x) for x in self.log[REMOTE]['locked_in'].keys()]) + 1
          268         else:
          269             self.log[REMOTE]['next_htlc_id'] = 0
          270         # htlcs removed
          271         for log_action in ('settles', 'fails'):
          272             for htlc_id, ctns in list(self.log[LOCAL][log_action].items()):
          273                 if ctns[LOCAL] > self.ctn_latest(LOCAL):
          274                     del self.log[LOCAL][log_action][htlc_id]
          275         # fee updates
          276         for k, fee_update in list(self.log[REMOTE]['fee_updates'].items()):
          277             if fee_update.ctn_local > self.ctn_latest(LOCAL):
          278                 self.log[REMOTE]['fee_updates'].pop(k)
          279 
          280     @with_lock
          281     def store_local_update_raw_msg(self, raw_update_msg: bytes, *, is_commitment_signed: bool) -> None:
          282         """We need to be able to replay unacknowledged updates we sent to the remote
          283         in case of disconnections. Hence, raw update and commitment_signed messages
          284         are stored temporarily (until they are acked)."""
          285         # self.log['unacked_local_updates2'][ctn_idx] is a list of raw messages
          286         # containing some number of updates and then a single commitment_signed
          287         if is_commitment_signed:
          288             ctn_idx = self.ctn_latest(REMOTE)
          289         else:
          290             ctn_idx = self.ctn_latest(REMOTE) + 1
          291         l = self.log['unacked_local_updates2'].get(ctn_idx, [])
          292         l.append(raw_update_msg.hex())
          293         self.log['unacked_local_updates2'][ctn_idx] = l
          294 
          295     @with_lock
          296     def get_unacked_local_updates(self) -> Dict[int, Sequence[bytes]]:
          297         #return self.log['unacked_local_updates2']
          298         return {int(ctn): [bfh(msg) for msg in messages]
          299                 for ctn, messages in self.log['unacked_local_updates2'].items()}
          300 
          301     ##### Queries re HTLCs:
          302 
          303     def get_htlc_by_id(self, htlc_proposer: HTLCOwner, htlc_id: int) -> UpdateAddHtlc:
          304         return self.log[htlc_proposer]['adds'][htlc_id]
          305 
          306     @with_lock
          307     def is_htlc_active_at_ctn(self, *, ctx_owner: HTLCOwner, ctn: int,
          308                               htlc_proposer: HTLCOwner, htlc_id: int) -> bool:
          309         htlc_id = int(htlc_id)
          310         if htlc_id >= self.get_next_htlc_id(htlc_proposer):
          311             return False
          312         settles = self.log[htlc_proposer]['settles']
          313         fails = self.log[htlc_proposer]['fails']
          314         ctns = self.log[htlc_proposer]['locked_in'][htlc_id]
          315         if ctns[ctx_owner] is not None and ctns[ctx_owner] <= ctn:
          316             not_settled = htlc_id not in settles or settles[htlc_id][ctx_owner] is None or settles[htlc_id][ctx_owner] > ctn
          317             not_failed = htlc_id not in fails or fails[htlc_id][ctx_owner] is None or fails[htlc_id][ctx_owner] > ctn
          318             if not_settled and not_failed:
          319                 return True
          320         return False
          321 
          322     @with_lock
          323     def is_htlc_irrevocably_added_yet(
          324             self,
          325             *,
          326             ctx_owner: HTLCOwner = None,
          327             htlc_proposer: HTLCOwner,
          328             htlc_id: int,
          329     ) -> bool:
          330         """Returns whether `add_htlc` was irrevocably committed to `ctx_owner's` ctx.
          331         If `ctx_owner` is None, both parties' ctxs are checked.
          332         """
          333         in_local = self._is_htlc_irrevocably_added_yet(
          334             ctx_owner=LOCAL, htlc_proposer=htlc_proposer, htlc_id=htlc_id)
          335         in_remote = self._is_htlc_irrevocably_added_yet(
          336             ctx_owner=REMOTE, htlc_proposer=htlc_proposer, htlc_id=htlc_id)
          337         if ctx_owner is None:
          338             return in_local and in_remote
          339         elif ctx_owner == LOCAL:
          340             return in_local
          341         elif ctx_owner == REMOTE:
          342             return in_remote
          343         else:
          344             raise Exception(f"unexpected ctx_owner: {ctx_owner!r}")
          345 
          346     @with_lock
          347     def _is_htlc_irrevocably_added_yet(
          348             self,
          349             *,
          350             ctx_owner: HTLCOwner,
          351             htlc_proposer: HTLCOwner,
          352             htlc_id: int,
          353     ) -> bool:
          354         htlc_id = int(htlc_id)
          355         if htlc_id >= self.get_next_htlc_id(htlc_proposer):
          356             return False
          357         ctns = self.log[htlc_proposer]['locked_in'][htlc_id]
          358         if ctns[ctx_owner] is None:
          359             return False
          360         return ctns[ctx_owner] <= self.ctn_oldest_unrevoked(ctx_owner)
          361 
          362     @with_lock
          363     def is_htlc_irrevocably_removed_yet(
          364             self,
          365             *,
          366             ctx_owner: HTLCOwner = None,
          367             htlc_proposer: HTLCOwner,
          368             htlc_id: int,
          369     ) -> bool:
          370         """Returns whether the removal of an htlc was irrevocably committed to `ctx_owner's` ctx.
          371         The removal can either be a fulfill/settle or a fail; they are not distinguished.
          372         If `ctx_owner` is None, both parties' ctxs are checked.
          373         """
          374         in_local = self._is_htlc_irrevocably_removed_yet(
          375             ctx_owner=LOCAL, htlc_proposer=htlc_proposer, htlc_id=htlc_id)
          376         in_remote = self._is_htlc_irrevocably_removed_yet(
          377             ctx_owner=REMOTE, htlc_proposer=htlc_proposer, htlc_id=htlc_id)
          378         if ctx_owner is None:
          379             return in_local and in_remote
          380         elif ctx_owner == LOCAL:
          381             return in_local
          382         elif ctx_owner == REMOTE:
          383             return in_remote
          384         else:
          385             raise Exception(f"unexpected ctx_owner: {ctx_owner!r}")
          386 
          387     @with_lock
          388     def _is_htlc_irrevocably_removed_yet(
          389             self,
          390             *,
          391             ctx_owner: HTLCOwner,
          392             htlc_proposer: HTLCOwner,
          393             htlc_id: int,
          394     ) -> bool:
          395         htlc_id = int(htlc_id)
          396         if htlc_id >= self.get_next_htlc_id(htlc_proposer):
          397             return False
          398         if htlc_id in self.log[htlc_proposer]['settles']:
          399             ctn_of_settle = self.log[htlc_proposer]['settles'][htlc_id][ctx_owner]
          400         else:
          401             ctn_of_settle = None
          402         if htlc_id in self.log[htlc_proposer]['fails']:
          403             ctn_of_fail = self.log[htlc_proposer]['fails'][htlc_id][ctx_owner]
          404         else:
          405             ctn_of_fail = None
          406         ctn_of_rm = ctn_of_settle or ctn_of_fail or None
          407         if ctn_of_rm is None:
          408             return False
          409         return ctn_of_rm <= self.ctn_oldest_unrevoked(ctx_owner)
          410 
          411     @with_lock
          412     def htlcs_by_direction(self, subject: HTLCOwner, direction: Direction,
          413                            ctn: int = None) -> Dict[int, UpdateAddHtlc]:
          414         """Return the dict of received or sent (depending on direction) HTLCs
          415         in subject's ctx at ctn, keyed by htlc_id.
          416 
          417         direction is relative to subject!
          418         """
          419         assert type(subject) is HTLCOwner
          420         assert type(direction) is Direction
          421         if ctn is None:
          422             ctn = self.ctn_oldest_unrevoked(subject)
          423         d = {}
          424         # subject's ctx
          425         # party is the proposer of the HTLCs
          426         party = subject if direction == SENT else subject.inverted()
          427         if ctn >= self.ctn_oldest_unrevoked(subject):
          428             considered_htlc_ids = self._maybe_active_htlc_ids[party]
          429         else:  # ctn is too old; need to consider full log (slow...)
          430             considered_htlc_ids = self.log[party]['locked_in']
          431         for htlc_id in considered_htlc_ids:
          432             htlc_id = int(htlc_id)
          433             if self.is_htlc_active_at_ctn(ctx_owner=subject, ctn=ctn, htlc_proposer=party, htlc_id=htlc_id):
          434                 d[htlc_id] = self.log[party]['adds'][htlc_id]
          435         return d
          436 
          437     @with_lock
          438     def htlcs(self, subject: HTLCOwner, ctn: int = None) -> Sequence[Tuple[Direction, UpdateAddHtlc]]:
          439         """Return the list of HTLCs in subject's ctx at ctn."""
          440         assert type(subject) is HTLCOwner
          441         if ctn is None:
          442             ctn = self.ctn_oldest_unrevoked(subject)
          443         l = []
          444         l += [(SENT, x) for x in self.htlcs_by_direction(subject, SENT, ctn).values()]
          445         l += [(RECEIVED, x) for x in self.htlcs_by_direction(subject, RECEIVED, ctn).values()]
          446         return l
          447 
          448     @with_lock
          449     def get_htlcs_in_oldest_unrevoked_ctx(self, subject: HTLCOwner) -> Sequence[Tuple[Direction, UpdateAddHtlc]]:
          450         assert type(subject) is HTLCOwner
          451         ctn = self.ctn_oldest_unrevoked(subject)
          452         return self.htlcs(subject, ctn)
          453 
          454     @with_lock
          455     def get_htlcs_in_latest_ctx(self, subject: HTLCOwner) -> Sequence[Tuple[Direction, UpdateAddHtlc]]:
          456         assert type(subject) is HTLCOwner
          457         ctn = self.ctn_latest(subject)
          458         return self.htlcs(subject, ctn)
          459 
          460     @with_lock
          461     def get_htlcs_in_next_ctx(self, subject: HTLCOwner) -> Sequence[Tuple[Direction, UpdateAddHtlc]]:
          462         assert type(subject) is HTLCOwner
          463         ctn = self.ctn_latest(subject) + 1
          464         return self.htlcs(subject, ctn)
          465 
          466     def was_htlc_preimage_released(self, *, htlc_id: int, htlc_proposer: HTLCOwner) -> bool:
          467         settles = self.log[htlc_proposer]['settles']
          468         if htlc_id not in settles:
          469             return False
          470         return settles[htlc_id][htlc_proposer] is not None
          471 
          472     def was_htlc_failed(self, *, htlc_id: int, htlc_proposer: HTLCOwner) -> bool:
          473         """Returns whether an HTLC has been (or will be if we already know) failed."""
          474         fails = self.log[htlc_proposer]['fails']
          475         if htlc_id not in fails:
          476             return False
          477         return fails[htlc_id][htlc_proposer] is not None
          478 
          479     @with_lock
          480     def all_settled_htlcs_ever_by_direction(self, subject: HTLCOwner, direction: Direction,
          481                                             ctn: int = None) -> Sequence[UpdateAddHtlc]:
          482         """Return the list of all HTLCs that have been ever settled in subject's
          483         ctx up to ctn, filtered to only "direction".
          484         """
          485         assert type(subject) is HTLCOwner
          486         if ctn is None:
          487             ctn = self.ctn_oldest_unrevoked(subject)
          488         # subject's ctx
          489         # party is the proposer of the HTLCs
          490         party = subject if direction == SENT else subject.inverted()
          491         d = []
          492         for htlc_id, ctns in self.log[party]['settles'].items():
          493             if ctns[subject] is not None and ctns[subject] <= ctn:
          494                 d.append(self.log[party]['adds'][htlc_id])
          495         return d
          496 
          497     @with_lock
          498     def all_settled_htlcs_ever(self, subject: HTLCOwner, ctn: int = None) \
          499             -> Sequence[Tuple[Direction, UpdateAddHtlc]]:
          500         """Return the list of all HTLCs that have been ever settled in subject's
          501         ctx up to ctn.
          502         """
          503         assert type(subject) is HTLCOwner
          504         if ctn is None:
          505             ctn = self.ctn_oldest_unrevoked(subject)
          506         sent = [(SENT, x) for x in self.all_settled_htlcs_ever_by_direction(subject, SENT, ctn)]
          507         received = [(RECEIVED, x) for x in self.all_settled_htlcs_ever_by_direction(subject, RECEIVED, ctn)]
          508         return sent + received
          509 
          510     @with_lock
          511     def all_htlcs_ever(self) -> Sequence[Tuple[Direction, UpdateAddHtlc]]:
          512         sent = [(SENT, htlc) for htlc in self.log[LOCAL]['adds'].values()]
          513         received = [(RECEIVED, htlc) for htlc in self.log[REMOTE]['adds'].values()]
          514         return sent + received
          515 
          516     @with_lock
          517     def get_balance_msat(self, whose: HTLCOwner, *, ctx_owner=HTLCOwner.LOCAL, ctn: int = None,
          518                          initial_balance_msat: int) -> int:
          519         """Returns the balance of 'whose' in 'ctx' at 'ctn'.
          520         Only HTLCs that have been settled by that ctn are counted.
          521         """
          522         if ctn is None:
          523             ctn = self.ctn_oldest_unrevoked(ctx_owner)
          524         balance = initial_balance_msat
          525         if ctn >= self.ctn_oldest_unrevoked(ctx_owner):
          526             balance += self._balance_delta * whose
          527             considered_sent_htlc_ids = self._maybe_active_htlc_ids[whose]
          528             considered_recv_htlc_ids = self._maybe_active_htlc_ids[-whose]
          529         else:  # ctn is too old; need to consider full log (slow...)
          530             considered_sent_htlc_ids = self.log[whose]['settles']
          531             considered_recv_htlc_ids = self.log[-whose]['settles']
          532         # sent htlcs
          533         for htlc_id in considered_sent_htlc_ids:
          534             ctns = self.log[whose]['settles'].get(htlc_id, None)
          535             if ctns is None: continue
          536             if ctns[ctx_owner] is not None and ctns[ctx_owner] <= ctn:
          537                 htlc = self.log[whose]['adds'][htlc_id]
          538                 balance -= htlc.amount_msat
          539         # recv htlcs
          540         for htlc_id in considered_recv_htlc_ids:
          541             ctns = self.log[-whose]['settles'].get(htlc_id, None)
          542             if ctns is None: continue
          543             if ctns[ctx_owner] is not None and ctns[ctx_owner] <= ctn:
          544                 htlc = self.log[-whose]['adds'][htlc_id]
          545                 balance += htlc.amount_msat
          546         return balance
          547 
          548     @with_lock
          549     def _get_htlcs_that_got_removed_exactly_at_ctn(
          550             self, ctn: int, *, ctx_owner: HTLCOwner, htlc_proposer: HTLCOwner, log_action: str,
          551     ) -> Sequence[UpdateAddHtlc]:
          552         if ctn >= self.ctn_oldest_unrevoked(ctx_owner):
          553             considered_htlc_ids = self._maybe_active_htlc_ids[htlc_proposer]
          554         else:  # ctn is too old; need to consider full log (slow...)
          555             considered_htlc_ids = self.log[htlc_proposer][log_action]
          556         htlcs = []
          557         for htlc_id in considered_htlc_ids:
          558             ctns = self.log[htlc_proposer][log_action].get(htlc_id, None)
          559             if ctns is None: continue
          560             if ctns[ctx_owner] == ctn:
          561                 htlcs.append(self.log[htlc_proposer]['adds'][htlc_id])
          562         return htlcs
          563 
          564     def received_in_ctn(self, local_ctn: int) -> Sequence[UpdateAddHtlc]:
          565         """
          566         received htlcs that became fulfilled when we send a revocation.
          567         we check only local, because they are committed in the remote ctx first.
          568         """
          569         return self._get_htlcs_that_got_removed_exactly_at_ctn(local_ctn,
          570                                                                ctx_owner=LOCAL,
          571                                                                htlc_proposer=REMOTE,
          572                                                                log_action='settles')
          573 
          574     def sent_in_ctn(self, remote_ctn: int) -> Sequence[UpdateAddHtlc]:
          575         """
          576         sent htlcs that became fulfilled when we received a revocation
          577         we check only remote, because they are committed in the local ctx first.
          578         """
          579         return self._get_htlcs_that_got_removed_exactly_at_ctn(remote_ctn,
          580                                                                ctx_owner=REMOTE,
          581                                                                htlc_proposer=LOCAL,
          582                                                                log_action='settles')
          583 
          584     def failed_in_ctn(self, remote_ctn: int) -> Sequence[UpdateAddHtlc]:
          585         """
          586         sent htlcs that became failed when we received a revocation
          587         we check only remote, because they are committed in the local ctx first.
          588         """
          589         return self._get_htlcs_that_got_removed_exactly_at_ctn(remote_ctn,
          590                                                                ctx_owner=REMOTE,
          591                                                                htlc_proposer=LOCAL,
          592                                                                log_action='fails')
          593 
          594     ##### Queries re Fees:
          595     # note: feerates are in sat/kw everywhere in this file
          596 
          597     @with_lock
          598     def get_feerate(self, subject: HTLCOwner, ctn: int) -> int:
          599         """Return feerate (sat/kw) used in subject's commitment txn at ctn."""
          600         ctn = max(0, ctn)  # FIXME rm this
          601         # only one party can update fees; use length of logs to figure out which:
          602         assert not (len(self.log[LOCAL]['fee_updates']) > 1 and len(self.log[REMOTE]['fee_updates']) > 1)
          603         fee_log = self.log[LOCAL]['fee_updates']  # type: Sequence[FeeUpdate]
          604         if len(self.log[REMOTE]['fee_updates']) > 1:
          605             fee_log = self.log[REMOTE]['fee_updates']
          606         # binary search
          607         left = 0
          608         right = len(fee_log)
          609         while True:
          610             i = (left + right) // 2
          611             ctn_at_i = fee_log[i].ctn_local if subject==LOCAL else fee_log[i].ctn_remote
          612             if right - left <= 1:
          613                 break
          614             if ctn_at_i is None:  # Nones can only be on the right end
          615                 right = i
          616                 continue
          617             if ctn_at_i <= ctn:  # among equals, we want the rightmost
          618                 left = i
          619             else:
          620                 right = i
          621         assert ctn_at_i <= ctn
          622         return fee_log[i].rate
          623 
          624     def get_feerate_in_oldest_unrevoked_ctx(self, subject: HTLCOwner) -> int:
          625         return self.get_feerate(subject=subject, ctn=self.ctn_oldest_unrevoked(subject))
          626 
          627     def get_feerate_in_latest_ctx(self, subject: HTLCOwner) -> int:
          628         return self.get_feerate(subject=subject, ctn=self.ctn_latest(subject))
          629 
          630     def get_feerate_in_next_ctx(self, subject: HTLCOwner) -> int:
          631         return self.get_feerate(subject=subject, ctn=self.ctn_latest(subject) + 1)