commit 0f552422a674b91617ae3b3aa5478232f4362741
parent 1ffaed718c317f9d7bed04824020e84be3c88340
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:
2 files changed, 74 insertions(+), 10 deletions(-)
diff --git a/lib/lnbase.py b/lib/lnbase.py
@@ -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'),
@@ -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):
@@ -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
@@ -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")
@@ -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",
@@ -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.')
@@ -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)
diff --git a/lib/tests/test_lnbase_online.py b/lib/tests/test_lnbase_online.py
@@ -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)