URI: 
       tlnbase: handle commitment transaction update (receive funds, not working yet) - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 0f552422a674b91617ae3b3aa5478232f4362741
   DIR parent 1ffaed718c317f9d7bed04824020e84be3c88340
  HTML Author: Janus <ysangkok@gmail.com>
       Date:   Mon, 23 Apr 2018 19:52:24 +0200
       
       lnbase: handle commitment transaction update (receive funds, not working yet)
       
       Diffstat:
         M lib/lnbase.py                       |      74 +++++++++++++++++++++++++++----
         M lib/tests/test_lnbase_online.py     |      10 ++++++++--
       
       2 files changed, 74 insertions(+), 10 deletions(-)
       ---
   DIR diff --git a/lib/lnbase.py b/lib/lnbase.py
       t@@ -32,6 +32,15 @@ from . import transaction
        from .util import PrintError, bh2u, print_error, bfh, profiler
        from .transaction import opcodes, Transaction
        
       +from collections import namedtuple
       +LocalCtxArgs = namedtuple("LocalCtxArgs",
       +            ["ctn",
       +            "funding_pubkey", "remote_funding_pubkey", "remotepubkey",
       +            "base_point", "remote_payment_basepoint",
       +            "remote_revocation_pubkey", "local_delayedpubkey", "to_self_delay",
       +            "funding_txid", "funding_index", "funding_satoshis",
       +            "local_amount", "remote_amount", "dust_limit_satoshis"])
       +
        # hardcoded nodes
        node_list = [
            ('ecdsa.net', '9735', '038370f0e7a03eded3e1d41dc081084a87f0afa1c5b22090b4f3abb391eb15d8ff'),
       t@@ -76,7 +85,12 @@ def calcexp(exp, ma):
            Returns int
            """
            exp = str(exp)
       -    assert "*" not in exp
       +    if "*" in exp:
       +      assert "+" not in exp
       +      result = 1
       +      for term in exp.split("*"):
       +        result *= handlesingle(term, ma)
       +      return result
            return sum(handlesingle(x, ma) for x in exp.split("+"))
        
        def make_handler(k, v):
       t@@ -475,16 +489,23 @@ class Peer(PrintError):
                self.network = network
                self.read_buffer = b''
                self.ping_time = 0
       +        self.futures = ["channel_accepted",
       +            "funding_signed",
       +            "local_funding_locked",
       +            "remote_funding_locked",
       +            "commitment_signed"]
                self.channel_accepted = {}
                self.funding_signed = {}
                self.local_funding_locked = {}
                self.remote_funding_locked = {}
       +        self.commitment_signed = {}
                self.initialized = asyncio.Future()
                self.localfeatures = (0x08 if request_initial_sync else 0)
                # view of the network
                self.nodes = {} # received node announcements
                self.channel_db = ChannelDB()
                self.path_finder = LNPathFinder(self.channel_db)
       +        self.unfulfilled_htlcs = []
        
            def diagnostic_name(self):
                return self.host
       t@@ -588,10 +609,11 @@ class Peer(PrintError):
                f(payload)
        
            def on_error(self, payload):
       -        if payload["channel_id"] in self.channel_accepted:
       -            self.channel_accepted[payload["channel_id"]].set_exception(LightningError(payload["data"]))
       -        if payload["channel_id"] in self.funding_signed:
       -            self.funding_signed[payload["channel_id"]].set_exception(LightningError(payload["data"]))
       +        for i in self.futures:
       +            if payload["channel_id"] in getattr(self, i):
       +                getattr(self, i)[payload["channel_id"]].set_exception(LightningError(payload["data"]))
       +                return
       +        self.print_error("no future found to resolve", payload)
        
            def on_ping(self, payload):
                l = int.from_bytes(payload['num_pong_bytes'], byteorder="big")
       t@@ -693,8 +715,9 @@ class Peer(PrintError):
                ctn = 0
                #
                base_point = secret_to_pubkey(base_secret)
       +        per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, per_commitment_secret_index)
                per_commitment_point_first = secret_to_pubkey(int.from_bytes(
       -            get_per_commitment_secret_from_seed(per_commitment_secret_seed, per_commitment_secret_index),
       +            per_commitment_secret_first,
                    byteorder="big"))
                msg = gen_msg(
                    "open_channel",
       t@@ -778,13 +801,14 @@ class Peer(PrintError):
                self.print_error('received funding_signed')
                remote_sig = payload['signature']
                # verify remote signature
       -        local_ctx = make_commitment(
       +        local_ctx_args = LocalCtxArgs(
                    ctn,
                    funding_pubkey, remote_funding_pubkey, remotepubkey,
                    base_point, remote_payment_basepoint,
                    remote_revocation_pubkey, local_delayedpubkey, to_self_delay,
                    funding_txid, funding_index, funding_satoshis,
                    local_amount, remote_amount, dust_limit_satoshis)
       +        local_ctx = make_commitment(*local_ctx_args)
                pre_hash = bitcoin.Hash(bfh(local_ctx.serialize_preimage(0)))
                if not bitcoin.verify_signature(remote_funding_pubkey, remote_sig, pre_hash):
                    raise Exception('verifying remote signature failed.')
       t@@ -823,10 +847,44 @@ class Peer(PrintError):
                finally:
                    del self.remote_funding_locked[channel_id]
                self.print_error('Done waiting for remote_funding_locked', payload)
       +        self.commitment_signed[channel_id] = asyncio.Future()
       +        return channel_id, per_commitment_secret_seed, local_ctx_args, remote_funding_pubkey
       +    async def receive_commitment_revoke_ack(self, channel_id, per_commitment_secret_seed, last_pcs_index, local_ctx_args, expected_received_sat, remote_funding_pubkey, next_commitment_number):
       +        try:
       +            commitment_signed_msg = await self.commitment_signed[channel_id]
       +        finally:
       +            del self.commitment_signed[channel_id]
       +            # TODO make new future? (there could be more updates)
       +
       +        local_ctx_args = local_ctx_args._replace(local_amount = local_ctx_args.local_amount + expected_received_sat)
       +        local_ctx_args = local_ctx_args._replace(remote_amount = local_ctx_args.remote_amount - expected_received_sat)
       +        local_ctx_args = local_ctx_args._replace(ctn = next_commitment_number)
       +        new_commitment = make_commitment(*local_ctx_args)
       +        pre_hash = bitcoin.Hash(bfh(new_commitment.serialize_preimage(0)))
       +        if not bitcoin.verify_signature(remote_funding_pubkey, commitment_signed_msg["signature"], pre_hash):
       +            raise Exception('failed verifying signature of updated commitment transaction')
       +
       +        last_per_commitment_secret = get_per_commitment_secret_from_seed(per_commitment_secret_seed, last_pcs_index)
       +
       +        next_per_commitment_secret = get_per_commitment_secret_from_seed(per_commitment_secret_seed, last_pcs_index - 1)
       +        next_per_commitment_point = secret_to_pubkey(int.from_bytes(
       +            next_per_commitment_secret,
       +            byteorder="big"))
       +
       +        self.send_message(gen_msg("revoke_and_ack", channel_id=channel_id, per_commitment_secret=last_per_commitment_secret, next_per_commitment_point=next_per_commitment_point))
       +
       +    async def fulfill_htlc(self, channel_id, htlc_id, payment_preimage):
       +        self.send_message(gen_msg("update_fulfill_htlc", channel_id=channel_id, id=htlc_id, payment_preimage=payment_preimage))
       +
       +    def on_commitment_signed(self, payload):
       +        channel_id = int.from_bytes(payload['channel_id'], byteorder="big")
       +        self.commitment_signed[channel_id].set_result(payload)
        
            def on_update_add_htlc(self, payload):
                # no onion routing for the moment: we assume we are the end node
       -        self.print_error('on_update_htlc')
       +        self.print_error('on_update_add_htlc', payload)
       +        assert self.unfulfilled_htlcs == []
       +        self.unfulfilled_htlcs.append(payload)
        
        
        
   DIR diff --git a/lib/tests/test_lnbase_online.py b/lib/tests/test_lnbase_online.py
       t@@ -48,10 +48,16 @@ if __name__ == "__main__":
        
            # run blocking test
            async def async_test():
       -        RHASH = sha256(bytes.fromhex("01"*32))
       -        await peer.channel_establishment_flow(wallet, config, funding_satoshis, push_msat)
       +        payment_preimage = bytes.fromhex("01"*32)
       +        RHASH = sha256(payment_preimage)
       +        channel_id, per_commitment_secret_seed, local_ctx_args, remote_funding_pubkey = await peer.channel_establishment_flow(wallet, config, funding_satoshis, push_msat)
                pay_req = lnencode(LnAddr(RHASH, amount=Decimal("0.00000001")*10, tags=[('d', 'one cup of coffee')]), peer.privkey[:32])
                print("payment request", pay_req)
       +        last_pcs_index = 2**48 - 1
       +        expected_received_sat = 10
       +        await peer.receive_commitment_revoke_ack(channel_id, per_commitment_secret_seed, last_pcs_index, local_ctx_args, expected_received_sat, remote_funding_pubkey, next_commitment_number=1)
       +        htlc_id = 0 # TODO should correspond with received htlc (when handling more than just one update)
       +        await peer.fulfill_htlc(channel_id, htlc_id, payment_preimage)
                while True:
                    await asyncio.sleep(1)
            fut = asyncio.run_coroutine_threadsafe(async_test(), network.asyncio_loop)