Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always re-send ChannelUpdate to private channels #1317

Merged
merged 10 commits into from
Feb 12, 2020
Merged

Always re-send ChannelUpdate to private channels #1317

merged 10 commits into from
Feb 12, 2020

Conversation

akumaigorodski
Copy link
Contributor

Current approach seems to be too brittle as ChannelUpdate is only sent once but may not reach a remote peer due to disconnect (this did happen to me).

This is what other implementations (LND at least) do already, I suppose that traffic overhead of this would be minimal. If it is a concern though then perhaps a following smarter strategy could work: send ChannelUpdate to private channel on each reconnect until there was an incoming payment attempt (respected commit number updated in commitments?).

Copy link
Member

@t-bast t-bast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the overall goal: it's cheap to send channel_updates for unannounced channels more aggressively (as they won't be forwarded further in the network).

But your current proposal will send it way too often (every time the commitment tx is updated).
I would instead only add a re-broadcast when going OFFLINE -> NORMAL.

@@ -2409,9 +2409,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
sender.send(alice, INPUT_DISCONNECTED)
assert(relayerA.expectMsgType[Status.Failure].cause.asInstanceOf[AddHtlcFailed].paymentHash === htlc1.paymentHash)
assert(relayerA.expectMsgType[Status.Failure].cause.asInstanceOf[AddHtlcFailed].paymentHash === htlc2.paymentHash)
val update2a = alice2bob.expectMsgType[ChannelUpdate]
assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate === update2a)
assert(!Announcements.isEnabled(update2a.channelFlags))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this was catching a superfluous ChannelUpdate on entering into OFFLINE state, it's not sent now.

(state, nextState, stateData, nextStateData) match {
// if channel is private, we send the channel_update directly to remote on each reconnect
// they need it "to learn the other end's forwarding parameters" (BOLT 7)
case (NORMAL, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if !d1.commitments.announceChannel && (!d1.buried && d2.buried || d1.channelUpdate != d2.channelUpdate) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's incorrect. You still always want to wait for d2.buried before sending a channel_update (which isn't the case here if d1.channelUpdate != d2.channelUpdate.

I'm a bit weary of matching now on states + data, there may be a case we're losing compared to the previous code.

Here is my take:

// if channel is private, we send the channel_update directly to remote
// they need it "to learn the other end's forwarding parameters" (BOLT 7)
(state, nextState, stateData, nextStateData) match {
  case (_, _, d1: DATA_NORMAL, d2: DATA_NORMAL) if !d1.commitments.announceChannel && !d1.buried && d2.buried =>
    // for a private channel, when the tx was just buried we need to send the channel_update to our peer (even if it didn't change)
    forwarder ! d2.channelUpdate
  case (OFFLINE, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if !d1.commitments.announceChannel && d2.buried =>
    // otherwise if we're coming back online, we rebroadcast the latest channel_update
    // this makes sure that if the channel_update was missed, we have a chance to re-send it
    forwarder ! d2.channelUpdate
  case (_, _, d1: DATA_NORMAL, d2: DATA_NORMAL) if !d1.commitments.announceChannel && d1.channelUpdate != d2.channelUpdate && d2.buried =>
    // otherwise, we only send it when it is different, and tx is already buried
    forwarder ! d2.channelUpdate
  case _ => ()
}

I believe it's safer as it keeps the existing behavior and only adds a new case when coming back online. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's incorrect.

It's less code and not sure it's strictly incorrect: NORMAL_DATA is present so at least default 3 confirmations are there, occasional premature update at this point won't add too much risk.

But I'm also totally fine with using your code.

Copy link
Member

@t-bast t-bast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@t-bast t-bast merged commit 8afc00d into ACINQ:master Feb 12, 2020
pm47 added a commit that referenced this pull request Mar 6, 2020
Previous implementation in #1317 wasn't working because in a bug in the
transition. Added a test and fixed it.
pm47 added a commit that referenced this pull request Mar 11, 2020
Previous implementation in #1317 wasn't working because in a bug in the
transition. Added a test and fixed it.

Co-authored-by: Bastien Teinturier <31281497+t-bast@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants