Skip to content

Commit d9a03a5

Browse files
authored
Use warning messages for connection issues (#1863)
lightning/bolts#834 recommends sending warning messages instead of connection-level errors in some cases, which avoids unnecessary channel closure.
1 parent 291c128 commit d9a03a5

File tree

4 files changed

+30
-23
lines changed

4 files changed

+30
-23
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala

+6-5
Original file line numberDiff line numberDiff line change
@@ -1701,18 +1701,19 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
17011701
// a channel_reestablish when reconnecting a channel that recently got confirmed, and instead send a funding_locked
17021702
// first and then go silent. This is due to a race condition on their side, so we trigger a reconnection, hoping that
17031703
// we will eventually receive their channel_reestablish.
1704-
case Event(_: FundingLocked, _) =>
1704+
case Event(_: FundingLocked, d) =>
17051705
log.warning("received funding_locked before channel_reestablish (known lnd bug): disconnecting...")
1706-
peer ! Peer.Disconnect(remoteNodeId)
1707-
stay
1706+
// NB: we use a small delay to ensure we've sent our warning before disconnecting.
1707+
context.system.scheduler.scheduleOnce(2 second, peer, Peer.Disconnect(remoteNodeId))
1708+
stay sending Warning(d.channelId, "spec violation: you sent funding_locked before channel_reestablish")
17081709

17091710
// This handler is a workaround for an issue in lnd similar to the one above: they sometimes send announcement_signatures
17101711
// before channel_reestablish, which is a minor spec violation. It doesn't halt the channel, we can simply postpone
17111712
// that message.
1712-
case Event(remoteAnnSigs: AnnouncementSignatures, _) =>
1713+
case Event(remoteAnnSigs: AnnouncementSignatures, d) =>
17131714
log.warning("received announcement_signatures before channel_reestablish (known lnd bug): delaying...")
17141715
context.system.scheduler.scheduleOnce(5 seconds, self, remoteAnnSigs)
1715-
stay
1716+
stay sending Warning(d.channelId, "spec violation: you sent announcement_signatures before channel_reestablish")
17161717

17171718
case Event(ProcessCurrentBlockCount(c), d: HasCommitments) => handleNewBlock(c, d)
17181719

eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala

+2-5
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: EclairWa
106106
stay
107107

108108
case Event(warning: Warning, _: ConnectedData) =>
109-
log.warning("peer sent warning: {}", warning.channelId, warning.toAscii)
109+
log.warning("peer sent warning: {}", warning.toAscii)
110110
// NB: we don't forward warnings to the channel actors, they shouldn't take any automatic action.
111111
// It's up to the node operator to decide what to do to address the warning.
112112
stay
@@ -318,7 +318,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: EclairWa
318318
}
319319

320320
def replyUnknownChannel(peerConnection: ActorRef, unknownChannelId: ByteVector32): Unit = {
321-
val msg = Error(unknownChannelId, UNKNOWN_CHANNEL_MESSAGE)
321+
val msg = Warning(unknownChannelId, "unknown channel")
322322
logMessage(msg, "OUT")
323323
peerConnection ! msg
324324
}
@@ -361,10 +361,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: EclairWa
361361

362362
object Peer {
363363

364-
// @formatter:off
365364
val CHANNELID_ZERO: ByteVector32 = ByteVector32.Zeroes
366-
val UNKNOWN_CHANNEL_MESSAGE: ByteVector = ByteVector.view("unknown channel".getBytes())
367-
// @formatter:on
368365

369366
trait ChannelFactory {
370367
def spawn(context: ActorContext, remoteNodeId: PublicKey, origin_opt: Option[ActorRef]): ActorRef

eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala

+10-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import fr.acinq.eclair.Logs.LogCategory
2424
import fr.acinq.eclair.crypto.Noise.KeyPair
2525
import fr.acinq.eclair.crypto.TransportHandler
2626
import fr.acinq.eclair.io.Monitoring.{Metrics, Tags}
27-
import fr.acinq.eclair.io.Peer.CHANNELID_ZERO
2827
import fr.acinq.eclair.remote.EclairInternalsSerializer.RemoteTypes
2928
import fr.acinq.eclair.router.Router._
3029
import fr.acinq.eclair.wire.protocol
@@ -195,6 +194,7 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
195194
d.transport ! Pong(ByteVector.fill(pongLength)(0.toByte))
196195
} else {
197196
log.warning(s"ignoring invalid ping with pongLength=${ping.pongLength}")
197+
d.transport ! Warning(s"invalid pong length (${ping.pongLength})")
198198
}
199199
stay
200200

@@ -207,8 +207,9 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
207207
log.debug(s"received pong with latency=$latency")
208208
cancelTimer(PingTimeout.toString())
209209
// we don't need to call scheduleNextPing here, the next ping was already scheduled when we received that pong
210-
case None =>
211-
log.debug(s"received unexpected pong with size=${data.length}")
210+
case _ =>
211+
log.debug(s"received unexpected pong with length=${data.length}")
212+
d.transport ! Warning(s"invalid pong with length=${data.length}")
212213
}
213214
stay using d.copy(expectedPong_opt = None)
214215

