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

Use warning messages for connection issues #1863

Merged
merged 3 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1701,16 +1701,18 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
// a channel_reestablish when reconnecting a channel that recently got confirmed, and instead send a funding_locked
// first and then go silent. This is due to a race condition on their side, so we trigger a reconnection, hoping that
// we will eventually receive their channel_reestablish.
case Event(_: FundingLocked, _) =>
case Event(_: FundingLocked, d) =>
log.warning("received funding_locked before channel_reestablish (known lnd bug): disconnecting...")
send(Warning(d.channelId, "spec violation: you sent funding_locked before channel_reestablish"))
peer ! Peer.Disconnect(remoteNodeId)
stay
pm47 marked this conversation as resolved.
Show resolved Hide resolved

// This handler is a workaround for an issue in lnd similar to the one above: they sometimes send announcement_signatures
// before channel_reestablish, which is a minor spec violation. It doesn't halt the channel, we can simply postpone
// that message.
case Event(remoteAnnSigs: AnnouncementSignatures, _) =>
case Event(remoteAnnSigs: AnnouncementSignatures, d) =>
log.warning("received announcement_signatures before channel_reestablish (known lnd bug): delaying...")
send(Warning(d.channelId, "spec violation: you sent announcement_signatures before channel_reestablish"))
context.system.scheduler.scheduleOnce(5 seconds, self, remoteAnnSigs)
stay
t-bast marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
7 changes: 2 additions & 5 deletions eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: EclairWa
stay

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

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

object Peer {

// @formatter:off
val CHANNELID_ZERO: ByteVector32 = ByteVector32.Zeroes
val UNKNOWN_CHANNEL_MESSAGE: ByteVector = ByteVector.view("unknown channel".getBytes())
// @formatter:on

trait ChannelFactory {
def spawn(context: ActorContext, remoteNodeId: PublicKey, origin_opt: Option[ActorRef]): ActorRef
Expand Down
29 changes: 18 additions & 11 deletions eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import fr.acinq.eclair.Logs.LogCategory
import fr.acinq.eclair.crypto.Noise.KeyPair
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.io.Monitoring.{Metrics, Tags}
import fr.acinq.eclair.io.Peer.CHANNELID_ZERO
import fr.acinq.eclair.remote.EclairInternalsSerializer.RemoteTypes
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.wire.protocol
Expand Down Expand Up @@ -195,18 +194,24 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
d.transport ! Pong(ByteVector.fill(pongLength)(0.toByte))
} else {
log.warning(s"ignoring invalid ping with pongLength=${ping.pongLength}")
d.transport ! Warning(s"invalid pong length (${ping.pongLength})")
}
stay

case Event(pong@Pong(data), d: ConnectedData) =>
d.transport ! TransportHandler.ReadAck(pong)
d.expectedPong_opt match {
case Some(ExpectedPong(ping, timestamp)) if ping.pongLength == data.length =>
// we use the pong size to correlate between pings and pongs
val latency = System.currentTimeMillis - timestamp
log.debug(s"received pong with latency=$latency")
cancelTimer(PingTimeout.toString())
// we don't need to call scheduleNextPing here, the next ping was already scheduled when we received that pong
case Some(ExpectedPong(ping, timestamp)) =>
if (ping.pongLength == data.length) {
// we use the pong size to correlate between pings and pongs
val latency = System.currentTimeMillis - timestamp
log.debug(s"received pong with latency=$latency")
cancelTimer(PingTimeout.toString())
// we don't need to call scheduleNextPing here, the next ping was already scheduled when we received that pong
} else {
log.warning(s"ignoring invalid pong with length=${data.length}")
d.transport ! Warning(s"invalid pong length (${data.length})")
}
case None =>
log.debug(s"received unexpected pong with size=${data.length}")
pm47 marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down Expand Up @@ -264,6 +269,7 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
d.transport ! TransportHandler.ReadAck(msg)
if (msg.chainHash != d.chainHash) {
log.warning("received gossip_timestamp_range message for chain {}, we're on {}", msg.chainHash, d.chainHash)
d.transport ! Warning(s"invalid gossip_timestamp_range chain (${msg.chainHash})")
stay
} else {
log.info(s"setting up gossipTimestampFilter=$msg")
Expand Down Expand Up @@ -306,16 +312,16 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
case _ => "unknown"
}
log.error(s"peer sent us a routing message with invalid sig: r=$r bin=$bin")
// for now we just return an error, maybe ban the peer in the future?
// for now we just send a warning, maybe ban the peer in the future?
// TODO: this doesn't actually disconnect the peer, once we introduce peer banning we should actively disconnect
d.transport ! Error(CHANNELID_ZERO, ByteVector.view(s"bad announcement sig! bin=$bin".getBytes()))
d.transport ! Warning(s"invalid announcement sig (bin=$bin)")
d.behavior
case GossipDecision.InvalidAnnouncement(c) =>
// they seem to be sending us fake announcements?
log.error(s"couldn't find funding tx with valid scripts for shortChannelId=${c.shortChannelId}")
// for now we just return an error, maybe ban the peer in the future?
// for now we just send a warning, maybe ban the peer in the future?
// TODO: this doesn't actually disconnect the peer, once we introduce peer banning we should actively disconnect
d.transport ! Error(CHANNELID_ZERO, ByteVector.view(s"couldn't verify channel! shortChannelId=${c.shortChannelId}".getBytes()))
d.transport ! Warning(s"invalid announcement, couldn't verify channel (shortChannelId=${c.shortChannelId})")
d.behavior
case GossipDecision.ChannelClosed(_) =>
if (d.behavior.ignoreNetworkAnnouncement) {
Expand All @@ -325,6 +331,7 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
d.behavior.copy(fundingTxAlreadySpentCount = d.behavior.fundingTxAlreadySpentCount + 1)
} else {
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")
d.transport ! Warning("too many channel announcements with funding tx already spent, please check your bitcoin node")
t-bast marked this conversation as resolved.
Show resolved Hide resolved
setTimer(ResumeAnnouncements.toString, ResumeAnnouncements, IGNORE_NETWORK_ANNOUNCEMENTS_PERIOD, repeat = false)
d.behavior.copy(fundingTxAlreadySpentCount = d.behavior.fundingTxAlreadySpentCount + 1, ignoreNetworkAnnouncement = true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
val ping = Ping(Int.MaxValue, randomBytes(127))
transport.send(peerConnection, ping)
transport.expectMsg(TransportHandler.ReadAck(ping))
assert(transport.expectMsgType[Warning].channelId === Peer.CHANNELID_ZERO)
transport.expectNoMsg()
}

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

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

}
Expand Down