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)