From d3cbde4b4631a66ef75e1e3ab124114703a3de1f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 1 Apr 2017 21:28:30 +1030 Subject: [PATCH] lightningd/channel: send and receive revoke_and_ack packets. Signed-off-by: Rusty Russell --- lightningd/channel/channel.c | 138 +++++++++++++++++++++++++++++++++-- lightningd/test/test-basic | 3 +- 2 files changed, 134 insertions(+), 7 deletions(-) diff --git a/lightningd/channel/channel.c b/lightningd/channel/channel.c index f5a9efd6a..476b2187d 100644 --- a/lightningd/channel/channel.c +++ b/lightningd/channel/channel.c @@ -48,6 +48,7 @@ struct peer { struct pubkey old_per_commit[NUM_SIDES]; struct pubkey current_per_commit[NUM_SIDES]; bool funding_locked[NUM_SIDES]; + u64 commit_index[NUM_SIDES]; /* Their sig for current commit. */ secp256k1_ecdsa_signature their_commit_sig; @@ -58,6 +59,9 @@ struct peer { /* Our shaseed for generating per-commitment-secrets. */ struct sha256 shaseed; + /* Their shachain. */ + struct shachain their_shachain; + /* BOLT #2: * * A sending node MUST set `id` to 0 for the first HTLC it offers, and @@ -279,6 +283,7 @@ static void send_commit(struct peer *peer) /* FIXME: Document this requirement in BOLT 2! */ /* We can't send two commits in a row. */ if (channel_awaiting_revoke_and_ack(peer->channel)) { + status_trace("Can't send commit: waiting for revoke_and_ack"); tal_free(tmpctx); return; } @@ -289,6 +294,7 @@ static void send_commit(struct peer *peer) * include any updates. */ if (!channel_sending_commit(peer->channel)) { + status_trace("Can't send commit: nothing to send"); tal_free(tmpctx); return; } @@ -369,6 +375,7 @@ static void start_commit_timer(struct peer *peer) static void handle_peer_commit_sig(struct peer *peer, const u8 *msg) { tal_t *tmpctx = tal_tmpctx(peer); + struct sha256 old_commit_secret; struct channel_id channel_id; secp256k1_ecdsa_signature commit_sig, *htlc_sigs; struct pubkey remotekey; @@ -466,11 +473,124 @@ static void handle_peer_commit_sig(struct peer *peer, const u8 *msg) status_trace("Received commit_sig with %zu htlc sigs", tal_count(htlc_sigs)); - /* This may have triggered changes, so restart timer. */ - start_commit_timer(peer); + struct pubkey oldpoint = peer->old_per_commit[LOCAL], test; + status_trace("Sending secret for point %"PRIu64" %s", + peer->commit_index[LOCAL]-1, + type_to_string(trc, struct pubkey, + &peer->old_per_commit[LOCAL])); + + peer->old_per_commit[LOCAL] = peer->current_per_commit[LOCAL]; + if (!next_per_commit_point(&peer->shaseed, &old_commit_secret, + &peer->current_per_commit[LOCAL], + peer->commit_index[LOCAL])) + status_failed(WIRE_CHANNEL_CRYPTO_FAILED, + "Deriving next commit_point"); + + pubkey_from_privkey((struct privkey *)&old_commit_secret, &test); + if (!pubkey_eq(&test, &oldpoint)) + status_failed(WIRE_CHANNEL_CRYPTO_FAILED, + "Invalid secret %s for commit_point", + tal_hexstr(msg, &old_commit_secret, + sizeof(old_commit_secret))); + + peer->commit_index[LOCAL]++; + + /* If this queues more changes on the other end, send commit. */ + if (channel_sending_revoke_and_ack(peer->channel)) { + status_trace("revoke_and_ack made pending: commit timer"); + start_commit_timer(peer); + } + + msg = towire_revoke_and_ack(msg, &channel_id, &old_commit_secret, + &peer->current_per_commit[LOCAL]); + msg_enqueue(&peer->peer_out, take(msg)); tal_free(tmpctx); } +static void our_htlc_failed(const struct htlc *htlc, struct peer *peer) +{ + status_trace("FIXME: our htlc %"PRIu64" failed", htlc->id); +} + +static void their_htlc_locked(const struct htlc *htlc, struct peer *peer) +{ + status_trace("FIXME: their htlc %"PRIu64" locked", htlc->id); +} + +static void handle_peer_revoke_and_ack(struct peer *peer, const u8 *msg) +{ + struct sha256 old_commit_secret; + struct privkey privkey; + struct channel_id channel_id; + struct pubkey per_commit_point, next_per_commit; + + if (!fromwire_revoke_and_ack(msg, NULL, &channel_id, &old_commit_secret, + &next_per_commit)) { + peer_failed(io_conn_fd(peer->peer_conn), + &peer->pcs.cs, + &peer->channel_id, + WIRE_CHANNEL_PEER_BAD_MESSAGE, + "Bad revoke_and_ack %s", tal_hex(msg, msg)); + } + + /* BOLT #2: + * + * A receiving node MUST check that `per-commitment-secret` generates + * the previous `per-commitment-point`, and MUST fail if it does + * not. + */ + memcpy(&privkey, &old_commit_secret, sizeof(privkey)); + if (!pubkey_from_privkey(&privkey, &per_commit_point)) { + peer_failed(io_conn_fd(peer->peer_conn), + &peer->pcs.cs, + &peer->channel_id, + WIRE_CHANNEL_PEER_BAD_MESSAGE, + "Bad privkey %s", + type_to_string(msg, struct privkey, &privkey)); + } + if (!pubkey_eq(&per_commit_point, &peer->old_per_commit[REMOTE])) { + peer_failed(io_conn_fd(peer->peer_conn), + &peer->pcs.cs, + &peer->channel_id, + WIRE_CHANNEL_PEER_BAD_MESSAGE, + "Wrong privkey %s for %s", + type_to_string(msg, struct privkey, &privkey), + type_to_string(msg, struct pubkey, + &peer->old_per_commit[REMOTE])); + } + + /* BOLT #2: + * + * A receiving node MAY fail if the `per-commitment-secret` was not + * generated by the protocol in [BOLT #3] + */ + if (!shachain_add_hash(&peer->their_shachain, + 281474976710655ULL - peer->commit_index[REMOTE], + &old_commit_secret)) { + peer_failed(io_conn_fd(peer->peer_conn), + &peer->pcs.cs, + &peer->channel_id, + WIRE_CHANNEL_PEER_BAD_MESSAGE, + "Bad shachain for privkey %"PRIu64" %s ", + peer->commit_index[REMOTE], + type_to_string(msg, struct privkey, &privkey)); + } + peer->commit_index[REMOTE]++; + peer->old_per_commit[REMOTE] = peer->current_per_commit[REMOTE]; + peer->current_per_commit[REMOTE] = next_per_commit; + + /* We start timer even if this returns false: we might have delayed + * commit because we were waiting for this! */ + if (channel_rcvd_revoke_and_ack(peer->channel, + our_htlc_failed, their_htlc_locked, + peer)) + status_trace("Commits outstanding after recv revoke_and_ack"); + else + status_trace("No commits outstanding after recv revoke_and_ack"); + + start_commit_timer(peer); +} + static struct io_plan *peer_in(struct io_conn *conn, struct peer *peer, u8 *msg) { enum wire_type type = fromwire_peektype(msg); @@ -510,6 +630,9 @@ static struct io_plan *peer_in(struct io_conn *conn, struct peer *peer, u8 *msg) case WIRE_COMMITMENT_SIGNED: handle_peer_commit_sig(peer, msg); goto done; + case WIRE_REVOKE_AND_ACK: + handle_peer_revoke_and_ack(peer, msg); + goto done; case WIRE_INIT: case WIRE_ERROR: case WIRE_OPEN_CHANNEL: @@ -523,7 +646,6 @@ static struct io_plan *peer_in(struct io_conn *conn, struct peer *peer, u8 *msg) case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: - case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: peer_failed(io_conn_fd(peer->peer_conn), &peer->pcs.cs, @@ -593,7 +715,11 @@ static void init_channel(struct peer *peer, const u8 *msg) /* We derive everything from the one secret seed. */ derive_basepoints(&seed, &funding_pubkey[LOCAL], &points[LOCAL], &peer->our_secrets, &peer->shaseed, - &peer->old_per_commit[LOCAL], 0); + &peer->old_per_commit[LOCAL], + peer->commit_index[LOCAL]); + status_trace("First per_commit_point = %s", + type_to_string(trc, struct pubkey, + &peer->old_per_commit[LOCAL])); peer->channel = new_channel(peer, &funding_txid, funding_txout, funding_satoshi, push_msat, feerate, @@ -619,7 +745,7 @@ static void handle_funding_locked(struct peer *peer, const u8 *msg) next_per_commit_point(&peer->shaseed, NULL, &peer->current_per_commit[LOCAL], - 0); + peer->commit_index[LOCAL]++); msg = towire_funding_locked(peer, &peer->channel_id, @@ -849,6 +975,8 @@ int main(int argc, char *argv[]) peer->htlc_id = 0; timers_init(&peer->timers, time_mono()); peer->commit_timer = NULL; + peer->commit_index[LOCAL] = peer->commit_index[REMOTE] = 0; + shachain_init(&peer->their_shachain); status_setup_async(&peer->master); msg_queue_init(&peer->peer_out, peer); diff --git a/lightningd/test/test-basic b/lightningd/test/test-basic index 3d224e5c9..6e32eccd6 100755 --- a/lightningd/test/test-basic +++ b/lightningd/test/test-basic @@ -55,8 +55,7 @@ RHASH=`lcli1 dev-rhash $SECRET | sed 's/.*"\([0-9a-f]*\)".*/\1/'` lcli1 dev-newhtlc $ID2 100000 $(( $(blockheight) + 10 )) $RHASH -sleep 1 -check "lcli2 getpeers | tr -s '\012\011\" ' ' ' | $FGREP 'condition : Normal operation'" +check "lcli2 getlog debug | $FGREP 'their htlc 0 locked'" lcli1 stop lcli2 stop