diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d126b5fb..b0cb22781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - JSON API: Added description to invoices and payments (#1740). - pylightning: RpcError now has `method` and `payload` fields. - Sending lightningd a SIGHUP will make it reopen its `log-file`, if any. +- Protocol: `option_data_loss_protect` now supported to protect peers + against being out-of-date. ### Changed diff --git a/channeld/channel.c b/channeld/channel.c index 7959c6ada..c0ae24644 100644 --- a/channeld/channel.c +++ b/channeld/channel.c @@ -1830,7 +1830,8 @@ static void resend_commitment(struct peer *peer, const struct changed_htlc *last peer->revocations_received); } -static void peer_reconnect(struct peer *peer) +static void peer_reconnect(struct peer *peer, + const struct secret *last_remote_per_commit_secret) { struct channel_id channel_id; /* Note: BOLT #2 uses these names, which are sender-relative! */ @@ -1839,6 +1840,10 @@ static void peer_reconnect(struct peer *peer) struct htlc_map_iter it; const struct htlc *htlc; u8 *msg; + struct pubkey my_current_per_commitment_point; + + get_per_commitment_point(peer->next_index[LOCAL]-1, + &my_current_per_commitment_point, NULL); /* BOLT #2: * @@ -1856,10 +1861,19 @@ static void peer_reconnect(struct peer *peer) * of the next `commitment_signed` it expects to receive. * - MUST set `next_remote_revocation_number` to the commitment number * of the next `revoke_and_ack` message it expects to receive. + * - if it supports `option_data_loss_protect`: + * - if `next_remote_revocation_number` equals 0: + * - MUST set `your_last_per_commitment_secret` to all zeroes + * - otherwise: + * - MUST set `your_last_per_commitment_secret` to the last + * `per_commitment_secret` it received */ - msg = towire_channel_reestablish(NULL, &peer->channel_id, - peer->next_index[LOCAL], - peer->revocations_received); + msg = towire_channel_reestablish_option_data_loss_protect + (NULL, &peer->channel_id, + peer->next_index[LOCAL], + peer->revocations_received, + last_remote_per_commit_secret, + &my_current_per_commitment_point); sync_crypto_write(&peer->cs, PEER_FD, take(msg)); peer_billboard(false, "Sent reestablish, waiting for theirs"); @@ -2337,6 +2351,7 @@ static void init_channel(struct peer *peer) u8 *funding_signed; const u8 *msg; u32 feerate_per_kw[NUM_SIDES]; + struct secret last_remote_per_commit_secret; assert(!(fcntl(MASTER_FD, F_GETFL) & O_NONBLOCK)); @@ -2387,7 +2402,8 @@ static void init_channel(struct peer *peer) &peer->final_scriptpubkey, &peer->channel_flags, &funding_signed, - &peer->announce_depth_reached)) + &peer->announce_depth_reached, + &last_remote_per_commit_secret)) master_badmsg(WIRE_CHANNEL_INIT, msg); status_trace("init %s: remote_per_commit = %s, old_remote_per_commit = %s" @@ -2447,7 +2463,7 @@ static void init_channel(struct peer *peer) /* OK, now we can process peer messages. */ if (reconnected) - peer_reconnect(peer); + peer_reconnect(peer, &last_remote_per_commit_secret); /* If we have a funding_signed message, send that immediately */ if (funding_signed) diff --git a/channeld/channel_wire.csv b/channeld/channel_wire.csv index 245c48ea8..866f5c264 100644 --- a/channeld/channel_wire.csv +++ b/channeld/channel_wire.csv @@ -58,6 +58,7 @@ channel_init,,flags,u8 channel_init,,init_peer_pkt_len,u16 channel_init,,init_peer_pkt,init_peer_pkt_len*u8 channel_init,,reached_announce_depth,bool +channel_init,,last_remote_secret,struct secret # master->channeld funding hit new depth >= lock depth channel_funding_locked,1002 diff --git a/common/features.c b/common/features.c index 69c6df684..392a504cf 100644 --- a/common/features.c +++ b/common/features.c @@ -4,6 +4,7 @@ #include static const u32 local_features[] = { + LOCAL_DATA_LOSS_PROTECT, LOCAL_INITIAL_ROUTING_SYNC, LOCAL_GOSSIP_QUERIES }; diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index aebaf0657..87917f10c 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -188,6 +188,7 @@ void peer_start_channeld(struct channel *channel, struct lightningd *ld = channel->peer->ld; const struct config *cfg = &ld->config; bool reached_announce_depth; + struct secret last_remote_per_commit_secret; hsmfd = hsm_get_client_fd(ld, &channel->peer->id, channel->dbid, @@ -235,6 +236,27 @@ void peer_start_channeld(struct channel *channel, num_revocations = revocations_received(&channel->their_shachain.chain); + /* BOLT #2: + * + * - if it supports `option_data_loss_protect`: + * - if `next_remote_revocation_number` equals 0: + * - MUST set `your_last_per_commitment_secret` to all zeroes + * - otherwise: + * - MUST set `your_last_per_commitment_secret` to the last + * `per_commitment_secret` it received + */ + if (num_revocations == 0) + memset(&last_remote_per_commit_secret, 0, + sizeof(last_remote_per_commit_secret)); + else if (!shachain_get_secret(&channel->their_shachain.chain, + num_revocations-1, + &last_remote_per_commit_secret)) { + channel_fail_permanent(channel, + "Could not get revocation secret %"PRIu64, + num_revocations-1); + return; + } + /* Warn once. */ if (ld->config.ignore_fee_limits) log_debug(channel->log, "Ignoring fee limits!"); @@ -284,7 +306,8 @@ void peer_start_channeld(struct channel *channel, channel->final_key_idx), channel->channel_flags, funding_signed, - reached_announce_depth); + reached_announce_depth, + &last_remote_per_commit_secret); /* We don't expect a response: we are triggered by funding_depth_cb. */ subd_send_msg(channel->owner, take(initmsg)); diff --git a/tests/test_connection.py b/tests/test_connection.py index 787a87687..3d9ed4509 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1006,7 +1006,7 @@ def test_peerinfo(node_factory, bitcoind): # Gossiping but no node announcement yet assert l1.rpc.getpeer(l2.info['id'])['connected'] assert len(l1.rpc.getpeer(l2.info['id'])['channels']) == 0 - assert l1.rpc.getpeer(l2.info['id'])['local_features'] == '88' + assert l1.rpc.getpeer(l2.info['id'])['local_features'] == '8a' assert l1.rpc.getpeer(l2.info['id'])['global_features'] == '' # Fund a channel to force a node announcement