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