URI: 
       tln: avoid duplicated htlc filter code, support multiple htlcs better - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit e23e0d6c6e6e419bd6c467478845e9e3403a1674
   DIR parent e18a3b5a3df7f465c16a619714ba916dbca894ca
  HTML Author: Janus <ysangkok@gmail.com>
       Date:   Tue, 25 Sep 2018 17:08:46 +0200
       
       ln: avoid duplicated htlc filter code, support multiple htlcs better
       
       Diffstat:
         M electrum/lnhtlc.py                  |      83 ++++++++++++-------------------
         M electrum/lnutil.py                  |      33 +++++++++++++++++++------------
       
       2 files changed, 53 insertions(+), 63 deletions(-)
       ---
   DIR diff --git a/electrum/lnhtlc.py b/electrum/lnhtlc.py
       t@@ -17,6 +17,7 @@ from .lnutil import sign_and_get_sig_string
        from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc
        from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT
        from .lnutil import funding_output_script, LOCAL, REMOTE, HTLCOwner, make_closing_tx, make_outputs
       +from .lnutil import ScriptHtlc
        from .transaction import Transaction
        
        
       t@@ -265,19 +266,10 @@ class HTLCStateMachine(PrintError):
        
                for_us = False
        
       -        feerate = self.pending_feerate(REMOTE)
       -
                htlcsigs = []
       -        for we_receive, htlcs in zip([True, False], [self.htlcs_in_remote, self.htlcs_in_local]):
       -            assert len(htlcs) <= 1
       +        for we_receive, htlcs in zip([True, False], [self.included_htlcs(REMOTE, REMOTE), self.included_htlcs(REMOTE, LOCAL)]):
                    for htlc in htlcs:
       -                weight = HTLC_SUCCESS_WEIGHT if we_receive else HTLC_TIMEOUT_WEIGHT
       -                fee = feerate // 1000 * weight
       -                if htlc.amount_msat // 1000 < self.remote_config.dust_limit_sat + fee:
       -                    print("value too small, skipping. htlc amt: {}, weight: {}, remote feerate {}, remote dust limit {}".format( htlc.amount_msat, weight, feerate, self.remote_config.dust_limit_sat))
       -                    continue
       -                original_htlc_output_index = 0
       -                args = [self.remote_state.next_per_commitment_point, for_us, we_receive, htlc.amount_msat, htlc.cltv_expiry, htlc.payment_hash, pending_remote_commitment, original_htlc_output_index]
       +                args = [self.remote_state.next_per_commitment_point, for_us, we_receive, pending_remote_commitment, htlc]
                        htlc_tx = make_htlc_tx_with_open_channel(self, *args)
                        sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
                        htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
       t@@ -293,6 +285,8 @@ class HTLCStateMachine(PrintError):
                if self.lnwatcher:
                    self.lnwatcher.process_new_offchain_ctx(self, pending_remote_commitment, ours=False)
        
       +        htlcsigs.sort()
       +
                return sig_64, htlcsigs
        
            def receive_new_commitment(self, sig, htlc_sigs):
       t@@ -322,27 +316,19 @@ class HTLCStateMachine(PrintError):
        
                _, this_point, _ = self.points
        
       -        if len(pending_local_commitment.outputs()) >= 3:
       -            print("CHECKING HTLC SIGS")
       -            assert len(pending_local_commitment.outputs()) == 3
       -            if len(self.htlcs_in_remote) > 0:
       -                assert len(self.htlcs_in_remote) == 1
       -                we_receive = True
       -                htlc = self.htlcs_in_remote[0]
       -            elif len(self.htlcs_in_local) > 0:
       -                assert len(self.htlcs_in_local) == 1
       -                we_receive = False
       -                htlc = self.htlcs_in_local[0]
       -            else:
       -                assert False
       -
       -            htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, htlc.amount_msat, htlc.cltv_expiry, htlc.payment_hash, pending_local_commitment, 0)
       -            pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
       -            remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
       -            if not ecc.verify_signature(remote_htlc_pubkey, htlc_sigs[0], pre_hash):
       -                raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs")
       -
       -        # TODO check htlc in htlcs_in_local
       +        for htlcs, we_receive in [(self.included_htlcs(LOCAL, REMOTE), True), (self.included_htlcs(LOCAL, LOCAL), False)]:
       +            for htlc in htlcs:
       +                htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, pending_local_commitment, htlc)
       +                pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
       +                remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
       +                for idx, sig in enumerate(htlc_sigs):
       +                    if ecc.verify_signature(remote_htlc_pubkey, sig, pre_hash):
       +                        del htlc_sigs[idx]
       +                        break
       +                else:
       +                    raise Exception(f'failed verifying HTLC signatures: {htlc}')
       +        if len(htlc_sigs) != 0: # all sigs should have been popped above
       +            raise Exception('failed verifying HTLC signatures: invalid amount of correct signatures')
        
                for pending_fee in self.fee_mgr:
                    if pending_fee.is_proposed():
       t@@ -513,6 +499,13 @@ class HTLCStateMachine(PrintError):
                  unsettled_local + remote_settled - local_settled
                return remote_msat, local_msat
        
       +    def included_htlcs(self, subject, htlc_initiator):
       +        feerate = self.pending_feerate(subject)
       +        conf = self.remote_config if subject == REMOTE else self.local_config
       +        weight = HTLC_SUCCESS_WEIGHT if subject != htlc_initiator else HTLC_TIMEOUT_WEIGHT
       +        return filter(lambda htlc: htlc.amount_msat // 1000 - weight * (feerate // 1000) >= conf.dust_limit_sat,
       +            self.htlcs_in_local if htlc_initiator == LOCAL else self.htlcs_in_remote)
       +
            @property
            def pending_remote_commitment(self):
                remote_msat, local_msat = self.amounts()
       t@@ -525,21 +518,15 @@ class HTLCStateMachine(PrintError):
                local_htlc_pubkey = derive_pubkey(self.local_config.htlc_basepoint.pubkey, this_point)
                local_revocation_pubkey = derive_blinded_pubkey(self.local_config.revocation_basepoint.pubkey, this_point)
        
       -        feerate = self.pending_feerate(REMOTE)
       -
                htlcs_in_local = []
       -        for htlc in self.htlcs_in_local:
       -            if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (feerate // 1000) < self.remote_config.dust_limit_sat:
       -                continue
       +        for htlc in self.included_htlcs(REMOTE, LOCAL):
                    htlcs_in_local.append(
       -                ( make_received_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat))
       +                ScriptHtlc( make_received_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc))
        
                htlcs_in_remote = []
       -        for htlc in self.htlcs_in_remote:
       -            if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (feerate // 1000) < self.remote_config.dust_limit_sat:
       -                continue
       +        for htlc in self.included_htlcs(REMOTE, REMOTE):
                    htlcs_in_remote.append(
       -                ( make_offered_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash), htlc.amount_msat))
       +                ScriptHtlc( make_offered_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash), htlc))
        
                commit = self.make_commitment(self.remote_state.ctn + 1,
                    False, this_point,
       t@@ -575,18 +562,14 @@ class HTLCStateMachine(PrintError):
                feerate = self.pending_feerate(LOCAL)
        
                htlcs_in_local = []
       -        for htlc in self.htlcs_in_local:
       -            if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (feerate // 1000) < self.local_config.dust_limit_sat:
       -                continue
       +        for htlc in self.included_htlcs(LOCAL, LOCAL):
                    htlcs_in_local.append(
       -                ( make_offered_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash), htlc.amount_msat))
       +                ScriptHtlc( make_offered_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash), htlc))
        
                htlcs_in_remote = []
       -        for htlc in self.htlcs_in_remote:
       -            if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (feerate // 1000) < self.local_config.dust_limit_sat:
       -                continue
       +        for htlc in self.included_htlcs(LOCAL, REMOTE):
                    htlcs_in_remote.append(
       -                ( make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat))
       +                ScriptHtlc( make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc))
        
                commit = self.make_commitment(self.local_state.ctn + 1,
                    True, this_point,
   DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py
       t@@ -25,6 +25,8 @@ LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amo
        ChannelConstraints = namedtuple("ChannelConstraints", ["capacity", "is_initiator", "funding_txn_minimum_depth"])
        #OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"])
        
       +ScriptHtlc = namedtuple('ScriptHtlc', ['redeem_script', 'htlc'])
       +
        class Outpoint(NamedTuple("Outpoint", [('txid', str), ('output_index', int)])):
            def to_str(self):
                return "{}:{}".format(self.txid, self.output_index)
       t@@ -235,7 +237,8 @@ def make_received_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, p
                + bitcoin.add_number_to_script(cltv_expiry) \
                + bytes([opcodes.OP_CLTV, opcodes.OP_DROP, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF])
        
       -def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, amount_msat, cltv_expiry, payment_hash, commit, original_htlc_output_index):
       +def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, commit, htlc):
       +    amount_msat, cltv_expiry, payment_hash = htlc.amount_msat, htlc.cltv_expiry, htlc.payment_hash
            conf = chan.local_config if for_us else chan.remote_config
            other_conf = chan.local_config if not for_us else chan.remote_config
        
       t@@ -258,8 +261,9 @@ def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, amount_msat, c
                preimage_script = make_received_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash, cltv_expiry)
            else:
                preimage_script = make_offered_htlc(other_revocation_pubkey, other_htlc_pubkey, htlc_pubkey, payment_hash)
       +    output_idx = commit.htlc_output_indices[htlc.payment_hash]
            htlc_tx_inputs = make_htlc_tx_inputs(
       -        commit.txid(), commit.htlc_output_indices[original_htlc_output_index],
       +        commit.txid(), output_idx,
                revocationpubkey=revocation_pubkey,
                local_delayedpubkey=delayedpubkey,
                amount_msat=amount_msat,
       t@@ -290,20 +294,21 @@ def make_funding_input(local_funding_pubkey: bytes, remote_funding_pubkey: bytes
            return c_input, payments
        
        def make_outputs(fee_msat: int, we_pay_fee: bool, local_amount: int, remote_amount: int,
       -        local_tupl, remote_tupl, htlcs: List[Tuple[bytes, int]], dust_limit_sat: int) -> Tuple[List[TxOutput], List[TxOutput]]:
       +        local_tupl, remote_tupl, htlcs: List[ScriptHtlc], dust_limit_sat: int) -> Tuple[List[TxOutput], List[TxOutput]]:
            to_local_amt = local_amount - (fee_msat if we_pay_fee else 0)
            to_local = TxOutput(*local_tupl, to_local_amt // 1000)
            to_remote_amt = remote_amount - (fee_msat if not we_pay_fee else 0)
            to_remote = TxOutput(*remote_tupl, to_remote_amt // 1000)
       -    c_outputs = [to_local, to_remote]
       -    for script, msat_amount in htlcs:
       -        c_outputs += [TxOutput(bitcoin.TYPE_ADDRESS,
       +    non_htlc_outputs = [to_local, to_remote]
       +    htlc_outputs = []
       +    for script, htlc in htlcs:
       +        htlc_outputs.append(TxOutput(bitcoin.TYPE_ADDRESS,
                                       bitcoin.redeem_script_to_address('p2wsh', bh2u(script)),
       -                               msat_amount // 1000)]
       +                               htlc.amount_msat // 1000))
        
            # trim outputs
       -    c_outputs_filtered = list(filter(lambda x:x[2]>= dust_limit_sat, c_outputs))
       -    return c_outputs, c_outputs_filtered
       +    c_outputs_filtered = list(filter(lambda x: x.value >= dust_limit_sat, non_htlc_outputs + htlc_outputs))
       +    return htlc_outputs, c_outputs_filtered
        
        
        def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey,
       t@@ -331,19 +336,21 @@ def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey,
            fee = fee // 1000 * 1000
            we_pay_fee = for_us == we_are_initiator
        
       -    c_outputs, c_outputs_filtered = make_outputs(fee, we_pay_fee, local_amount, remote_amount,
       +    htlc_outputs, c_outputs_filtered = make_outputs(fee, we_pay_fee, local_amount, remote_amount,
                (bitcoin.TYPE_ADDRESS, local_address), (bitcoin.TYPE_ADDRESS, remote_address), htlcs, dust_limit_sat)
        
       -    assert sum(x[2] for x in c_outputs) <= funding_sat
       +    assert sum(x.value for x in c_outputs_filtered) <= funding_sat
        
            # create commitment tx
            tx = Transaction.from_io(c_inputs, c_outputs_filtered, locktime=locktime, version=2)
        
            tx.htlc_output_indices = {}
       -    for idx, output in enumerate(c_outputs):
       +    assert len(htlcs) == len(htlc_outputs)
       +    for script_htlc, output in zip(htlcs, htlc_outputs):
                if output in tx.outputs():
                    # minus the first two outputs (to_local, to_remote)
       -            tx.htlc_output_indices[idx - 2] = tx.outputs().index(output)
       +            assert script_htlc.htlc.payment_hash not in tx.htlc_output_indices
       +            tx.htlc_output_indices[script_htlc.htlc.payment_hash] = tx.outputs().index(output)
        
            return tx