From b76858c1a10a6fbb0f45ea4ce67b392f87852303 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 22 Jan 2016 06:45:28 +1030 Subject: [PATCH] daemon: implement HTLC expiry. We do the simplest thing: a timer goes off, and we check all HTLCs for one which has expired more than 30 seconds ago. Signed-off-by: Rusty Russell --- daemon/packets.c | 63 +++++++++++++++++++++++++++++++++++++++++++-- daemon/peer.c | 59 ++++++++++++++++++++++++++++++++++++++++++ daemon/peer.h | 3 +++ daemon/test/test.sh | 16 ++++++++++++ 4 files changed, 139 insertions(+), 2 deletions(-) diff --git a/daemon/packets.c b/daemon/packets.c index 36f8f82fd..f4e8afb1d 100644 --- a/daemon/packets.c +++ b/daemon/packets.c @@ -163,7 +163,14 @@ Pkt *pkt_htlc_fulfill(const tal_t *ctx, const struct peer *peer, Pkt *pkt_htlc_timedout(const tal_t *ctx, const struct peer *peer, const struct htlc_progress *htlc_prog) { - FIXME_STUB(peer); + UpdateTimedoutHtlc *t = tal(ctx, UpdateTimedoutHtlc); + + update_timedout_htlc__init(t); + + t->revocation_hash = sha256_to_proto(t, &htlc_prog->our_revocation_hash); + t->r_hash = sha256_to_proto(t, &htlc_prog->htlc->rhash); + + return make_pkt(ctx, PKT__PKT_UPDATE_TIMEDOUT_HTLC, t); } Pkt *pkt_htlc_routefail(const tal_t *ctx, const struct peer *peer, @@ -448,6 +455,7 @@ Pkt *accept_pkt_htlc_update(const tal_t *ctx, /* Add the htlc to their side of channel. */ funding_add_htlc(&cur->cstate->b, cur->htlc->msatoshis, &cur->htlc->expiry, &cur->htlc->rhash); + peer_add_htlc_expiry(peer, &cur->htlc->expiry); peer_get_revocation_hash(peer, peer->num_htlcs+1, &cur->our_revocation_hash); @@ -504,6 +512,7 @@ Pkt *accept_pkt_htlc_routefail(const tal_t *ctx, " milli-satoshis", cur->htlc->msatoshis); } funding_remove_htlc(&cur->cstate->a, i); + /* FIXME: Remove timer. */ peer_get_revocation_hash(peer, peer->num_htlcs+1, &cur->our_revocation_hash); @@ -526,7 +535,57 @@ fail: Pkt *accept_pkt_htlc_timedout(const tal_t *ctx, struct peer *peer, const Pkt *pkt) { - FIXME_STUB(peer); + const UpdateTimedoutHtlc *t = pkt->update_timedout_htlc; + struct htlc_progress *cur = tal(peer, struct htlc_progress); + Pkt *err; + size_t i; + struct sha256 rhash; + + proto_to_sha256(t->revocation_hash, &cur->their_revocation_hash); + proto_to_sha256(t->r_hash, &rhash); + + i = funding_find_htlc(&peer->cstate->a, &rhash); + if (i == tal_count(peer->cstate->a.htlcs)) { + err = pkt_err(ctx, "Unknown HTLC"); + goto fail; + } + + cur->htlc = &peer->cstate->a.htlcs[i]; + + /* Do we agree it has timed out? */ + if (controlled_time().ts.tv_sec < abs_locktime_to_seconds(&cur->htlc->expiry)) { + err = pkt_err(ctx, "HTLC has not yet expired!"); + goto fail; + } + + /* Removing it should not fail: we regain HTLC amount */ + cur->cstate = copy_funding(cur, peer->cstate); + if (!funding_delta(peer->us.offer_anchor == CMD_OPEN_WITH_ANCHOR, + peer->anchor.satoshis, + 0, -cur->htlc->msatoshis, + &cur->cstate->a, &cur->cstate->b)) { + fatal("Unexpected failure fulfilling HTLC of %"PRIu64 + " milli-satoshis", cur->htlc->msatoshis); + } + funding_remove_htlc(&cur->cstate->a, i); + /* FIXME: Remove timer. */ + + peer_get_revocation_hash(peer, peer->num_htlcs+1, + &cur->our_revocation_hash); + + /* Now we create the commit tx pair. */ + make_commit_txs(cur, peer, &cur->our_revocation_hash, + &cur->their_revocation_hash, + cur->cstate, + &cur->our_commit, &cur->their_commit); + + assert(!peer->current_htlc); + peer->current_htlc = cur; + return NULL; + +fail: + tal_free(cur); + return err; } Pkt *accept_pkt_htlc_fulfill(const tal_t *ctx, diff --git a/daemon/peer.c b/daemon/peer.c index f51d4d67d..cab293948 100644 --- a/daemon/peer.c +++ b/daemon/peer.c @@ -1070,6 +1070,64 @@ static void set_htlc_command(struct peer *peer, set_current_command(peer, cmd, peer->current_htlc, jsoncmd); } +/* FIXME: Keep a timeout for each peer, in case they're unresponsive. */ + +static void check_htlc_expiry(struct peer *peer, void *unused) +{ + size_t i; + + /* Check their htlcs for expiry. */ + for (i = 0; i < tal_count(peer->cstate->b.htlcs); i++) { + struct channel_htlc *htlc = &peer->cstate->b.htlcs[i]; + struct channel_state *cstate; + + /* Not a seconds-based expiry? */ + if (!abs_locktime_is_seconds(&htlc->expiry)) + continue; + + /* Not well-expired? */ + if (controlled_time().ts.tv_sec - 30 + < abs_locktime_to_seconds(&htlc->expiry)) + continue; + + cstate = copy_funding(peer, peer->cstate); + + /* This should never fail! */ + if (!funding_delta(peer->them.offer_anchor + == CMD_OPEN_WITH_ANCHOR, + peer->anchor.satoshis, + 0, + -htlc->msatoshis, + &cstate->b, &cstate->a)) { + fatal("Unexpected failure expirint HTLC of %"PRIu64 + " milli-satoshis", htlc->msatoshis); + } + funding_remove_htlc(&cstate->b, i); + + set_htlc_command(peer, cstate, NULL, htlc, + CMD_SEND_HTLC_TIMEDOUT, NULL); + return; + } +} + +static void htlc_expiry_timeout(struct peer *peer) +{ + log_debug(peer->log, "Expiry timedout!"); + queue_cmd(peer, check_htlc_expiry, NULL); +} + +void peer_add_htlc_expiry(struct peer *peer, + const struct abs_locktime *expiry) +{ + time_t when; + + /* Add 30 seconds to be sure peers agree on timeout. */ + when = abs_locktime_to_seconds(expiry) - controlled_time().ts.tv_sec; + when += 30; + + oneshot_timeout(peer->dstate, peer, when, htlc_expiry_timeout, peer); +} + struct newhtlc { struct channel_htlc *htlc; struct command *jsoncmd; @@ -1097,6 +1155,7 @@ static void do_newhtlc(struct peer *peer, struct newhtlc *newhtlc) /* Add the htlc to our side of channel. */ funding_add_htlc(&cstate->a, newhtlc->htlc->msatoshis, &newhtlc->htlc->expiry, &newhtlc->htlc->rhash); + peer_add_htlc_expiry(peer, &newhtlc->htlc->expiry); set_htlc_command(peer, cstate, newhtlc->jsoncmd, &cstate->a.htlcs[tal_count(cstate->a.htlcs)-1], diff --git a/daemon/peer.h b/daemon/peer.h index e6df21aa4..904cd5708 100644 --- a/daemon/peer.h +++ b/daemon/peer.h @@ -132,4 +132,7 @@ void make_commit_txs(const tal_t *ctx, const struct channel_state *cstate, struct bitcoin_tx **ours, struct bitcoin_tx **theirs); +void peer_add_htlc_expiry(struct peer *peer, + const struct abs_locktime *expiry); + #endif /* LIGHTNING_DAEMON_PEER_H */ diff --git a/daemon/test/test.sh b/daemon/test/test.sh index 35b8e4255..66e574ae8 100755 --- a/daemon/test/test.sh +++ b/daemon/test/test.sh @@ -122,6 +122,22 @@ $LCLI2 failhtlc $ID1 $RHASH # Back to how we were before. check_status 949999000 49000000 "" 0 1000000 "" +# Same again, but this time it expires. +$LCLI1 newhtlc $ID2 10000000 $EXPIRY $RHASH + +# Check channel status +check_status 939999000 49000000 '{ "msatoshis" : 10000000, "expiry" : { "second" : '$EXPIRY' }, "rhash" : "'$RHASH'" } ' 0 1000000 "" + +# Make sure node1 accepts the expiry packet. +$LCLI1 dev-mocktime $(($EXPIRY)) + +# This should make node2 send it. +$LCLI2 dev-mocktime $(($EXPIRY + 31)) +sleep 1 + +# Back to how we were before. +check_status 949999000 49000000 "" 0 1000000 "" + sleep 1 $LCLI1 stop