tlnbase: fix pay(), save htlc_id's, generate onion packet correctly - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit b81fb449527a5416b7224ed72f2e9a6e536ff9dd DIR parent 34da1349e0e7f65844b21335e5d59995a7427502 HTML Author: Janus <ysangkok@gmail.com> Date: Wed, 16 May 2018 15:42:54 +0200 lnbase: fix pay(), save htlc_id's, generate onion packet correctly Diffstat: M lib/lnbase.py | 81 ++++++++++++++++++++++--------- M lib/tests/test_lnbase_online.py | 13 ++++++------- 2 files changed, 63 insertions(+), 31 deletions(-) --- DIR diff --git a/lib/lnbase.py b/lib/lnbase.py t@@ -275,8 +275,8 @@ ChannelConfig = namedtuple("ChannelConfig", [ "payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint", "to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"]) OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"]) -RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_sat", "revocation_store", "last_per_commitment_point"]) -LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_sat"]) +RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_sat", "revocation_store", "last_per_commitment_point", "next_htlc_id"]) +LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_sat", "next_htlc_id"]) ChannelConstraints = namedtuple("ChannelConstraints", ["feerate", "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"]) t@@ -563,6 +563,11 @@ def sign_and_get_sig_string(tx, local_config, remote_config): sig_64 = sigencode_string_canonize(r, s, SECP256k1.generator.order()) return sig_64 +def is_synced(network): + local_height, server_height = network.get_status_value("updated") + synced = server_height != 0 and network.is_up_to_date() and local_height >= server_height + return synced + class Peer(PrintError): def __init__(self, host, port, pubkey, privkey, request_initial_sync=False, network=None): self.host = host t@@ -606,7 +611,7 @@ class Peer(PrintError): def send_message(self, msg): message_type, payload = decode_msg(msg) - self.print_error("Sending '%s'"%message_type.upper(), payload) + self.print_error("Sending '%s'"%message_type.upper()) l = len(msg).to_bytes(2, 'big') lc = aead_encrypt(self.sk, self.sn(), b'', l) c = aead_encrypt(self.sk, self.sn(), b'', msg) t@@ -937,12 +942,14 @@ class Peer(PrintError): next_per_commitment_point=None, last_per_commitment_point=remote_per_commitment_point, amount_sat=remote_amount, - revocation_store=their_revocation_store + revocation_store=their_revocation_store, + next_htlc_id = 0 ), local_state=LocalState( ctn = 0, per_commitment_secret_seed=per_commitment_secret_seed, - amount_sat=local_amount + amount_sat=local_amount, + next_htlc_id = 0 ), constraints=ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth) ) t@@ -1040,17 +1047,22 @@ class Peer(PrintError): ) ) return last_secret, this_point, next_point + their_revstore = chan.remote_state.revocation_store sat = int(sat) - cltv_expiry = wallet.get_local_height() + 9 + await asyncio.sleep(1) + while not is_synced(wallet.network): + await asyncio.sleep(1) + print("sleeping more") + cltv_expiry = wallet.get_local_height() + chan.remote_config.to_self_delay assert sat > 0, "sat is not positive" amount_msat = sat * 1000 assert type(self.pubkey) is bytes hops_data = [OnionHopsDataSingle(OnionPerHop(chan.short_channel_id, amount_msat.to_bytes(8, "big"), cltv_expiry.to_bytes(4, "big")))] - associated_data = b"" - onion = new_onion_packet([self.pubkey], self.privkey, hops_data, associated_data) + associated_data = payment_hash + onion = new_onion_packet([self.pubkey], os.urandom(32), hops_data, associated_data) - self.send_message(gen_msg("update_add_htlc", channel_id=chan.channel_id, id=0, cltv_expiry=cltv_expiry, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes())) + self.send_message(gen_msg("update_add_htlc", channel_id=chan.channel_id, id=chan.local_state.next_htlc_id, cltv_expiry=cltv_expiry, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes())) their_local_htlc_pubkey = derive_pubkey(chan.remote_config.htlc_basepoint.pubkey, chan.remote_state.next_per_commitment_point) their_remote_htlc_pubkey = derive_pubkey(chan.local_config.htlc_basepoint.pubkey, chan.remote_state.next_per_commitment_point) t@@ -1074,18 +1086,25 @@ class Peer(PrintError): self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig)) + try: + revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id] + finally: + del self.revoke_and_ack[chan.channel_id] + # TODO check revoke_and_ack results + last_secret, _, next_point = derive_and_incr() + their_revstore.add_next_entry(last_secret) self.send_message(gen_msg("revoke_and_ack", channel_id=chan.channel_id, per_commitment_secret=last_secret, next_per_commitment_point=next_point)) try: - revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id] + update_fulfill_htlc_msg = await self.update_fulfill_htlc[chan.channel_id] finally: - del self.revoke_and_ack[chan.channel_id] + del self.update_fulfill_htlc[chan.channel_id] - # TODO check revoke_and_ack results + # TODO use other fields too next_per_commitment_point = revoke_and_ack_msg["next_per_commitment_point"] try: t@@ -1096,26 +1115,37 @@ class Peer(PrintError): # TODO check commitment_signed results last_secret, _, next_point = derive_and_incr() + their_revstore.add_next_entry(last_secret) self.send_message(gen_msg("revoke_and_ack", channel_id=chan.channel_id, per_commitment_secret=last_secret, next_per_commitment_point=next_point)) - try: - update_fulfill_htlc_msg = await self.update_fulfill_htlc[chan.channel_id] - finally: - del self.update_fulfill_htlc[chan.channel_id] + bare_ctx = make_commitment_using_open_channel(chan, chan.remote_state.ctn + 2, False, next_per_commitment_point, + chan.remote_state.amount_sat + sat, chan.local_state.amount_sat - sat) - print("update_fulfill_htlc_msg", update_fulfill_htlc_msg) + sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config) + self.send_message(gen_msg("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=0)) try: - commitment_signed_msg = await self.commitment_signed[chan.channel_id] + revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id] finally: - del self.commitment_signed[chan.channel_id] - - # TODO send revoke_and_ack + del self.revoke_and_ack[chan.channel_id] + # TODO check revoke_and_ack results - # TODO send commitment_signed + return chan._replace( + local_state=chan.local_state._replace( + amount_sat=chan.local_state.amount_sat - sat, + next_htlc_id=chan.local_state.next_htlc_id + 1 + ), + remote_state=chan.remote_state._replace( + ctn=chan.remote_state.ctn + 2, + revocation_store=their_revstore, + last_per_commitment_point=next_per_commitment_point, + next_per_commitment_point=revoke_and_ack_msg["next_per_commitment_point"], + amount_sat=chan.remote_state.amount_sat + sat + ) + ) async def receive_commitment_revoke_ack(self, chan, expected_received_sat, payment_preimage): def derive_and_incr(): t@@ -1143,8 +1173,10 @@ class Peer(PrintError): finally: del self.commitment_signed[channel_id] + assert len(self.unfulfilled_htlcs) == 1 htlc = self.unfulfilled_htlcs.pop() htlc_id = int.from_bytes(htlc["id"], 'big') + assert htlc_id == chan.remote_state.next_htlc_id, (htlc_id, chan.remote_state.next_htlc_id) cltv_expiry = int.from_bytes(htlc["cltv_expiry"], 'big') # TODO verify sanity of their cltv expiry amount_msat = int.from_bytes(htlc["amount_msat"], 'big') t@@ -1264,7 +1296,8 @@ class Peer(PrintError): revocation_store=their_revstore, last_per_commitment_point=remote_next_commitment_point, next_per_commitment_point=revoke_and_ack_msg["next_per_commitment_point"], - amount_sat=chan.remote_state.amount_sat - expected_received_sat + amount_sat=chan.remote_state.amount_sat - expected_received_sat, + next_htlc_id=htlc_id + 1 ) ) t@@ -1275,7 +1308,7 @@ class Peer(PrintError): def on_update_fulfill_htlc(self, payload): channel_id = int.from_bytes(payload["channel_id"], 'big') - self.update_fulfill_htlc[channel_id].set_reuslt(payload) + self.update_fulfill_htlc[channel_id].set_result(payload) def on_update_fail_malformed_htlc(self, payload): self.on_error(payload) DIR diff --git a/lib/tests/test_lnbase_online.py b/lib/tests/test_lnbase_online.py t@@ -128,14 +128,13 @@ if __name__ == "__main__": addr = lndecode(sys.argv[6], expected_hrp="sb" if sys.argv[2] == "simnet" else "tb") payment_hash = addr.paymenthash amt = int(addr.amount * COIN) - print("amt", amt) - await peer.pay(wallet, openchannel, amt, payment_hash) - return + advanced_channel = await peer.pay(wallet, openchannel, amt, payment_hash) + else: + expected_received_sat = 200000 + pay_req = lnencode(LnAddr(RHASH, amount=1/Decimal(COIN)*expected_received_sat, tags=[('d', 'one cup of coffee')]), peer.privkey[:32]) + print("payment request", pay_req) + advanced_channel = await peer.receive_commitment_revoke_ack(openchannel, expected_received_sat, payment_preimage) - expected_received_sat = 200000 - pay_req = lnencode(LnAddr(RHASH, amount=1/Decimal(COIN)*expected_received_sat, tags=[('d', 'one cup of coffee')]), peer.privkey[:32]) - print("payment request", pay_req) - advanced_channel = await peer.receive_commitment_revoke_ack(openchannel, expected_received_sat, payment_preimage) dumped = serialize_channels([advanced_channel]) wallet.storage.put("channels", dumped) wallet.storage.write()