@@ -264,6 +265,7 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
264265
d.transport ! TransportHandler.ReadAck(msg)
265266
if (msg.chainHash != d.chainHash) {
266267
log.warning("received gossip_timestamp_range message for chain {}, we're on {}", msg.chainHash, d.chainHash)
268+
d.transport ! Warning(s"invalid gossip_timestamp_range chain (${msg.chainHash})")
267269
stay
268270
} else {
269271
log.info(s"setting up gossipTimestampFilter=$msg")
@@ -306,16 +308,16 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
306308
case _ => "unknown"
307309
}
308310
log.error(s"peer sent us a routing message with invalid sig: r=$r bin=$bin")
309-
// for now we just return an error, maybe ban the peer in the future?
311+
// for now we just send a warning, maybe ban the peer in the future?
310312
// TODO: this doesn't actually disconnect the peer, once we introduce peer banning we should actively disconnect
311-
d.transport ! Error(CHANNELID_ZERO, ByteVector.view(s"bad announcement sig! bin=$bin".getBytes()))
313+
d.transport ! Warning(s"invalid announcement sig (bin=$bin)")
312314
d.behavior
313315
case GossipDecision.InvalidAnnouncement(c) =>
314316
// they seem to be sending us fake announcements?
315317
log.error(s"couldn't find funding tx with valid scripts for shortChannelId=${c.shortChannelId}")
316-
// for now we just return an error, maybe ban the peer in the future?
318+
// for now we just send a warning, maybe ban the peer in the future?
317319
// TODO: this doesn't actually disconnect the peer, once we introduce peer banning we should actively disconnect
318-
d.transport ! Error(CHANNELID_ZERO, ByteVector.view(s"couldn't verify channel! shortChannelId=${c.shortChannelId}".getBytes()))
320+
d.transport ! Warning(s"invalid announcement, couldn't verify channel (shortChannelId=${c.shortChannelId})")
319321
d.behavior
320322
case GossipDecision.ChannelClosed(_) =>
321323
if (d.behavior.ignoreNetworkAnnouncement) {
@@ -325,6 +327,7 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
325327
d.behavior.copy(fundingTxAlreadySpentCount = d.behavior.fundingTxAlreadySpentCount + 1)
326328
} else {
327329
log.warning(s"peer sent us too many channel announcements with funding tx already spent (count=${d.behavior.fundingTxAlreadySpentCount + 1}), ignoring network announcements for $IGNORE_NETWORK_ANNOUNCEMENTS_PERIOD")
330+
d.transport ! Warning("too many channel announcements with funding tx already spent, please check your bitcoin node")
328331
setTimer(ResumeAnnouncements.toString, ResumeAnnouncements, IGNORE_NETWORK_ANNOUNCEMENTS_PERIOD, repeat = false)
329332
d.behavior.copy(fundingTxAlreadySpentCount = d.behavior.fundingTxAlreadySpentCount + 1, ignoreNetworkAnnouncement = true)
330333
}

eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala

+12-6
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
231231
val ping = Ping(Int.MaxValue, randomBytes(127))
232232
transport.send(peerConnection, ping)
233233
transport.expectMsg(TransportHandler.ReadAck(ping))
234+
assert(transport.expectMsgType[Warning].channelId === Peer.CHANNELID_ZERO)
234235
transport.expectNoMsg()
235236
}
236237

@@ -332,8 +333,13 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
332333
router.send(peerConnection, GossipDecision.ChannelClosed(c))
333334
}
334335
// peer will temporary ignore announcements coming from bob
336+
var warningSent = false
335337
for (ann <- channels ++ updates) {
336338
transport.send(peerConnection, ann)
339+
if (!warningSent) {
340+
transport.expectMsgType[Warning]
341+
warningSent = true
342+
}
337343
transport.expectMsg(TransportHandler.ReadAck(ann))
338344
}
339345
router.expectNoMsg(1 second)
@@ -354,16 +360,16 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
354360
// now let's assume that the router isn't happy with those channels because the announcement is invalid
355361
router.send(peerConnection, GossipDecision.InvalidAnnouncement(channels(0)))
356362
// peer will return a connection-wide error, including the hex-encoded representation of the bad message
357-
val error1 = transport.expectMsgType[Error]
358-
assert(error1.channelId === Peer.CHANNELID_ZERO)
359-
assert(new String(error1.data.toArray).startsWith("couldn't verify channel! shortChannelId="))
363+
val warn1 = transport.expectMsgType[Warning]
364+
assert(warn1.channelId === Peer.CHANNELID_ZERO)
365+
assert(new String(warn1.data.toArray).startsWith("invalid announcement, couldn't verify channel"))
360366

361367
// let's assume that one of the sigs were invalid
362368
router.send(peerConnection, GossipDecision.InvalidSignature(channels(0)))
363369
// peer will return a connection-wide error, including the hex-encoded representation of the bad message
364-
val error2 = transport.expectMsgType[Error]
365-
assert(error2.channelId === Peer.CHANNELID_ZERO)
366-
assert(new String(error2.data.toArray).startsWith("bad announcement sig! bin=0100"))
370+
val warn2 = transport.expectMsgType[Warning]
371+
assert(warn2.channelId === Peer.CHANNELID_ZERO)
372+
assert(new String(warn2.data.toArray).startsWith("invalid announcement sig"))
367373
}
368374

369375
}

0 commit comments

Comments
 (0)