From be4af38d0c47ef425f9f4dcf48ac8f7dabc38592 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 1 Apr 2017 21:28:30 +1030 Subject: [PATCH] channel: unwrap and send incoming HTLCs to master. So far it just looks it up, marks it resolved, then does nothing. Signed-off-by: Rusty Russell --- lightningd/Makefile | 4 +- lightningd/channel.c | 1 - lightningd/channel/Makefile | 2 +- lightningd/channel/channel.c | 93 ++++++++++++++++++++++++- lightningd/channel/channel_wire.csv | 1 + lightningd/peer_control.c | 103 ++++++++++++++++++++++++++++ lightningd/test/test-basic | 15 +++- 7 files changed, 211 insertions(+), 8 deletions(-) diff --git a/lightningd/Makefile b/lightningd/Makefile index 2f95c0b02..4da68b5ea 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -23,7 +23,6 @@ LIGHTNINGD_OLD_SRC := \ daemon/opt_time.c \ daemon/pseudorand.c \ daemon/routing.c \ - daemon/sphinx.c \ daemon/watch.c LIGHTNINGD_OLD_OBJS := $(LIGHTNINGD_OLD_SRC:.c=.o) LIGHTNINGD_OLD_HEADERS := $(LIGHTNINGD_OLD_SRC:.c=.h) @@ -31,6 +30,7 @@ LIGHTNINGD_OLD_HEADERS := $(LIGHTNINGD_OLD_SRC:.c=.h) LIGHTNINGD_OLD_LIB_SRC := \ daemon/htlc_state.c \ daemon/pseudorand.c \ + daemon/sphinx.c \ daemon/timeout.c LIGHTNINGD_OLD_LIB_OBJS := $(LIGHTNINGD_OLD_LIB_SRC:.c=.o) LIGHTNINGD_OLD_LIB_HEADERS := $(LIGHTNINGD_OLD_LIB_SRC:.c=.h) @@ -104,11 +104,11 @@ LIGHTNINGD_HEADERS = $(LIGHTNINGD_HEADERS_NOGEN) $(LIGHTNINGD_HEADERS_GEN) $(LIG # These included makefiles add their headers to the LIGHTNINGD_HEADERS # variable so the include must preceed any actual use of the variable. -include lightningd/channel/Makefile include lightningd/hsm/Makefile include lightningd/handshake/Makefile include lightningd/gossip/Makefile include lightningd/opening/Makefile +include lightningd/channel/Makefile $(LIGHTNINGD_OBJS) $(LIGHTNINGD_LIB_OBJS): $(LIGHTNINGD_HEADERS) diff --git a/lightningd/channel.c b/lightningd/channel.c index 5f37aebc9..28ba8fd7b 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -455,7 +455,6 @@ enum channel_add_err channel_add_htlc(struct channel *channel, assert(balance_msat >= 0); for (i = 0; i < tal_count(adding); i++) balance_msat += balance_adding_htlc(adding[i], sender); - assert(balance_msat >= 0); /* This is a little subtle: * diff --git a/lightningd/channel/Makefile b/lightningd/channel/Makefile index 1f562170e..0c6709c33 100644 --- a/lightningd/channel/Makefile +++ b/lightningd/channel/Makefile @@ -43,7 +43,7 @@ lightningd/channel/gen_channel_wire.c: $(WIRE_GEN) lightningd/channel/channel_wi LIGHTNINGD_CHANNEL_OBJS := $(LIGHTNINGD_CHANNEL_SRC:.c=.o) $(LIGHTNINGD_CHANNEL_GEN_SRC:.c=.o) -lightningd/lightningd_channel: $(LIGHTNINGD_OLD_LIB_OBJS) $(LIGHTNINGD_LIB_OBJS) $(LIGHTNINGD_CHANNEL_OBJS) $(CORE_OBJS) $(CORE_TX_OBJS) $(WIRE_OBJS) $(BITCOIN_OBJS) $(CCAN_OBJS) $(CCAN_SHACHAIN48_OBJ) $(LIGHTNINGD_HSM_CLIENT_OBJS) $(LIBBASE58_OBJS) libsecp256k1.a libsodium.a libwallycore.a +lightningd/lightningd_channel: $(LIGHTNINGD_OLD_LIB_OBJS) $(LIGHTNINGD_LIB_OBJS) $(LIGHTNINGD_CHANNEL_OBJS) $(WIRE_ONION_OBJS) $(CORE_OBJS) $(CORE_TX_OBJS) $(WIRE_OBJS) $(BITCOIN_OBJS) $(CCAN_OBJS) $(CCAN_SHACHAIN48_OBJ) $(LIGHTNINGD_HSM_CLIENT_OBJS) $(LIBBASE58_OBJS) libsecp256k1.a libsodium.a libwallycore.a $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) check-source: $(LIGHTNINGD_CHANNEL_SRC_NOGEN:%=check-src-include-order/%) diff --git a/lightningd/channel/channel.c b/lightningd/channel/channel.c index 476b2187d..5c0132656 100644 --- a/lightningd/channel/channel.c +++ b/lightningd/channel/channel.c @@ -5,11 +5,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -21,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,10 +40,11 @@ #include #include -/* stdin == requests, 3 == peer, 4 = gossip */ +/* stdin == requests, 3 == peer, 4 = gossip, 5 = HSM */ #define REQ_FD STDIN_FILENO #define PEER_FD 3 #define GOSSIP_FD 4 +#define HSM_FD 5 struct peer { struct peer_crypto_state pcs; @@ -514,7 +518,89 @@ static void our_htlc_failed(const struct htlc *htlc, struct peer *peer) static void their_htlc_locked(const struct htlc *htlc, struct peer *peer) { - status_trace("FIXME: their htlc %"PRIu64" locked", htlc->id); + tal_t *tmpctx = tal_tmpctx(peer); + u8 *msg; + struct onionpacket *op; + struct route_step *rs; + struct sha256 ss, bad_onion_sha; + enum onion_type failcode; + enum channel_remove_err rerr; + struct pubkey ephemeral; + + status_trace("their htlc %"PRIu64" locked", htlc->id); + + /* We unwrap the onion now. */ + /* FIXME: We could do this earlier and call HSM async, for speed. */ + op = parse_onionpacket(tmpctx, htlc->routing, TOTAL_PACKET_SIZE); + if (!op) { + /* FIXME: could be bad version, bad key. */ + failcode = WIRE_INVALID_ONION_VERSION; + goto bad_onion; + } + + /* Because wire takes struct pubkey. */ + ephemeral.pubkey = op->ephemeralkey; + msg = towire_hsm_ecdh_req(tmpctx, &ephemeral); + if (!wire_sync_write(HSM_FD, msg)) + status_failed(WIRE_CHANNEL_HSM_FAILED, "Writing ecdh req"); + msg = wire_sync_read(tmpctx, HSM_FD); + if (!msg || !fromwire_hsm_ecdh_resp(msg, NULL, &ss)) + status_failed(WIRE_CHANNEL_HSM_FAILED, "Reading ecdh response"); + + if (memeqzero(&ss, sizeof(ss))) { + failcode = WIRE_INVALID_ONION_KEY; + goto bad_onion; + } + + rs = process_onionpacket(tmpctx, op, ss.u.u8, htlc->rhash.u.u8, + sizeof(htlc->rhash)); + if (!rs) { + failcode = WIRE_INVALID_ONION_HMAC; + goto bad_onion; + } + + /* Unknown realm isn't a bad onion, it's a normal failure. */ + /* FIXME: Push complete hoppayload up and have master parse? */ + if (rs->hoppayload->realm != 0) { + failcode = WIRE_INVALID_REALM; + msg = towire_update_fail_htlc(tmpctx, &peer->channel_id, + htlc->id, NULL); + msg_enqueue(&peer->peer_out, take(msg)); + goto remove_htlc; + } + + /* Tell master to deal with it. */ + msg = towire_channel_accepted_htlc(tmpctx, htlc->id, htlc->msatoshi, + abs_locktime_to_blocks(&htlc->expiry), + &htlc->rhash, + serialize_onionpacket(tmpctx, + rs->next), + rs->nextcase == ONION_FORWARD, + rs->hoppayload->amt_to_forward, + rs->hoppayload->outgoing_cltv_value); + daemon_conn_send(&peer->master, take(msg)); + tal_free(tmpctx); + return; + +bad_onion: + sha256(&bad_onion_sha, htlc->routing, TOTAL_PACKET_SIZE); + msg = towire_update_fail_malformed_htlc(tmpctx, &peer->channel_id, + htlc->id, &bad_onion_sha, + failcode); + msg_enqueue(&peer->peer_out, take(msg)); + +remove_htlc: + status_trace("htlc %"PRIu64" %s", htlc->id, onion_type_name(failcode)); + rerr = channel_fail_htlc(peer->channel, REMOTE, htlc->id); + if (rerr != CHANNEL_ERR_REMOVE_OK) + peer_failed(io_conn_fd(peer->peer_conn), + &peer->pcs.cs, + &peer->channel_id, + WIRE_CHANNEL_INTERNAL_ERROR, + "Could not fail malformed htlc %"PRIu64": %u", + htlc->id, rerr); + start_commit_timer(peer); + tal_free(tmpctx); } static void handle_peer_revoke_and_ack(struct peer *peer, const u8 *msg) @@ -772,7 +858,7 @@ static void handle_offer_htlc(struct peer *peer, const u8 *inmsg) u8 *msg; u32 amount_msat, cltv_expiry; struct sha256 payment_hash; - u8 onion_routing_packet[1254]; + u8 onion_routing_packet[TOTAL_PACKET_SIZE]; enum onion_type failcode; /* Subtle: must be tal_arr since we marshal using tal_len() */ const char *failmsg; @@ -933,6 +1019,7 @@ static struct io_plan *req_in(struct io_conn *conn, struct daemon_conn *master) case WIRE_CHANNEL_BAD_COMMAND: case WIRE_CHANNEL_HSM_FAILED: case WIRE_CHANNEL_CRYPTO_FAILED: + case WIRE_CHANNEL_INTERNAL_ERROR: case WIRE_CHANNEL_PEER_WRITE_FAILED: case WIRE_CHANNEL_PEER_READ_FAILED: case WIRE_CHANNEL_RECEIVED_FUNDING_LOCKED: diff --git a/lightningd/channel/channel_wire.csv b/lightningd/channel/channel_wire.csv index ba619b9e5..9e84b78c7 100644 --- a/lightningd/channel/channel_wire.csv +++ b/lightningd/channel/channel_wire.csv @@ -3,6 +3,7 @@ channel_bad_command,0x8000 # Also shouldn't happen channel_hsm_failed,0x8001 channel_crypto_failed,0x8002 +channel_internal_error,0x8003 # These are due to peer. channel_peer_write_failed,0x8010 diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index a08931458..f1908c8ec 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -9,8 +9,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -25,6 +27,7 @@ #include #include #include +#include #include static void destroy_peer(struct peer *peer) @@ -616,6 +619,104 @@ static bool opening_got_hsm_funding_sig(struct subd *hsm, const u8 *resp, return true; } +struct decoding_htlc { + struct peer *peer; + u64 id; + u32 amount_msat; + u32 cltv_expiry; + struct sha256 payment_hash; + u8 onion[1254]; + u8 shared_secret[32]; +}; + +static void fail_htlc(struct peer *peer, u64 htlc_id, enum onion_type failcode) +{ + log_broken(peer->log, "failed htlc %"PRIu64" code 0x%04x", + htlc_id, failcode); + /* FIXME: implement */ +} + +static void handle_localpay(struct peer *peer, + u64 htlc_id, + u32 amount_msat, + u32 cltv_expiry, + const struct sha256 *payment_hash) +{ + struct invoice *invoice = find_unpaid(peer->ld->dstate.invoices, + payment_hash); + + if (!invoice) { + fail_htlc(peer, htlc_id, WIRE_UNKNOWN_PAYMENT_HASH); + return; + } + + /* BOLT #4: + * + * If the amount paid is less than the amount expected, the final node + * MUST fail the HTLC. If the amount paid is more than the amount + * expected, the final node SHOULD fail the HTLC: + * + * 1. type: PERM|16 (`incorrect_payment_amount`) + */ + if (amount_msat < invoice->msatoshi) { + fail_htlc(peer, htlc_id, WIRE_INCORRECT_PAYMENT_AMOUNT); + return; + } else if (amount_msat > invoice->msatoshi * 2) { + fail_htlc(peer, htlc_id, WIRE_INCORRECT_PAYMENT_AMOUNT); + return; + } + + /* BOLT #4: + * + * If the `cltv-expiry` is too low, the final node MUST fail the HTLC: + */ + if (get_block_height(peer->ld->topology) + + peer->ld->dstate.config.deadline_blocks >= cltv_expiry) { + log_debug(peer->log, "Expiry cltv %u too close to current %u + deadline %u", + cltv_expiry, get_block_height(peer->ld->topology), + peer->ld->dstate.config.deadline_blocks); + fail_htlc(peer, htlc_id, WIRE_FINAL_EXPIRY_TOO_SOON); + return; + } + + /* FIXME: fail the peer if it doesn't tell us that htlc fulfill is + * committed before deadline. + */ + log_info(peer->ld->log, "Resolving invoice '%s' with HTLC %"PRIu64, + invoice->label, htlc_id); + + /* FIXME: msg = towire_channel_fulfill_htlc(htlc->id, &invoice->r); */ + resolve_invoice(&peer->ld->dstate, invoice); +} + +static int peer_accepted_htlc(struct peer *peer, const u8 *msg) +{ + u64 id; + u32 cltv_expiry, amount_msat; + struct sha256 payment_hash; + u8 next_onion[TOTAL_PACKET_SIZE]; + bool forward; + u64 amt_to_forward; + u32 outgoing_cltv_value; + + if (!fromwire_channel_accepted_htlc(msg, NULL, &id, &amount_msat, + &cltv_expiry, &payment_hash, + next_onion, &forward, + &amt_to_forward, + &outgoing_cltv_value)) { + log_broken(peer->log, "bad fromwire_channel_accepted_htlc %s", + tal_hex(peer, msg)); + return -1; + } + + if (forward) + log_broken(peer->log, "FIXME: Implement forwarding!"); + else + handle_localpay(peer, id, + amount_msat, cltv_expiry, &payment_hash); + return 0; +} + static int channel_msg(struct subd *sd, const u8 *msg, const int *unused) { enum channel_wire_type t = fromwire_peektype(msg); @@ -628,6 +729,7 @@ static int channel_msg(struct subd *sd, const u8 *msg, const int *unused) peer_set_condition(sd->peer, "Normal operation"); break; case WIRE_CHANNEL_ACCEPTED_HTLC: + return peer_accepted_htlc(sd->peer, msg); case WIRE_CHANNEL_FULFILLED_HTLC: case WIRE_CHANNEL_FAILED_HTLC: case WIRE_CHANNEL_MALFORMED_HTLC: @@ -639,6 +741,7 @@ static int channel_msg(struct subd *sd, const u8 *msg, const int *unused) case WIRE_CHANNEL_BAD_COMMAND: case WIRE_CHANNEL_HSM_FAILED: case WIRE_CHANNEL_CRYPTO_FAILED: + case WIRE_CHANNEL_INTERNAL_ERROR: case WIRE_CHANNEL_PEER_WRITE_FAILED: case WIRE_CHANNEL_PEER_READ_FAILED: case WIRE_CHANNEL_PEER_BAD_MESSAGE: diff --git a/lightningd/test/test-basic b/lightningd/test/test-basic index 6e32eccd6..02926232a 100755 --- a/lightningd/test/test-basic +++ b/lightningd/test/test-basic @@ -38,7 +38,7 @@ FUND_INPUT_TX=`$CLI getrawtransaction $FUND_INPUT_TXID` lcli1 addfunds $FUND_INPUT_TX | $FGREP '"satoshis" : 10000002' # Now fund a channel. -lcli1 fundchannel $ID2 100000 +lcli1 fundchannel $ID2 1000000 # Now wait for it to reach depth lcli1 getpeers info | $FGREP "Waiting for our funding tx" @@ -53,9 +53,22 @@ check "lcli2 getpeers | tr -s '\012\011\" ' ' ' | $FGREP 'condition : Normal ope SECRET=1de08917a61cb2b62ed5937d38577f6a7bfe59c176781c6d8128018e8b5ccdfd RHASH=`lcli1 dev-rhash $SECRET | sed 's/.*"\([0-9a-f]*\)".*/\1/'` +# This is actually dust lcli1 dev-newhtlc $ID2 100000 $(( $(blockheight) + 10 )) $RHASH +check "lcli1 getlog debug | $FGREP 'Sending commit_sig with 0 htlc sigs'" check "lcli2 getlog debug | $FGREP 'their htlc 0 locked'" +check "lcli2 getpeers info | $FGREP 'failed htlc 0 code 0x400f'" + +# This one isn't dust. +RHASH=`lcli2 invoice 100000000 testpayment1 | get_field rhash` +[ `lcli2 listinvoice testpayment1 | get_field complete` = false ] + +lcli1 dev-newhtlc $ID2 100000000 $(( $(blockheight) + 10 )) $RHASH +check "lcli1 getlog debug | $FGREP 'Sending commit_sig with 1 htlc sigs'" + +check "lcli2 getlog debug | $FGREP 'Resolving invoice '\'testpayment1\'' with HTLC 1'" +[ `lcli2 listinvoice testpayment1 | get_field complete` = true ] lcli1 stop lcli2 stop