Skip to content

Commit 69014d2

Browse files
committed
Don't initiate on-the-fly-funding if remote feature not active
We check the remote features before initiating an on-the-fly funding attempt: it doesn't make sense to initiate it if our peer has not activated the feature.
1 parent 021b3c8 commit 69014d2

File tree

12 files changed

+113
-79
lines changed

12 files changed

+113
-79
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ private class MessageRelay(nodeParams: NodeParams,
154154

155155
private def waitForPreviousPeerForPolicyCheck(msg: OnionMessage, nextNodeId: PublicKey): Behavior[Command] = {
156156
Behaviors.receiveMessagePartial {
157-
case WrappedPeerInfo(PeerInfo(_, _, _, _, channels)) if channels.nonEmpty =>
157+
case WrappedPeerInfo(info: PeerInfo) if info.channels.nonEmpty =>
158158
switchboard ! GetPeerInfo(context.messageAdapter(WrappedPeerInfo), nextNodeId)
159159
waitForNextPeerForPolicyCheck(msg, nextNodeId)
160160
case _ =>
@@ -167,8 +167,8 @@ private class MessageRelay(nodeParams: NodeParams,
167167

168168
private def waitForNextPeerForPolicyCheck(msg: OnionMessage, nextNodeId: PublicKey): Behavior[Command] = {
169169
Behaviors.receiveMessagePartial {
170-
case WrappedPeerInfo(PeerInfo(peer, _, _, _, channels)) if channels.nonEmpty =>
171-
peer ! Peer.RelayOnionMessage(messageId, msg, replyTo_opt)
170+
case WrappedPeerInfo(info: PeerInfo) if info.channels.nonEmpty =>
171+
info.peer ! Peer.RelayOnionMessage(messageId, msg, replyTo_opt)
172172
Behaviors.stopped
173173
case _ =>
174174
Metrics.OnionMessagesNotRelayed.withTag(Tags.Reason, Tags.Reasons.NoChannelWithNextPeer).increment()

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,10 +475,11 @@ class Peer(val nodeParams: NodeParams,
475475

476476
case Event(r: GetPeerInfo, d) =>
477477
val replyTo = r.replyTo.getOrElse(sender().toTyped)
478-
replyTo ! PeerInfo(self, remoteNodeId, stateName, d match {
479-
case c: ConnectedData => Some(c.address)
480-
case _ => None
481-
}, d.channels.values.toSet)
478+
val peerInfo = d match {
479+
case c: ConnectedData => PeerInfo(self, remoteNodeId, stateName, Some(c.remoteFeatures), Some(c.address), c.channels.values.toSet)
480+
case _ => PeerInfo(self, remoteNodeId, stateName, None, None, d.channels.values.toSet)
481+
}
482+
replyTo ! peerInfo
482483
stay()
483484

484485
case Event(r: GetPeerChannels, d) =>
@@ -867,7 +868,7 @@ object Peer {
867868

868869
case class GetPeerInfo(replyTo: Option[typed.ActorRef[PeerInfoResponse]])
869870
sealed trait PeerInfoResponse { def nodeId: PublicKey }
870-
case class PeerInfo(peer: ActorRef, nodeId: PublicKey, state: State, address: Option[NodeAddress], channels: Set[ActorRef]) extends PeerInfoResponse
871+
case class PeerInfo(peer: ActorRef, nodeId: PublicKey, state: State, features: Option[Features[InitFeature]], address: Option[NodeAddress], channels: Set[ActorRef]) extends PeerInfoResponse
871872
case class PeerNotFound(nodeId: PublicKey) extends PeerInfoResponse with DisconnectResponse { override def toString: String = s"peer $nodeId not found" }
872873

873874
/** Return the peer's current channels: note that the data may change concurrently, never assume it is fully up-to-date. */

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler}
2323
import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy}
2424
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2525
import fr.acinq.eclair.blockchain.CurrentBlockHeight
26-
import fr.acinq.eclair.{BlockHeight, Logs, channel}
26+
import fr.acinq.eclair.{BlockHeight, Features, InitFeature, Logs, channel}
2727

2828
import scala.concurrent.duration.{DurationInt, FiniteDuration}
2929

@@ -107,15 +107,15 @@ object PeerReadyNotifier {
107107
private case object PeerNotConnected extends Command
108108
private case object PeerConnected extends Command
109109
private case object PeerDisconnected extends Command
110-
private case class WrappedPeerInfo(peer: ActorRef[Peer.GetPeerChannels], channelCount: Int) extends Command
110+
private case class WrappedPeerInfo(peer: ActorRef[Peer.GetPeerChannels], remoteFeatures: Features[InitFeature], channelCount: Int) extends Command
111111
private case class NewBlockNotTimedOut(currentBlockHeight: BlockHeight) extends Command
112112
private case object CheckChannelsReady extends Command
113113
private case class WrappedPeerChannels(wrapped: Peer.PeerChannels) extends Command
114114
private case object Timeout extends Command
115115
private case object ToBeIgnored extends Command
116116

117117
sealed trait Result { def remoteNodeId: PublicKey }
118-
case class PeerReady(remoteNodeId: PublicKey, peer: akka.actor.ActorRef, channelInfos: Seq[Peer.ChannelInfo]) extends Result { val channelsCount: Int = channelInfos.size }
118+
case class PeerReady(remoteNodeId: PublicKey, peer: akka.actor.ActorRef, remoteFeatures: Features[InitFeature], channelInfos: Seq[Peer.ChannelInfo]) extends Result { val channelsCount: Int = channelInfos.size }
119119
case class PeerUnavailable(remoteNodeId: PublicKey) extends Result
120120

121121
private case object ChannelsReadyTimerKey
@@ -243,7 +243,7 @@ private class PeerReadyNotifier(replyTo: ActorRef[PeerReadyNotifier.Result],
243243
// In that case we still want to wait for a connection, because we may want to open a channel to them.
244244
case _: Peer.PeerNotFound => PeerNotConnected
245245
case info: Peer.PeerInfo if info.state != Peer.CONNECTED => PeerNotConnected
246-
case info: Peer.PeerInfo => WrappedPeerInfo(info.peer.toTyped, info.channels.size)
246+
case info: Peer.PeerInfo => WrappedPeerInfo(info.peer.toTyped, info.features.getOrElse(Features.empty), info.channels.size)
247247
}
248248
// We check whether the peer is already connected.
249249
switchboard ! Switchboard.GetPeerInfo(peerInfoAdapter, remoteNodeId)
@@ -256,14 +256,14 @@ private class PeerReadyNotifier(replyTo: ActorRef[PeerReadyNotifier.Result],
256256
Behaviors.same
257257
case PeerDisconnected =>
258258
Behaviors.same
259-
case WrappedPeerInfo(peer, channelCount) =>
259+
case WrappedPeerInfo(peer, remoteFeatures, channelCount) =>
260260
if (channelCount == 0) {
261261
log.info("peer is ready with no channels")
262-
replyTo ! PeerReady(remoteNodeId, peer.toClassic, Seq.empty)
262+
replyTo ! PeerReady(remoteNodeId, peer.toClassic, remoteFeatures, Seq.empty)
263263
Behaviors.stopped
264264
} else {
265265
log.debug("peer is connected with {} channels", channelCount)
266-
waitForChannelsReady(peer, switchboard)
266+
waitForChannelsReady(peer, switchboard, remoteFeatures)
267267
}
268268
case NewBlockNotTimedOut(currentBlockHeight) =>
269269
log.debug("waiting for peer to connect at block {}", currentBlockHeight)
@@ -277,7 +277,7 @@ private class PeerReadyNotifier(replyTo: ActorRef[PeerReadyNotifier.Result],
277277
}
278278
}
279279

280-
private def waitForChannelsReady(peer: ActorRef[Peer.GetPeerChannels], switchboard: ActorRef[Switchboard.GetPeerInfo]): Behavior[Command] = {
280+
private def waitForChannelsReady(peer: ActorRef[Peer.GetPeerChannels], switchboard: ActorRef[Switchboard.GetPeerInfo], remoteFeatures: Features[InitFeature]): Behavior[Command] = {
281281
timers.startTimerWithFixedDelay(ChannelsReadyTimerKey, CheckChannelsReady, initialDelay = 50 millis, delay = 1 second)
282282
Behaviors.receiveMessagePartial {
283283
case CheckChannelsReady =>
@@ -286,7 +286,7 @@ private class PeerReadyNotifier(replyTo: ActorRef[PeerReadyNotifier.Result],
286286
Behaviors.same
287287
case WrappedPeerChannels(peerChannels) =>
288288
if (peerChannels.channels.map(_.state).forall(isChannelReady)) {
289-
replyTo ! PeerReady(remoteNodeId, peer.toClassic, peerChannels.channels)
289+
replyTo ! PeerReady(remoteNodeId, peer.toClassic, remoteFeatures, peerChannels.channels)
290290
Behaviors.stopped
291291
} else {
292292
log.debug("peer has {} channels that are not ready", peerChannels.channels.count(s => !isChannelReady(s.state)))

eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket}
3434
import fr.acinq.eclair.wire.protocol.FailureMessageCodecs.createBadOnionFailure
3535
import fr.acinq.eclair.wire.protocol.PaymentOnion.IntermediatePayload
3636
import fr.acinq.eclair.wire.protocol._
37-
import fr.acinq.eclair.{Features, Logs, NodeParams, ShortChannelId, TimestampMilli, TimestampSecond, channel, nodeFee}
37+
import fr.acinq.eclair.{Features, InitFeature, Logs, NodeParams, TimestampMilli, TimestampSecond, channel, nodeFee}
3838

3939
import java.util.UUID
4040
import java.util.concurrent.TimeUnit
@@ -153,7 +153,7 @@ class ChannelRelay private(nodeParams: NodeParams,
153153
case Some(walletNodeId) if nodeParams.peerWakeUpConfig.enabled => wakeUp(walletNodeId)
154154
case _ =>
155155
context.self ! DoRelay
156-
relay(Seq.empty)
156+
relay(None, Seq.empty)
157157
}
158158
}
159159

@@ -166,21 +166,21 @@ class ChannelRelay private(nodeParams: NodeParams,
166166
Metrics.recordPaymentRelayFailed(Tags.FailureType.WakeUp, Tags.RelayType.Channel)
167167
context.log.info("rejecting htlc: failed to wake-up remote peer")
168168
safeSendAndStop(r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true))
169-
case WrappedPeerReadyResult(_: PeerReadyNotifier.PeerReady) =>
169+
case WrappedPeerReadyResult(r: PeerReadyNotifier.PeerReady) =>
170170
context.self ! DoRelay
171-
relay(Seq.empty)
171+
relay(Some(r.remoteFeatures), Seq.empty)
172172
}
173173
}
174174

175-
def relay(previousFailures: Seq[PreviouslyTried]): Behavior[Command] = {
175+
def relay(remoteFeatures_opt: Option[Features[InitFeature]], previousFailures: Seq[PreviouslyTried]): Behavior[Command] = {
176176
Behaviors.receiveMessagePartial {
177177
case DoRelay =>
178178
if (previousFailures.isEmpty) {
179179
val nextNodeId_opt = channels.headOption.map(_._2.nextNodeId)
180180
context.log.info("relaying htlc #{} from channelId={} to requestedShortChannelId={} nextNode={}", r.add.id, r.add.channelId, requestedShortChannelId_opt, nextNodeId_opt.getOrElse(""))
181181
}
182182
context.log.debug("attempting relay previousAttempts={}", previousFailures.size)
183-
handleRelay(previousFailures) match {
183+
handleRelay(remoteFeatures_opt, previousFailures) match {
184184
case RelayFailure(cmdFail) =>
185185
Metrics.recordPaymentRelayFailed(Tags.FailureType(cmdFail), Tags.RelayType.Channel)
186186
context.log.info("rejecting htlc reason={}", cmdFail.reason)
@@ -192,12 +192,12 @@ class ChannelRelay private(nodeParams: NodeParams,
192192
case RelaySuccess(selectedChannelId, cmdAdd) =>
193193
context.log.info("forwarding htlc #{} from channelId={} to channelId={}", r.add.id, r.add.channelId, selectedChannelId)
194194
register ! Register.Forward(forwardFailureAdapter, selectedChannelId, cmdAdd)
195-
waitForAddResponse(selectedChannelId, previousFailures)
195+
waitForAddResponse(selectedChannelId, remoteFeatures_opt, previousFailures)
196196
}
197197
}
198198
}
199199

200-
private def waitForAddResponse(selectedChannelId: ByteVector32, previousFailures: Seq[PreviouslyTried]): Behavior[Command] =
200+
private def waitForAddResponse(selectedChannelId: ByteVector32, remoteFeatures_opt: Option[Features[InitFeature]], previousFailures: Seq[PreviouslyTried]): Behavior[Command] =
201201
Behaviors.receiveMessagePartial {
202202
case WrappedForwardFailure(Register.ForwardFailure(Register.Forward(_, channelId, _))) =>
203203
context.log.warn(s"couldn't resolve downstream channel $channelId, failing htlc #${upstream.add.id}")
@@ -208,7 +208,7 @@ class ChannelRelay private(nodeParams: NodeParams,
208208
case WrappedAddResponse(addFailed: RES_ADD_FAILED[_]) =>
209209
context.log.info("attempt failed with reason={}", addFailed.t.getClass.getSimpleName)
210210
context.self ! DoRelay
211-
relay(previousFailures :+ PreviouslyTried(selectedChannelId, addFailed))
211+
relay(remoteFeatures_opt, previousFailures :+ PreviouslyTried(selectedChannelId, addFailed))
212212

213213
case WrappedAddResponse(_: RES_SUCCESS[_]) =>
214214
context.log.debug("sent htlc to the downstream channel")
@@ -280,7 +280,7 @@ class ChannelRelay private(nodeParams: NodeParams,
280280
* - a CMD_FAIL_HTLC to be sent back upstream
281281
* - a CMD_ADD_HTLC to propagate downstream
282282
*/
283-
private def handleRelay(previousFailures: Seq[PreviouslyTried]): RelayResult = {
283+
private def handleRelay(remoteFeatures_opt: Option[Features[InitFeature]], previousFailures: Seq[PreviouslyTried]): RelayResult = {
284284
val alreadyTried = previousFailures.map(_.channelId)
285285
selectPreferredChannel(alreadyTried) match {
286286
case Some(outgoingChannel) => relayOrFail(outgoingChannel)
@@ -298,7 +298,7 @@ class ChannelRelay private(nodeParams: NodeParams,
298298
CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true)
299299
}
300300
walletNodeId_opt match {
301-
case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(previousFailures) => RelayNeedsFunding(walletNodeId, cmdFail)
301+
case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(remoteFeatures_opt, previousFailures) => RelayNeedsFunding(walletNodeId, cmdFail)
302302
case _ => RelayFailure(cmdFail)
303303
}
304304
}
@@ -400,8 +400,8 @@ class ChannelRelay private(nodeParams: NodeParams,
400400
}
401401

402402
/** If we fail to relay a payment, we may want to attempt on-the-fly funding. */
403-
private def shouldAttemptOnTheFlyFunding(previousFailures: Seq[PreviouslyTried]): Boolean = {
404-
val featureOk = nodeParams.features.hasFeature(Features.OnTheFlyFunding)
403+
private def shouldAttemptOnTheFlyFunding(remoteFeatures_opt: Option[Features[InitFeature]], previousFailures: Seq[PreviouslyTried]): Boolean = {
404+
val featureOk = Features.canUseFeature(nodeParams.features.initFeatures(), remoteFeatures_opt.getOrElse(Features.empty), Features.OnTheFlyFunding)
405405
// If we have a channel with the next node, we only want to perform on-the-fly funding for liquidity issues.
406406
val liquidityIssue = previousFailures.forall {
407407
case PreviouslyTried(_, RES_ADD_FAILED(_, _: InsufficientFunds, _)) => true

eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import fr.acinq.eclair.router.Router.{ChannelHop, HopRelayParams, Route, RoutePa
4242
import fr.acinq.eclair.router.{BalanceTooLow, RouteNotFound}
4343
import fr.acinq.eclair.wire.protocol.PaymentOnion.IntermediatePayload
4444
import fr.acinq.eclair.wire.protocol._
45-
import fr.acinq.eclair.{Alias, CltvExpiry, CltvExpiryDelta, EncodedNodeId, Features, Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, TimestampMilli, UInt64, nodeFee, randomBytes32}
45+
import fr.acinq.eclair.{Alias, CltvExpiry, CltvExpiryDelta, EncodedNodeId, Features, InitFeature, Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, TimestampMilli, UInt64, nodeFee, randomBytes32}
4646

4747
import java.util.UUID
4848
import java.util.concurrent.TimeUnit
@@ -164,8 +164,8 @@ object NodeRelay {
164164
}
165165

166166
/** If we fail to relay a payment, we may want to attempt on-the-fly funding if it makes sense. */
167-
private def shouldAttemptOnTheFlyFunding(nodeParams: NodeParams, failures: Seq[PaymentFailure]): Boolean = {
168-
val featureOk = nodeParams.features.hasFeature(Features.OnTheFlyFunding)
167+
private def shouldAttemptOnTheFlyFunding(nodeParams: NodeParams, recipientFeatures_opt: Option[Features[InitFeature]], failures: Seq[PaymentFailure]): Boolean = {
168+
val featureOk = Features.canUseFeature(nodeParams.features.initFeatures(), recipientFeatures_opt.getOrElse(Features.empty), Features.OnTheFlyFunding)
169169
val balanceTooLow = failures.collectFirst { case f@LocalFailure(_, _, BalanceTooLow) => f }.nonEmpty
170170
val routeNotFound = failures.collectFirst { case f@LocalFailure(_, _, RouteNotFound) => f }.nonEmpty
171171
featureOk && (balanceTooLow || routeNotFound)
@@ -298,7 +298,7 @@ class NodeRelay private(nodeParams: NodeParams,
298298
private def ensureRecipientReady(upstream: Upstream.Hot.Trampoline, recipient: Recipient, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket]): Behavior[Command] = {
299299
nextWalletNodeId(nodeParams, recipient) match {
300300
case Some(walletNodeId) if nodeParams.peerWakeUpConfig.enabled => waitForPeerReady(upstream, walletNodeId, recipient, nextPayload, nextPacket_opt)
301-
case _ => relay(upstream, recipient, nextPayload, nextPacket_opt)
301+
case _ => relay(upstream, recipient, None, nextPayload, nextPacket_opt)
302302
}
303303
}
304304

@@ -316,14 +316,14 @@ class NodeRelay private(nodeParams: NodeParams,
316316
context.log.warn("rejecting payment: failed to wake-up remote peer")
317317
rejectPayment(upstream, Some(UnknownNextPeer()))
318318
stopping()
319-
case WrappedPeerReadyResult(_: PeerReadyNotifier.PeerReady) =>
320-
relay(upstream, recipient, nextPayload, nextPacket_opt)
319+
case WrappedPeerReadyResult(r: PeerReadyNotifier.PeerReady) =>
320+
relay(upstream, recipient, Some(r.remoteFeatures), nextPayload, nextPacket_opt)
321321
}
322322
}
323323
}
324324

325325
/** Relay the payment to the next identified node: this is similar to sending an outgoing payment. */
326-
private def relay(upstream: Upstream.Hot.Trampoline, recipient: Recipient, payloadOut: IntermediatePayload.NodeRelay, packetOut_opt: Option[OnionRoutingPacket]): Behavior[Command] = {
326+
private def relay(upstream: Upstream.Hot.Trampoline, recipient: Recipient, recipientFeatures_opt: Option[Features[InitFeature]], payloadOut: IntermediatePayload.NodeRelay, packetOut_opt: Option[OnionRoutingPacket]): Behavior[Command] = {
327327
context.log.debug("relaying trampoline payment (amountIn={} expiryIn={} amountOut={} expiryOut={})", upstream.amountIn, upstream.expiryIn, payloadOut.amountToForward, payloadOut.outgoingCltv)
328328
val confidence = (upstream.received.map(_.add.endorsement).min + 0.5) / 8
329329
val paymentCfg = SendPaymentConfig(relayId, relayId, None, paymentHash, recipient.nodeId, upstream, None, None, storeInDb = false, publishEvent = false, recordPathFindingMetrics = true, confidence)
@@ -342,7 +342,7 @@ class NodeRelay private(nodeParams: NodeParams,
342342
}
343343
val payFSM = outgoingPaymentFactory.spawnOutgoingPayFSM(context, paymentCfg, useMultiPart)
344344
payFSM ! payment
345-
sending(upstream, recipient, payloadOut, TimestampMilli.now(), fulfilledUpstream = false)
345+
sending(upstream, recipient, recipientFeatures_opt, payloadOut, TimestampMilli.now(), fulfilledUpstream = false)
346346
}
347347

348348
/**
@@ -354,6 +354,7 @@ class NodeRelay private(nodeParams: NodeParams,
354354
*/
355355
private def sending(upstream: Upstream.Hot.Trampoline,
356356
recipient: Recipient,
357+
recipientFeatures_opt: Option[Features[InitFeature]],
357358
nextPayload: IntermediatePayload.NodeRelay,
358359
startedAt: TimestampMilli,
359360
fulfilledUpstream: Boolean): Behavior[Command] =
@@ -365,7 +366,7 @@ class NodeRelay private(nodeParams: NodeParams,
365366
// We want to fulfill upstream as soon as we receive the preimage (even if not all HTLCs have fulfilled downstream).
366367
context.log.debug("got preimage from downstream")
367368
fulfillPayment(upstream, paymentPreimage)
368-
sending(upstream, recipient, nextPayload, startedAt, fulfilledUpstream = true)
369+
sending(upstream, recipient, recipientFeatures_opt, nextPayload, startedAt, fulfilledUpstream = true)
369370
} else {
370371
// we don't want to fulfill multiple times
371372
Behaviors.same
@@ -381,7 +382,7 @@ class NodeRelay private(nodeParams: NodeParams,
381382
stopping()
382383
case WrappedPaymentFailed(PaymentFailed(_, _, failures, _)) =>
383384
nextWalletNodeId(nodeParams, recipient) match {
384-
case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(nodeParams, failures) =>
385+
case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(nodeParams, recipientFeatures_opt, failures) =>
385386
context.log.info("trampoline payment failed, attempting on-the-fly funding")
386387
attemptOnTheFlyFunding(upstream, walletNodeId, recipient, nextPayload, failures, startedAt)
387388
case _ =>

0 commit comments

Comments
 (0)