From 69014d2fd312d72a62cdb169ec00213d3f9bed17 Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 11 Sep 2024 17:43:49 +0200 Subject: [PATCH] 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. --- .../fr/acinq/eclair/io/MessageRelay.scala | 6 +-- .../main/scala/fr/acinq/eclair/io/Peer.scala | 11 ++--- .../acinq/eclair/io/PeerReadyNotifier.scala | 18 ++++---- .../eclair/payment/relay/ChannelRelay.scala | 26 ++++++------ .../eclair/payment/relay/NodeRelay.scala | 21 +++++----- .../fr/acinq/eclair/io/MessageRelaySpec.scala | 10 ++--- .../eclair/io/PeerReadyNotifierSpec.scala | 41 +++++++++++-------- .../eclair/json/JsonSerializersSpec.scala | 2 +- .../relay/AsyncPaymentTriggererSpec.scala | 14 +++---- .../payment/relay/ChannelRelayerSpec.scala | 31 ++++++++++++-- .../payment/relay/NodeRelayerSpec.scala | 10 ++--- .../fr/acinq/eclair/api/ApiServiceSpec.scala | 2 + 12 files changed, 113 insertions(+), 79 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/MessageRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/MessageRelay.scala index 368d883c14..1a21129421 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/MessageRelay.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/MessageRelay.scala @@ -154,7 +154,7 @@ private class MessageRelay(nodeParams: NodeParams, private def waitForPreviousPeerForPolicyCheck(msg: OnionMessage, nextNodeId: PublicKey): Behavior[Command] = { Behaviors.receiveMessagePartial { - case WrappedPeerInfo(PeerInfo(_, _, _, _, channels)) if channels.nonEmpty => + case WrappedPeerInfo(info: PeerInfo) if info.channels.nonEmpty => switchboard ! GetPeerInfo(context.messageAdapter(WrappedPeerInfo), nextNodeId) waitForNextPeerForPolicyCheck(msg, nextNodeId) case _ => @@ -167,8 +167,8 @@ private class MessageRelay(nodeParams: NodeParams, private def waitForNextPeerForPolicyCheck(msg: OnionMessage, nextNodeId: PublicKey): Behavior[Command] = { Behaviors.receiveMessagePartial { - case WrappedPeerInfo(PeerInfo(peer, _, _, _, channels)) if channels.nonEmpty => - peer ! Peer.RelayOnionMessage(messageId, msg, replyTo_opt) + case WrappedPeerInfo(info: PeerInfo) if info.channels.nonEmpty => + info.peer ! Peer.RelayOnionMessage(messageId, msg, replyTo_opt) Behaviors.stopped case _ => Metrics.OnionMessagesNotRelayed.withTag(Tags.Reason, Tags.Reasons.NoChannelWithNextPeer).increment() diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 97a52a33dd..bd02ae6cc3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -475,10 +475,11 @@ class Peer(val nodeParams: NodeParams, case Event(r: GetPeerInfo, d) => val replyTo = r.replyTo.getOrElse(sender().toTyped) - replyTo ! PeerInfo(self, remoteNodeId, stateName, d match { - case c: ConnectedData => Some(c.address) - case _ => None - }, d.channels.values.toSet) + val peerInfo = d match { + case c: ConnectedData => PeerInfo(self, remoteNodeId, stateName, Some(c.remoteFeatures), Some(c.address), c.channels.values.toSet) + case _ => PeerInfo(self, remoteNodeId, stateName, None, None, d.channels.values.toSet) + } + replyTo ! peerInfo stay() case Event(r: GetPeerChannels, d) => @@ -867,7 +868,7 @@ object Peer { case class GetPeerInfo(replyTo: Option[typed.ActorRef[PeerInfoResponse]]) sealed trait PeerInfoResponse { def nodeId: PublicKey } - case class PeerInfo(peer: ActorRef, nodeId: PublicKey, state: State, address: Option[NodeAddress], channels: Set[ActorRef]) extends PeerInfoResponse + case class PeerInfo(peer: ActorRef, nodeId: PublicKey, state: State, features: Option[Features[InitFeature]], address: Option[NodeAddress], channels: Set[ActorRef]) extends PeerInfoResponse case class PeerNotFound(nodeId: PublicKey) extends PeerInfoResponse with DisconnectResponse { override def toString: String = s"peer $nodeId not found" } /** Return the peer's current channels: note that the data may change concurrently, never assume it is fully up-to-date. */ diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerReadyNotifier.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerReadyNotifier.scala index f4a8b8d677..932e414cdf 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerReadyNotifier.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerReadyNotifier.scala @@ -23,7 +23,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.blockchain.CurrentBlockHeight -import fr.acinq.eclair.{BlockHeight, Logs, channel} +import fr.acinq.eclair.{BlockHeight, Features, InitFeature, Logs, channel} import scala.concurrent.duration.{DurationInt, FiniteDuration} @@ -107,7 +107,7 @@ object PeerReadyNotifier { private case object PeerNotConnected extends Command private case object PeerConnected extends Command private case object PeerDisconnected extends Command - private case class WrappedPeerInfo(peer: ActorRef[Peer.GetPeerChannels], channelCount: Int) extends Command + private case class WrappedPeerInfo(peer: ActorRef[Peer.GetPeerChannels], remoteFeatures: Features[InitFeature], channelCount: Int) extends Command private case class NewBlockNotTimedOut(currentBlockHeight: BlockHeight) extends Command private case object CheckChannelsReady extends Command private case class WrappedPeerChannels(wrapped: Peer.PeerChannels) extends Command @@ -115,7 +115,7 @@ object PeerReadyNotifier { private case object ToBeIgnored extends Command sealed trait Result { def remoteNodeId: PublicKey } - case class PeerReady(remoteNodeId: PublicKey, peer: akka.actor.ActorRef, channelInfos: Seq[Peer.ChannelInfo]) extends Result { val channelsCount: Int = channelInfos.size } + case class PeerReady(remoteNodeId: PublicKey, peer: akka.actor.ActorRef, remoteFeatures: Features[InitFeature], channelInfos: Seq[Peer.ChannelInfo]) extends Result { val channelsCount: Int = channelInfos.size } case class PeerUnavailable(remoteNodeId: PublicKey) extends Result private case object ChannelsReadyTimerKey @@ -243,7 +243,7 @@ private class PeerReadyNotifier(replyTo: ActorRef[PeerReadyNotifier.Result], // In that case we still want to wait for a connection, because we may want to open a channel to them. case _: Peer.PeerNotFound => PeerNotConnected case info: Peer.PeerInfo if info.state != Peer.CONNECTED => PeerNotConnected - case info: Peer.PeerInfo => WrappedPeerInfo(info.peer.toTyped, info.channels.size) + case info: Peer.PeerInfo => WrappedPeerInfo(info.peer.toTyped, info.features.getOrElse(Features.empty), info.channels.size) } // We check whether the peer is already connected. switchboard ! Switchboard.GetPeerInfo(peerInfoAdapter, remoteNodeId) @@ -256,14 +256,14 @@ private class PeerReadyNotifier(replyTo: ActorRef[PeerReadyNotifier.Result], Behaviors.same case PeerDisconnected => Behaviors.same - case WrappedPeerInfo(peer, channelCount) => + case WrappedPeerInfo(peer, remoteFeatures, channelCount) => if (channelCount == 0) { log.info("peer is ready with no channels") - replyTo ! PeerReady(remoteNodeId, peer.toClassic, Seq.empty) + replyTo ! PeerReady(remoteNodeId, peer.toClassic, remoteFeatures, Seq.empty) Behaviors.stopped } else { log.debug("peer is connected with {} channels", channelCount) - waitForChannelsReady(peer, switchboard) + waitForChannelsReady(peer, switchboard, remoteFeatures) } case NewBlockNotTimedOut(currentBlockHeight) => log.debug("waiting for peer to connect at block {}", currentBlockHeight) @@ -277,7 +277,7 @@ private class PeerReadyNotifier(replyTo: ActorRef[PeerReadyNotifier.Result], } } - private def waitForChannelsReady(peer: ActorRef[Peer.GetPeerChannels], switchboard: ActorRef[Switchboard.GetPeerInfo]): Behavior[Command] = { + private def waitForChannelsReady(peer: ActorRef[Peer.GetPeerChannels], switchboard: ActorRef[Switchboard.GetPeerInfo], remoteFeatures: Features[InitFeature]): Behavior[Command] = { timers.startTimerWithFixedDelay(ChannelsReadyTimerKey, CheckChannelsReady, initialDelay = 50 millis, delay = 1 second) Behaviors.receiveMessagePartial { case CheckChannelsReady => @@ -286,7 +286,7 @@ private class PeerReadyNotifier(replyTo: ActorRef[PeerReadyNotifier.Result], Behaviors.same case WrappedPeerChannels(peerChannels) => if (peerChannels.channels.map(_.state).forall(isChannelReady)) { - replyTo ! PeerReady(remoteNodeId, peer.toClassic, peerChannels.channels) + replyTo ! PeerReady(remoteNodeId, peer.toClassic, remoteFeatures, peerChannels.channels) Behaviors.stopped } else { log.debug("peer has {} channels that are not ready", peerChannels.channels.count(s => !isChannelReady(s.state))) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala index 7188606783..e6159d5ec2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala @@ -34,7 +34,7 @@ import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket} import fr.acinq.eclair.wire.protocol.FailureMessageCodecs.createBadOnionFailure import fr.acinq.eclair.wire.protocol.PaymentOnion.IntermediatePayload import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{Features, Logs, NodeParams, ShortChannelId, TimestampMilli, TimestampSecond, channel, nodeFee} +import fr.acinq.eclair.{Features, InitFeature, Logs, NodeParams, TimestampMilli, TimestampSecond, channel, nodeFee} import java.util.UUID import java.util.concurrent.TimeUnit @@ -153,7 +153,7 @@ class ChannelRelay private(nodeParams: NodeParams, case Some(walletNodeId) if nodeParams.peerWakeUpConfig.enabled => wakeUp(walletNodeId) case _ => context.self ! DoRelay - relay(Seq.empty) + relay(None, Seq.empty) } } @@ -166,13 +166,13 @@ class ChannelRelay private(nodeParams: NodeParams, Metrics.recordPaymentRelayFailed(Tags.FailureType.WakeUp, Tags.RelayType.Channel) context.log.info("rejecting htlc: failed to wake-up remote peer") safeSendAndStop(r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true)) - case WrappedPeerReadyResult(_: PeerReadyNotifier.PeerReady) => + case WrappedPeerReadyResult(r: PeerReadyNotifier.PeerReady) => context.self ! DoRelay - relay(Seq.empty) + relay(Some(r.remoteFeatures), Seq.empty) } } - def relay(previousFailures: Seq[PreviouslyTried]): Behavior[Command] = { + def relay(remoteFeatures_opt: Option[Features[InitFeature]], previousFailures: Seq[PreviouslyTried]): Behavior[Command] = { Behaviors.receiveMessagePartial { case DoRelay => if (previousFailures.isEmpty) { @@ -180,7 +180,7 @@ class ChannelRelay private(nodeParams: NodeParams, context.log.info("relaying htlc #{} from channelId={} to requestedShortChannelId={} nextNode={}", r.add.id, r.add.channelId, requestedShortChannelId_opt, nextNodeId_opt.getOrElse("")) } context.log.debug("attempting relay previousAttempts={}", previousFailures.size) - handleRelay(previousFailures) match { + handleRelay(remoteFeatures_opt, previousFailures) match { case RelayFailure(cmdFail) => Metrics.recordPaymentRelayFailed(Tags.FailureType(cmdFail), Tags.RelayType.Channel) context.log.info("rejecting htlc reason={}", cmdFail.reason) @@ -192,12 +192,12 @@ class ChannelRelay private(nodeParams: NodeParams, case RelaySuccess(selectedChannelId, cmdAdd) => context.log.info("forwarding htlc #{} from channelId={} to channelId={}", r.add.id, r.add.channelId, selectedChannelId) register ! Register.Forward(forwardFailureAdapter, selectedChannelId, cmdAdd) - waitForAddResponse(selectedChannelId, previousFailures) + waitForAddResponse(selectedChannelId, remoteFeatures_opt, previousFailures) } } } - private def waitForAddResponse(selectedChannelId: ByteVector32, previousFailures: Seq[PreviouslyTried]): Behavior[Command] = + private def waitForAddResponse(selectedChannelId: ByteVector32, remoteFeatures_opt: Option[Features[InitFeature]], previousFailures: Seq[PreviouslyTried]): Behavior[Command] = Behaviors.receiveMessagePartial { case WrappedForwardFailure(Register.ForwardFailure(Register.Forward(_, channelId, _))) => context.log.warn(s"couldn't resolve downstream channel $channelId, failing htlc #${upstream.add.id}") @@ -208,7 +208,7 @@ class ChannelRelay private(nodeParams: NodeParams, case WrappedAddResponse(addFailed: RES_ADD_FAILED[_]) => context.log.info("attempt failed with reason={}", addFailed.t.getClass.getSimpleName) context.self ! DoRelay - relay(previousFailures :+ PreviouslyTried(selectedChannelId, addFailed)) + relay(remoteFeatures_opt, previousFailures :+ PreviouslyTried(selectedChannelId, addFailed)) case WrappedAddResponse(_: RES_SUCCESS[_]) => context.log.debug("sent htlc to the downstream channel") @@ -280,7 +280,7 @@ class ChannelRelay private(nodeParams: NodeParams, * - a CMD_FAIL_HTLC to be sent back upstream * - a CMD_ADD_HTLC to propagate downstream */ - private def handleRelay(previousFailures: Seq[PreviouslyTried]): RelayResult = { + private def handleRelay(remoteFeatures_opt: Option[Features[InitFeature]], previousFailures: Seq[PreviouslyTried]): RelayResult = { val alreadyTried = previousFailures.map(_.channelId) selectPreferredChannel(alreadyTried) match { case Some(outgoingChannel) => relayOrFail(outgoingChannel) @@ -298,7 +298,7 @@ class ChannelRelay private(nodeParams: NodeParams, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true) } walletNodeId_opt match { - case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(previousFailures) => RelayNeedsFunding(walletNodeId, cmdFail) + case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(remoteFeatures_opt, previousFailures) => RelayNeedsFunding(walletNodeId, cmdFail) case _ => RelayFailure(cmdFail) } } @@ -400,8 +400,8 @@ class ChannelRelay private(nodeParams: NodeParams, } /** If we fail to relay a payment, we may want to attempt on-the-fly funding. */ - private def shouldAttemptOnTheFlyFunding(previousFailures: Seq[PreviouslyTried]): Boolean = { - val featureOk = nodeParams.features.hasFeature(Features.OnTheFlyFunding) + private def shouldAttemptOnTheFlyFunding(remoteFeatures_opt: Option[Features[InitFeature]], previousFailures: Seq[PreviouslyTried]): Boolean = { + val featureOk = Features.canUseFeature(nodeParams.features.initFeatures(), remoteFeatures_opt.getOrElse(Features.empty), Features.OnTheFlyFunding) // If we have a channel with the next node, we only want to perform on-the-fly funding for liquidity issues. val liquidityIssue = previousFailures.forall { case PreviouslyTried(_, RES_ADD_FAILED(_, _: InsufficientFunds, _)) => true diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala index 6690928ef2..cec8e18aca 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala @@ -42,7 +42,7 @@ import fr.acinq.eclair.router.Router.{ChannelHop, HopRelayParams, Route, RoutePa import fr.acinq.eclair.router.{BalanceTooLow, RouteNotFound} import fr.acinq.eclair.wire.protocol.PaymentOnion.IntermediatePayload import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{Alias, CltvExpiry, CltvExpiryDelta, EncodedNodeId, Features, Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, TimestampMilli, UInt64, nodeFee, randomBytes32} +import fr.acinq.eclair.{Alias, CltvExpiry, CltvExpiryDelta, EncodedNodeId, Features, InitFeature, Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, TimestampMilli, UInt64, nodeFee, randomBytes32} import java.util.UUID import java.util.concurrent.TimeUnit @@ -164,8 +164,8 @@ object NodeRelay { } /** If we fail to relay a payment, we may want to attempt on-the-fly funding if it makes sense. */ - private def shouldAttemptOnTheFlyFunding(nodeParams: NodeParams, failures: Seq[PaymentFailure]): Boolean = { - val featureOk = nodeParams.features.hasFeature(Features.OnTheFlyFunding) + private def shouldAttemptOnTheFlyFunding(nodeParams: NodeParams, recipientFeatures_opt: Option[Features[InitFeature]], failures: Seq[PaymentFailure]): Boolean = { + val featureOk = Features.canUseFeature(nodeParams.features.initFeatures(), recipientFeatures_opt.getOrElse(Features.empty), Features.OnTheFlyFunding) val balanceTooLow = failures.collectFirst { case f@LocalFailure(_, _, BalanceTooLow) => f }.nonEmpty val routeNotFound = failures.collectFirst { case f@LocalFailure(_, _, RouteNotFound) => f }.nonEmpty featureOk && (balanceTooLow || routeNotFound) @@ -298,7 +298,7 @@ class NodeRelay private(nodeParams: NodeParams, private def ensureRecipientReady(upstream: Upstream.Hot.Trampoline, recipient: Recipient, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket]): Behavior[Command] = { nextWalletNodeId(nodeParams, recipient) match { case Some(walletNodeId) if nodeParams.peerWakeUpConfig.enabled => waitForPeerReady(upstream, walletNodeId, recipient, nextPayload, nextPacket_opt) - case _ => relay(upstream, recipient, nextPayload, nextPacket_opt) + case _ => relay(upstream, recipient, None, nextPayload, nextPacket_opt) } } @@ -316,14 +316,14 @@ class NodeRelay private(nodeParams: NodeParams, context.log.warn("rejecting payment: failed to wake-up remote peer") rejectPayment(upstream, Some(UnknownNextPeer())) stopping() - case WrappedPeerReadyResult(_: PeerReadyNotifier.PeerReady) => - relay(upstream, recipient, nextPayload, nextPacket_opt) + case WrappedPeerReadyResult(r: PeerReadyNotifier.PeerReady) => + relay(upstream, recipient, Some(r.remoteFeatures), nextPayload, nextPacket_opt) } } } /** Relay the payment to the next identified node: this is similar to sending an outgoing payment. */ - private def relay(upstream: Upstream.Hot.Trampoline, recipient: Recipient, payloadOut: IntermediatePayload.NodeRelay, packetOut_opt: Option[OnionRoutingPacket]): Behavior[Command] = { + private def relay(upstream: Upstream.Hot.Trampoline, recipient: Recipient, recipientFeatures_opt: Option[Features[InitFeature]], payloadOut: IntermediatePayload.NodeRelay, packetOut_opt: Option[OnionRoutingPacket]): Behavior[Command] = { context.log.debug("relaying trampoline payment (amountIn={} expiryIn={} amountOut={} expiryOut={})", upstream.amountIn, upstream.expiryIn, payloadOut.amountToForward, payloadOut.outgoingCltv) val confidence = (upstream.received.map(_.add.endorsement).min + 0.5) / 8 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, } val payFSM = outgoingPaymentFactory.spawnOutgoingPayFSM(context, paymentCfg, useMultiPart) payFSM ! payment - sending(upstream, recipient, payloadOut, TimestampMilli.now(), fulfilledUpstream = false) + sending(upstream, recipient, recipientFeatures_opt, payloadOut, TimestampMilli.now(), fulfilledUpstream = false) } /** @@ -354,6 +354,7 @@ class NodeRelay private(nodeParams: NodeParams, */ private def sending(upstream: Upstream.Hot.Trampoline, recipient: Recipient, + recipientFeatures_opt: Option[Features[InitFeature]], nextPayload: IntermediatePayload.NodeRelay, startedAt: TimestampMilli, fulfilledUpstream: Boolean): Behavior[Command] = @@ -365,7 +366,7 @@ class NodeRelay private(nodeParams: NodeParams, // We want to fulfill upstream as soon as we receive the preimage (even if not all HTLCs have fulfilled downstream). context.log.debug("got preimage from downstream") fulfillPayment(upstream, paymentPreimage) - sending(upstream, recipient, nextPayload, startedAt, fulfilledUpstream = true) + sending(upstream, recipient, recipientFeatures_opt, nextPayload, startedAt, fulfilledUpstream = true) } else { // we don't want to fulfill multiple times Behaviors.same @@ -381,7 +382,7 @@ class NodeRelay private(nodeParams: NodeParams, stopping() case WrappedPaymentFailed(PaymentFailed(_, _, failures, _)) => nextWalletNodeId(nodeParams, recipient) match { - case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(nodeParams, failures) => + case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(nodeParams, recipientFeatures_opt, failures) => context.log.info("trampoline payment failed, attempting on-the-fly funding") attemptOnTheFlyFunding(upstream, walletNodeId, recipient, nextPayload, failures, startedAt) case _ => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/MessageRelaySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/MessageRelaySpec.scala index ae45c4c95b..a35ead69be 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/MessageRelaySpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/MessageRelaySpec.scala @@ -112,7 +112,7 @@ class MessageRelaySpec extends ScalaTestWithActorTestKit(ConfigFactory.load("app val request = switchboard.expectMsgType[GetPeerInfo] assert(request.remoteNodeId == bobId) - request.replyTo ! Peer.PeerInfo(peer.ref.toClassic, bobId, Peer.CONNECTED, None, Set.empty) + request.replyTo ! Peer.PeerInfo(peer.ref.toClassic, bobId, Peer.CONNECTED, None, None, Set.empty) assert(peer.expectMessageType[Peer.RelayOnionMessage].msg == message) } @@ -148,7 +148,7 @@ class MessageRelaySpec extends ScalaTestWithActorTestKit(ConfigFactory.load("app val getPeerInfo = switchboard.expectMsgType[GetPeerInfo] assert(getPeerInfo.remoteNodeId == previousNodeId) - getPeerInfo.replyTo ! PeerInfo(peer.ref.toClassic, previousNodeId, Peer.CONNECTED, None, Set.empty) + getPeerInfo.replyTo ! PeerInfo(peer.ref.toClassic, previousNodeId, Peer.CONNECTED, None, None, Set.empty) probe.expectMessage(AgainstPolicy(messageId, RelayChannelsOnly)) peer.expectNoMessage(100 millis) @@ -164,7 +164,7 @@ class MessageRelaySpec extends ScalaTestWithActorTestKit(ConfigFactory.load("app val getPeerInfo1 = switchboard.expectMsgType[GetPeerInfo] assert(getPeerInfo1.remoteNodeId == previousNodeId) - getPeerInfo1.replyTo ! PeerInfo(peer.ref.toClassic, previousNodeId, Peer.CONNECTED, None, Set(TestProbe()(system.classicSystem).ref)) + getPeerInfo1.replyTo ! PeerInfo(peer.ref.toClassic, previousNodeId, Peer.CONNECTED, None, None, Set(TestProbe()(system.classicSystem).ref)) val getPeerInfo2 = switchboard.expectMsgType[GetPeerInfo] assert(getPeerInfo2.remoteNodeId == bobId) @@ -184,11 +184,11 @@ class MessageRelaySpec extends ScalaTestWithActorTestKit(ConfigFactory.load("app val getPeerInfo1 = switchboard.expectMsgType[GetPeerInfo] assert(getPeerInfo1.remoteNodeId == previousNodeId) - getPeerInfo1.replyTo ! PeerInfo(TestProbe()(system.classicSystem).ref, previousNodeId, Peer.CONNECTED, None, Set(TestProbe()(system.classicSystem).ref)) + getPeerInfo1.replyTo ! PeerInfo(TestProbe()(system.classicSystem).ref, previousNodeId, Peer.CONNECTED, None, None, Set(TestProbe()(system.classicSystem).ref)) val getPeerInfo2 = switchboard.expectMsgType[GetPeerInfo] assert(getPeerInfo2.remoteNodeId == bobId) - getPeerInfo2.replyTo ! PeerInfo(peer.ref.toClassic, bobId, Peer.CONNECTED, None, Set(0, 1).map(_ => TestProbe()(system.classicSystem).ref)) + getPeerInfo2.replyTo ! PeerInfo(peer.ref.toClassic, bobId, Peer.CONNECTED, None, None, Set(0, 1).map(_ => TestProbe()(system.classicSystem).ref)) assert(peer.expectMessageType[Peer.RelayOnionMessage].msg == message) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerReadyNotifierSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerReadyNotifierSpec.scala index 0d1dc5e826..29d10e3953 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerReadyNotifierSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerReadyNotifierSpec.scala @@ -25,7 +25,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.blockchain.CurrentBlockHeight import fr.acinq.eclair.channel._ import fr.acinq.eclair.io.PeerReadyNotifier.{NotifyWhenPeerReady, PeerUnavailable} -import fr.acinq.eclair.{BlockHeight, randomKey} +import fr.acinq.eclair.{BlockHeight, FeatureSupport, Features, randomKey} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -33,6 +33,12 @@ import scala.concurrent.duration.DurationInt class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("application")) with FixtureAnyFunSuiteLike { + private val remoteFeatures = Features( + Features.StaticRemoteKey -> FeatureSupport.Optional, + Features.AnchorOutputsZeroFeeHtlcTx -> FeatureSupport.Optional, + Features.RouteBlinding -> FeatureSupport.Optional, + ).initFeatures() + case class FixtureParam(remoteNodeId: PublicKey, peerReadyManager: TestProbe[PeerReadyManager.Register], switchboard: TestProbe[Switchboard.GetPeerInfo], peer: TestProbe[Peer.GetPeerChannels], probe: TestProbe[PeerReadyNotifier.Result]) override def withFixture(test: OneArgTest): Outcome = { @@ -83,8 +89,8 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load notifier ! NotifyWhenPeerReady(probe.ref) peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(remoteNodeId, otherAttempts = 0) val request = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set.empty) - probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, Seq.empty)) + request.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, Some(remoteFeatures), None, Set.empty) + probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, remoteFeatures, Seq.empty)) } test("peer connected (with channels)") { f => @@ -94,7 +100,7 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load notifier ! NotifyWhenPeerReady(probe.ref) peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(remoteNodeId, otherAttempts = 0) val request1 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic, TestProbe().ref.toClassic)) + request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, Some(remoteFeatures), None, Set(TestProbe().ref.toClassic, TestProbe().ref.toClassic)) // Channels are not ready yet. val channels2 = Seq(Peer.ChannelInfo(null, SYNCING, null), Peer.ChannelInfo(null, SYNCING, null)) @@ -112,7 +118,7 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load val channels4 = Seq(Peer.ChannelInfo(null, NORMAL, null), Peer.ChannelInfo(null, SHUTDOWN, null)) val request4 = peer.expectMessageType[Peer.GetPeerChannels] request4.replyTo ! Peer.PeerChannels(remoteNodeId, channels4) - probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, channels4)) + probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, remoteFeatures, channels4)) } test("peer connects after initial request") { f => @@ -122,7 +128,7 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load notifier ! NotifyWhenPeerReady(probe.ref) peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(remoteNodeId, otherAttempts = 1) val request1 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, Set(TestProbe().ref.toClassic, TestProbe().ref.toClassic)) + request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, None, Set(TestProbe().ref.toClassic, TestProbe().ref.toClassic)) peer.expectNoMessage(100 millis) // An unrelated peer connects. @@ -132,10 +138,10 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load // The target peer connects. system.eventStream ! EventStream.Publish(PeerConnected(TestProbe().ref.toClassic, remoteNodeId, null)) val request2 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic, TestProbe().ref.toClassic)) + request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, Some(remoteFeatures), None, Set(TestProbe().ref.toClassic, TestProbe().ref.toClassic)) val channels = Seq(Peer.ChannelInfo(null, NEGOTIATING, null)) peer.expectMessageType[Peer.GetPeerChannels].replyTo ! Peer.PeerChannels(remoteNodeId, channels) - probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, channels)) + probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, remoteFeatures, channels)) } test("peer connects then disconnects") { f => @@ -151,16 +157,16 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load // The target peer connects and instantly disconnects. system.eventStream ! EventStream.Publish(PeerConnected(TestProbe().ref.toClassic, remoteNodeId, null)) val request2 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, Set(TestProbe().ref.toClassic)) + request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, None, Set(TestProbe().ref.toClassic)) peer.expectNoMessage(100 millis) // The target peer reconnects and stays connected. system.eventStream ! EventStream.Publish(PeerConnected(TestProbe().ref.toClassic, remoteNodeId, null)) val request3 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request3.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic)) + request3.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, Some(remoteFeatures), None, Set(TestProbe().ref.toClassic)) val channels = Seq(Peer.ChannelInfo(null, CLOSING, null)) peer.expectMessageType[Peer.GetPeerChannels].replyTo ! Peer.PeerChannels(remoteNodeId, channels) - probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, channels)) + probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, remoteFeatures, channels)) } test("peer connects then disconnects (while waiting for channel states)") { f => @@ -170,22 +176,23 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load notifier ! NotifyWhenPeerReady(probe.ref) peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(remoteNodeId, otherAttempts = 5) val request1 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, Set.empty) + request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, None, Set.empty) peer.expectNoMessage(100 millis) // The target peer connects. system.eventStream ! EventStream.Publish(PeerConnected(TestProbe().ref.toClassic, remoteNodeId, null)) val request2 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic)) + request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, Some(remoteFeatures), None, Set(TestProbe().ref.toClassic)) peer.expectMessageType[Peer.GetPeerChannels] // The target peer disconnects, so we wait for them to connect again. system.eventStream ! EventStream.Publish(PeerDisconnected(TestProbe().ref.toClassic, remoteNodeId)) val request3 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request3.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic)) + val remoteFeatures1 = remoteFeatures.add(Features.OnionMessages, FeatureSupport.Optional) + request3.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, Some(remoteFeatures1), None, Set(TestProbe().ref.toClassic)) val channels = Seq(Peer.ChannelInfo(null, NORMAL, null)) peer.expectMessageType[Peer.GetPeerChannels].replyTo ! Peer.PeerChannels(remoteNodeId, channels) - probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, channels)) + probe.expectMessage(PeerReadyNotifier.PeerReady(remoteNodeId, peer.ref.toClassic, remoteFeatures1, channels)) } test("peer connected (duration timeout)") { f => @@ -195,7 +202,7 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load notifier ! NotifyWhenPeerReady(probe.ref) peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(remoteNodeId, otherAttempts = 0) val request = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic)) + request.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, Some(Features.empty), None, Set(TestProbe().ref.toClassic)) peer.expectMessageType[Peer.GetPeerChannels] probe.expectMessage(PeerUnavailable(remoteNodeId)) } @@ -207,7 +214,7 @@ class PeerReadyNotifierSpec extends ScalaTestWithActorTestKit(ConfigFactory.load notifier ! NotifyWhenPeerReady(probe.ref) peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(remoteNodeId, otherAttempts = 2) val request = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic)) + request.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, None, Set(TestProbe().ref.toClassic)) peer.expectMessageType[Peer.GetPeerChannels] system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100))) probe.expectMessage(PeerUnavailable(remoteNodeId)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 587c1e6006..2bb3b81042 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -110,7 +110,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat } test("PeerInfo serialization") { - val peerInfo = PeerInfo(ActorRef.noSender, PublicKey(hex"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), Peer.CONNECTED, None, Set(ActorRef.noSender)) + val peerInfo = PeerInfo(ActorRef.noSender, PublicKey(hex"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), Peer.CONNECTED, None, None, Set(ActorRef.noSender)) val expected = """{"nodeId":"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b","state":"CONNECTED","channels":1}""" JsonSerializers.serialization.write(peerInfo)(JsonSerializers.formats) shouldBe expected } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggererSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggererSpec.scala index eb19cfd3ad..55d0335b3e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggererSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/AsyncPaymentTriggererSpec.scala @@ -129,7 +129,7 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. triggerer ! Watch(probe.ref, remoteNodeId, paymentHash = ByteVector32.Zeroes, timeout = BlockHeight(100)) val request1 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, Set(TestProbe().ref.toClassic)) + request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, None, Set(TestProbe().ref.toClassic)) // An unrelated peer connects. system.eventStream ! EventStream.Publish(PeerConnected(peer.ref.toClassic, randomKey().publicKey, null)) @@ -138,7 +138,7 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. // The target peer connects. system.eventStream ! EventStream.Publish(PeerConnected(peer.ref.toClassic, remoteNodeId, null)) val request2 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic)) + request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, None, Set(TestProbe().ref.toClassic)) peer.expectMessageType[Peer.GetPeerChannels].replyTo ! Peer.PeerChannels(remoteNodeId, Seq(Peer.ChannelInfo(null, NEGOTIATING, null))) probe.expectMessage(AsyncPaymentTriggered) @@ -152,7 +152,7 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. triggerer ! Watch(probe.ref, remoteNodeId, paymentHash = ByteVector32.Zeroes, timeout = BlockHeight(100)) val request1 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, Set(TestProbe().ref.toClassic)) + request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, None, Set(TestProbe().ref.toClassic)) // Another async payment node relay watches the peer val probe2 = TestProbe[Result]() @@ -165,7 +165,7 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. // Second watch succeeds system.eventStream ! EventStream.Publish(PeerConnected(peer.ref.toClassic, remoteNodeId, null)) val request2 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic)) + request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.CONNECTED, None, None, Set(TestProbe().ref.toClassic)) peer.expectMessageType[Peer.GetPeerChannels].replyTo ! Peer.PeerChannels(remoteNodeId, Seq(Peer.ChannelInfo(null, NEGOTIATING, null))) probe.expectNoMessage(100 millis) probe2.expectMessage(AsyncPaymentTriggered) @@ -177,14 +177,14 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. // watch remote node triggerer ! Watch(probe.ref, remoteNodeId, paymentHash = ByteVector32.Zeroes, timeout = BlockHeight(100)) val request1 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, Set(TestProbe().ref.toClassic)) + request1.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId, Peer.DISCONNECTED, None, None, Set(TestProbe().ref.toClassic)) // watch another remote node val remoteNodeId2 = TestConstants.Bob.nodeParams.nodeId val probe2 = TestProbe[Result]() triggerer ! Watch(probe2.ref, remoteNodeId2, paymentHash = ByteVector32.Zeroes, timeout = BlockHeight(101)) val request2 = switchboard.expectMessageType[Switchboard.GetPeerInfo] - request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId2, Peer.DISCONNECTED, None, Set(TestProbe().ref.toClassic)) + request2.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId2, Peer.DISCONNECTED, None, None, Set(TestProbe().ref.toClassic)) // First remote node times out system.eventStream ! EventStream.Publish(CurrentBlockHeight(BlockHeight(100))) @@ -194,7 +194,7 @@ class AsyncPaymentTriggererSpec extends ScalaTestWithActorTestKit(ConfigFactory. system.eventStream ! EventStream.Publish(PeerConnected(peer.ref.toClassic, remoteNodeId2, null)) val request3 = switchboard.expectMessageType[Switchboard.GetPeerInfo] assert(request3.remoteNodeId == remoteNodeId2) - request3.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId2, Peer.CONNECTED, None, Set(TestProbe().ref.toClassic)) + request3.replyTo ! Peer.PeerInfo(peer.ref.toClassic, remoteNodeId2, Peer.CONNECTED, None, None, Set(TestProbe().ref.toClassic)) peer.expectMessageType[Peer.GetPeerChannels].replyTo ! Peer.PeerChannels(remoteNodeId, Seq(Peer.ChannelInfo(null, NEGOTIATING, null))) probe.expectNoMessage(100 millis) probe2.expectMessage(AsyncPaymentTriggered) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala index b17bde39d2..ea2d35b05a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala @@ -207,7 +207,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 0) val wakeUp = switchboard.expectMessageType[Switchboard.GetPeerInfo] assert(wakeUp.remoteNodeId == outgoingNodeId) - wakeUp.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, None, Set.empty) + wakeUp.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(nodeParams.features.initFeatures()), None, Set.empty) expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry, 7) }) @@ -232,7 +232,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a wakeUp.replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 0) val peerInfo = switchboard.expectMessageType[Switchboard.GetPeerInfo] assert(peerInfo.remoteNodeId == outgoingNodeId) - peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, None, Set.empty) + peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(nodeParams.features.initFeatures()), None, Set.empty) cleanUpWakeUpActors(peerReadyManager, switchboard) // We try to use existing channels, but they don't have enough liquidity. @@ -246,6 +246,29 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a assert(fwdNodeId.message.expiry == outgoingExpiry) } + test("relay blinded payment (on-the-fly funding not supported)", Tag(wakeUpEnabled), Tag(onTheFlyFunding)) { f => + import f._ + + val (peerReadyManager, switchboard) = createWakeUpActors() + + val u = createLocalUpdate(channelId1, feeBaseMsat = 5000 msat, feeProportionalMillionths = 0) + val payload = createBlindedPayload(Left(outgoingNodeId), u.channelUpdate, isIntroduction = false) + val r = createValidIncomingPacket(payload, outgoingAmount + u.channelUpdate.feeBaseMsat, outgoingExpiry + u.channelUpdate.cltvExpiryDelta) + + channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId) + + // We try to wake-up the next node. + peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 1) + val peerInfo = switchboard.expectMessageType[Switchboard.GetPeerInfo] + assert(peerInfo.remoteNodeId == outgoingNodeId) + // The next node doesn't support the on-the-fly funding feature. + peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(Features.empty), None, Set.empty) + cleanUpWakeUpActors(peerReadyManager, switchboard) + + // We fail without attempting on-the-fly funding. + expectFwdFail(register, r.add.channelId, CMD_FAIL_MALFORMED_HTLC(r.add.id, Sphinx.hash(r.add.onionRoutingPacket), InvalidOnionBlinding(Sphinx.hash(r.add.onionRoutingPacket)).code, commit = true)) + } + test("relay blinded payment (on-the-fly funding failed)", Tag(wakeUpEnabled), Tag(onTheFlyFunding)) { f => import f._ @@ -261,7 +284,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 1) val peerInfo = switchboard.expectMessageType[Switchboard.GetPeerInfo] assert(peerInfo.remoteNodeId == outgoingNodeId) - peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, None, Set.empty) + peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(nodeParams.features.initFeatures()), None, Set.empty) cleanUpWakeUpActors(peerReadyManager, switchboard) // We don't have any channel, so we attempt on-the-fly funding, but the peer is not available. @@ -287,7 +310,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 0) val peerInfo = switchboard.expectMessageType[Switchboard.GetPeerInfo] assert(peerInfo.remoteNodeId == outgoingNodeId) - peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, None, Set.empty) + peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(nodeParams.features.initFeatures()), None, Set.empty) cleanUpWakeUpActors(peerReadyManager, switchboard) // We try to use existing channels, but they reject the payment for a reason that isn't tied to the liquidity. diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala index 55b8ba9995..122f4e5560 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala @@ -648,7 +648,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl wakeUp.replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 0) val peerInfo = switchboard.expectMessageType[Switchboard.GetPeerInfo] assert(peerInfo.remoteNodeId == outgoingNodeId) - peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, None, Set.empty) + peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(nodeParams.features.initFeatures()), None, Set.empty) cleanUpWakeUpActors(peerReadyManager, switchboard) val outgoingCfg = mockPayFSM.expectMessageType[SendPaymentConfig] @@ -689,7 +689,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 0) val peerInfo = switchboard.expectMessageType[Switchboard.GetPeerInfo] assert(peerInfo.remoteNodeId == outgoingNodeId) - peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, None, Set.empty) + peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(nodeParams.features.initFeatures()), None, Set.empty) cleanUpWakeUpActors(peerReadyManager, switchboard) val outgoingCfg = mockPayFSM.expectMessageType[SendPaymentConfig] @@ -894,7 +894,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 0) val wakeUp = switchboard.expectMessageType[Switchboard.GetPeerInfo] assert(wakeUp.remoteNodeId == outgoingNodeId) - wakeUp.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, None, Set.empty) + wakeUp.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(nodeParams.features.initFeatures()), None, Set.empty) cleanUpWakeUpActors(peerReadyManager, switchboard) val outgoingCfg = mockPayFSM.expectMessageType[SendPaymentConfig] @@ -957,7 +957,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl // The remote node is a wallet node: we wake them up before relaying the payment. peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 1) val peerInfo = switchboard.expectMessageType[Switchboard.GetPeerInfo] - peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, None, Set.empty) + peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(nodeParams.features.initFeatures()), None, Set.empty) cleanUpWakeUpActors(peerReadyManager, switchboard) val outgoingCfg = mockPayFSM.expectMessageType[SendPaymentConfig] @@ -996,7 +996,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl // The remote node is a wallet node: we wake them up before relaying the payment. peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 0) val peerInfo = switchboard.expectMessageType[Switchboard.GetPeerInfo] - peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, None, Set.empty) + peerInfo.replyTo ! Peer.PeerInfo(TestProbe[Any]().ref.toClassic, outgoingNodeId, Peer.CONNECTED, Some(nodeParams.features.initFeatures()), None, Set.empty) cleanUpWakeUpActors(peerReadyManager, switchboard) val outgoingCfg = mockPayFSM.expectMessageType[SendPaymentConfig] diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index aabcd8eb09..f0b1326d42 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -168,12 +168,14 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM ActorRef.noSender, nodeId = aliceNodeId, state = Peer.CONNECTED, + features = Some(Features(Features.ChannelRangeQueries -> FeatureSupport.Optional).initFeatures()), address = Some(NodeAddress.fromParts("127.0.0.1", 9731).get), channels = Set(ActorRef.noSender)), PeerInfo( ActorRef.noSender, nodeId = bobNodeId, state = Peer.DISCONNECTED, + features = None, address = None, channels = Set(ActorRef.noSender))))