Skip to content

Commit 83d790e

Browse files
Add incoming peer to Hot.Channel (#2883)
1 parent 86373b4 commit 83d790e

16 files changed

Lines changed: 135 additions & 134 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ object Upstream {
140140
sealed trait Hot extends Upstream
141141
object Hot {
142142
/** Our node is forwarding a single incoming HTLC. */
143-
case class Channel(add: UpdateAddHtlc, receivedAt: TimestampMilli) extends Hot {
143+
case class Channel(add: UpdateAddHtlc, receivedAt: TimestampMilli, receivedFrom: PublicKey) extends Hot {
144144
override val amountIn: MilliSatoshi = add.amountMsat
145145
val expiryIn: CltvExpiry = add.cltvExpiry
146146
}
@@ -158,7 +158,7 @@ object Upstream {
158158
object Cold {
159159
def apply(hot: Hot): Cold = hot match {
160160
case Local(id) => Local(id)
161-
case Hot.Channel(add, _) => Cold.Channel(add.channelId, add.id, add.amountMsat)
161+
case Hot.Channel(add, _, _) => Cold.Channel(add.channelId, add.id, add.amountMsat)
162162
case Hot.Trampoline(received) => Cold.Trampoline(received.map(r => Cold.Channel(r.add.channelId, r.add.id, r.add.amountMsat)).toList)
163163
}
164164

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
637637
actions.foreach {
638638
case PostRevocationAction.RelayHtlc(add) =>
639639
log.debug("forwarding incoming htlc {} to relayer", add)
640-
relayer ! Relayer.RelayForward(add)
640+
relayer ! Relayer.RelayForward(add, remoteNodeId)
641641
case PostRevocationAction.RejectHtlc(add) =>
642642
log.debug("rejecting incoming htlc {}", add)
643643
// NB: we don't set commit = true, we will sign all updates at once afterwards.

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import akka.actor.typed.eventstream.EventStream
2222
import akka.actor.typed.scaladsl.adapter.TypedActorRefOps
2323
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
2424
import fr.acinq.bitcoin.scalacompat.ByteVector32
25+
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2526
import fr.acinq.eclair.channel._
2627
import fr.acinq.eclair.crypto.Sphinx
2728
import fr.acinq.eclair.db.PendingCommandsDb
@@ -53,15 +54,16 @@ object ChannelRelay {
5354
case class RelaySuccess(selectedChannelId: ByteVector32, cmdAdd: CMD_ADD_HTLC) extends RelayResult
5455
// @formatter:on
5556

56-
def apply(nodeParams: NodeParams, register: ActorRef, channels: Map[ByteVector32, Relayer.OutgoingChannel], relayId: UUID, r: IncomingPaymentPacket.ChannelRelayPacket): Behavior[Command] =
57+
def apply(nodeParams: NodeParams, register: ActorRef, channels: Map[ByteVector32, Relayer.OutgoingChannel], originNode: PublicKey, relayId: UUID, r: IncomingPaymentPacket.ChannelRelayPacket): Behavior[Command] =
5758
Behaviors.setup { context =>
5859
Behaviors.withMdc(Logs.mdc(
5960
category_opt = Some(Logs.LogCategory.PAYMENT),
6061
parentPaymentId_opt = Some(relayId), // for a channel relay, parent payment id = relay id
6162
paymentHash_opt = Some(r.add.paymentHash),
6263
nodeAlias_opt = Some(nodeParams.alias))) {
64+
val upstream = Upstream.Hot.Channel(r.add.removeUnknownTlvs(), TimestampMilli.now(), originNode)
6365
context.self ! DoRelay
64-
new ChannelRelay(nodeParams, register, channels, r, context).relay(Seq.empty)
66+
new ChannelRelay(nodeParams, register, channels, r, upstream, context).relay(Seq.empty)
6567
}
6668
}
6769

@@ -104,16 +106,14 @@ class ChannelRelay private(nodeParams: NodeParams,
104106
register: ActorRef,
105107
channels: Map[ByteVector32, Relayer.OutgoingChannel],
106108
r: IncomingPaymentPacket.ChannelRelayPacket,
107-
context: ActorContext[ChannelRelay.Command],
108-
startedAt: TimestampMilli = TimestampMilli.now()) {
109+
upstream: Upstream.Hot.Channel,
110+
context: ActorContext[ChannelRelay.Command]) {
109111

110112
import ChannelRelay._
111113

112114
private val forwardFailureAdapter = context.messageAdapter[Register.ForwardFailure[CMD_ADD_HTLC]](WrappedForwardFailure)
113115
private val addResponseAdapter = context.messageAdapter[CommandResponse[CMD_ADD_HTLC]](WrappedAddResponse)
114116

115-
private val upstream = Upstream.Hot.Channel(r.add.removeUnknownTlvs(), startedAt)
116-
117117
private case class PreviouslyTried(channelId: ByteVector32, failure: RES_ADD_FAILED[ChannelException])
118118

119119
def relay(previousFailures: Seq[PreviouslyTried]): Behavior[Command] = {
@@ -159,7 +159,7 @@ class ChannelRelay private(nodeParams: NodeParams,
159159
case WrappedAddResponse(RES_ADD_SETTLED(_, htlc, fulfill: HtlcResult.Fulfill)) =>
160160
context.log.debug("relaying fulfill to upstream")
161161
val cmd = CMD_FULFILL_HTLC(upstream.add.id, fulfill.paymentPreimage, commit = true)
162-
context.system.eventStream ! EventStream.Publish(ChannelPaymentRelayed(upstream.amountIn, htlc.amountMsat, htlc.paymentHash, upstream.add.channelId, htlc.channelId, startedAt, TimestampMilli.now()))
162+
context.system.eventStream ! EventStream.Publish(ChannelPaymentRelayed(upstream.amountIn, htlc.amountMsat, htlc.paymentHash, upstream.add.channelId, htlc.channelId, upstream.receivedAt, TimestampMilli.now()))
163163
recordRelayDuration(isSuccess = true)
164164
safeSendAndStop(upstream.add.channelId, cmd)
165165

@@ -320,5 +320,5 @@ class ChannelRelay private(nodeParams: NodeParams,
320320
Metrics.RelayedPaymentDuration
321321
.withTag(Tags.Relay, Tags.RelayType.Channel)
322322
.withTag(Tags.Success, isSuccess)
323-
.record((TimestampMilli.now() - startedAt).toMillis, TimeUnit.MILLISECONDS)
323+
.record((TimestampMilli.now() - upstream.receivedAt).toMillis, TimeUnit.MILLISECONDS)
324324
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ object ChannelRelayer {
4242
// @formatter:off
4343
sealed trait Command
4444
case class GetOutgoingChannels(replyTo: ActorRef, getOutgoingChannels: Relayer.GetOutgoingChannels) extends Command
45-
case class Relay(channelRelayPacket: IncomingPaymentPacket.ChannelRelayPacket) extends Command
45+
case class Relay(channelRelayPacket: IncomingPaymentPacket.ChannelRelayPacket, originNode: PublicKey) extends Command
4646
private[payment] case class WrappedLocalChannelUpdate(localChannelUpdate: LocalChannelUpdate) extends Command
4747
private[payment] case class WrappedLocalChannelDown(localChannelDown: LocalChannelDown) extends Command
4848
private[payment] case class WrappedAvailableBalanceChanged(availableBalanceChanged: AvailableBalanceChanged) extends Command
@@ -66,10 +66,9 @@ object ChannelRelayer {
6666
context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[LocalChannelDown](WrappedLocalChannelDown))
6767
context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[AvailableBalanceChanged](WrappedAvailableBalanceChanged))
6868
context.system.eventStream ! EventStream.Publish(SubscriptionsComplete(this.getClass))
69-
context.messageAdapter[IncomingPaymentPacket.ChannelRelayPacket](Relay)
7069
Behaviors.withMdc(Logs.mdc(category_opt = Some(Logs.LogCategory.PAYMENT), nodeAlias_opt = Some(nodeParams.alias)), mdc) {
7170
Behaviors.receiveMessage {
72-
case Relay(channelRelayPacket) =>
71+
case Relay(channelRelayPacket, originNode) =>
7372
val relayId = UUID.randomUUID()
7473
val nextNodeId_opt: Option[PublicKey] = scid2channels.get(channelRelayPacket.payload.outgoingChannelId) match {
7574
case Some(channelId) => channels.get(channelId).map(_.nextNodeId)
@@ -80,7 +79,7 @@ object ChannelRelayer {
8079
case None => Map.empty
8180
}
8281
context.log.debug(s"spawning a new handler with relayId=$relayId to nextNodeId={} with channels={}", nextNodeId_opt.getOrElse(""), nextChannels.keys.mkString(","))
83-
context.spawn(ChannelRelay.apply(nodeParams, register, nextChannels, relayId, channelRelayPacket), name = relayId.toString)
82+
context.spawn(ChannelRelay.apply(nodeParams, register, nextChannels, originNode, relayId, channelRelayPacket), name = relayId.toString)
8483
Behaviors.same
8584

8685
case GetOutgoingChannels(replyTo, Relayer.GetOutgoingChannels(enabledOnly)) =>

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
2323
import akka.actor.{ActorRef, typed}
2424
import com.softwaremill.quicklens.ModifyPimp
2525
import fr.acinq.bitcoin.scalacompat.ByteVector32
26+
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2627
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, Upstream}
2728
import fr.acinq.eclair.db.PendingCommandsDb
2829
import fr.acinq.eclair.payment.IncomingPaymentPacket.NodeRelayPacket
@@ -53,7 +54,7 @@ object NodeRelay {
5354

5455
// @formatter:off
5556
sealed trait Command
56-
case class Relay(nodeRelayPacket: IncomingPaymentPacket.NodeRelayPacket) extends Command
57+
case class Relay(nodeRelayPacket: IncomingPaymentPacket.NodeRelayPacket, originNode: PublicKey) extends Command
5758
case object Stop extends Command
5859
private case class WrappedMultiPartExtraPaymentReceived(mppExtraReceived: MultiPartPaymentFSM.ExtraPaymentReceived[HtlcPart]) extends Command
5960
private case class WrappedMultiPartPaymentFailed(mppFailed: MultiPartPaymentFSM.MultiPartPaymentFailed) extends Command
@@ -203,11 +204,11 @@ class NodeRelay private(nodeParams: NodeParams,
203204
*/
204205
private def receiving(htlcs: Queue[Upstream.Hot.Channel], nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket], handler: ActorRef): Behavior[Command] =
205206
Behaviors.receiveMessagePartial {
206-
case Relay(packet: IncomingPaymentPacket.NodeRelayPacket) =>
207+
case Relay(packet: IncomingPaymentPacket.NodeRelayPacket, originNode) =>
207208
require(packet.outerPayload.paymentSecret == paymentSecret, "payment secret mismatch")
208209
context.log.debug("forwarding incoming htlc #{} from channel {} to the payment FSM", packet.add.id, packet.add.channelId)
209210
handler ! MultiPartPaymentFSM.HtlcPart(packet.outerPayload.totalAmount, packet.add)
210-
receiving(htlcs :+ Upstream.Hot.Channel(packet.add.removeUnknownTlvs(), TimestampMilli.now()), nextPayload, nextPacket_opt, handler)
211+
receiving(htlcs :+ Upstream.Hot.Channel(packet.add.removeUnknownTlvs(), TimestampMilli.now(), originNode), nextPayload, nextPacket_opt, handler)
211212
case WrappedMultiPartPaymentFailed(MultiPartPaymentFSM.MultiPartPaymentFailed(_, failure, parts)) =>
212213
context.log.warn("could not complete incoming multi-part payment (parts={} paidAmount={} failure={})", parts.size, parts.map(_.amount).sum, failure)
213214
Metrics.recordPaymentRelayFailed(failure.getClass.getSimpleName, Tags.RelayType.Trampoline)
@@ -384,7 +385,7 @@ class NodeRelay private(nodeParams: NodeParams,
384385
}
385386

386387
private def rejectExtraHtlcPartialFunction: PartialFunction[Command, Behavior[Command]] = {
387-
case Relay(nodeRelayPacket) =>
388+
case Relay(nodeRelayPacket, _) =>
388389
rejectExtraHtlc(nodeRelayPacket.add)
389390
Behaviors.same
390391
// NB: this message would be sent from the payment FSM which we stopped before going to this state, but all this is asynchronous.

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import akka.actor.typed
2020
import akka.actor.typed.scaladsl.Behaviors
2121
import akka.actor.typed.{ActorRef, Behavior}
2222
import fr.acinq.bitcoin.scalacompat.ByteVector32
23+
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2324
import fr.acinq.eclair.payment._
2425
import fr.acinq.eclair.{Logs, NodeParams}
2526

@@ -38,7 +39,7 @@ object NodeRelayer {
3839

3940
// @formatter:off
4041
sealed trait Command
41-
case class Relay(nodeRelayPacket: IncomingPaymentPacket.NodeRelayPacket) extends Command
42+
case class Relay(nodeRelayPacket: IncomingPaymentPacket.NodeRelayPacket, originNode: PublicKey) extends Command
4243
case class RelayComplete(childHandler: ActorRef[NodeRelay.Command], paymentHash: ByteVector32, paymentSecret: ByteVector32) extends Command
4344
private[relay] case class GetPendingPayments(replyTo: akka.actor.ActorRef) extends Command
4445
// @formatter:on
@@ -61,20 +62,20 @@ object NodeRelayer {
6162
Behaviors.setup { context =>
6263
Behaviors.withMdc(Logs.mdc(category_opt = Some(Logs.LogCategory.PAYMENT)), mdc) {
6364
Behaviors.receiveMessage {
64-
case Relay(nodeRelayPacket) =>
65+
case Relay(nodeRelayPacket, originNode) =>
6566
val htlcIn = nodeRelayPacket.add
6667
val childKey = PaymentKey(htlcIn.paymentHash, nodeRelayPacket.outerPayload.paymentSecret)
6768
children.get(childKey) match {
6869
case Some(handler) =>
6970
context.log.debug("forwarding incoming htlc #{} from channel {} to existing handler", htlcIn.id, htlcIn.channelId)
70-
handler ! NodeRelay.Relay(nodeRelayPacket)
71+
handler ! NodeRelay.Relay(nodeRelayPacket, originNode)
7172
Behaviors.same
7273
case None =>
7374
val relayId = UUID.randomUUID()
7475
context.log.debug(s"spawning a new handler with relayId=$relayId")
7576
val handler = context.spawn(NodeRelay.apply(nodeParams, context.self, register, relayId, nodeRelayPacket, outgoingPaymentFactory, triggerer, router), relayId.toString)
7677
context.log.debug("forwarding incoming htlc #{} from channel {} to new handler", htlcIn.id, htlcIn.channelId)
77-
handler ! NodeRelay.Relay(nodeRelayPacket)
78+
handler ! NodeRelay.Relay(nodeRelayPacket, originNode)
7879
apply(nodeParams, register, outgoingPaymentFactory, triggerer, router, children + (childKey -> handler))
7980
}
8081
case RelayComplete(childHandler, paymentHash, paymentSecret) =>

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,20 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym
6262

6363
def receive: Receive = {
6464
case init: PostRestartHtlcCleaner.Init => postRestartCleaner forward init
65-
case RelayForward(add) =>
65+
case RelayForward(add, originNode) =>
6666
log.debug(s"received forwarding request for htlc #${add.id} from channelId=${add.channelId}")
6767
IncomingPaymentPacket.decrypt(add, nodeParams.privateKey, nodeParams.features) match {
6868
case Right(p: IncomingPaymentPacket.FinalPacket) =>
6969
log.debug(s"forwarding htlc #${add.id} to payment-handler")
7070
paymentHandler forward p
7171
case Right(r: IncomingPaymentPacket.ChannelRelayPacket) =>
72-
channelRelayer ! ChannelRelayer.Relay(r)
72+
channelRelayer ! ChannelRelayer.Relay(r, originNode)
7373
case Right(r: IncomingPaymentPacket.NodeRelayPacket) =>
7474
if (!nodeParams.enableTrampolinePayment) {
7575
log.warning(s"rejecting htlc #${add.id} from channelId=${add.channelId} reason=trampoline disabled")
7676
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, CMD_FAIL_HTLC(add.id, Right(RequiredNodeFeatureMissing()), commit = true))
7777
} else {
78-
nodeRelayer ! NodeRelayer.Relay(r)
78+
nodeRelayer ! NodeRelayer.Relay(r, originNode)
7979
}
8080
case Left(badOnion: BadOnion) =>
8181
log.warning(s"couldn't parse onion: reason=${badOnion.message}")
@@ -108,7 +108,7 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym
108108

109109
override def mdc(currentMessage: Any): MDC = {
110110
val paymentHash_opt = currentMessage match {
111-
case RelayForward(add) => Some(add.paymentHash)
111+
case RelayForward(add, _) => Some(add.paymentHash)
112112
case addFailed: RES_ADD_FAILED[_] => Some(addFailed.c.paymentHash)
113113
case addCompleted: RES_ADD_SETTLED[_, _] => Some(addCompleted.htlc.paymentHash)
114114
case _ => None
@@ -145,7 +145,7 @@ object Relayer extends Logging {
145145
}
146146
}
147147

148-
case class RelayForward(add: UpdateAddHtlc)
148+
case class RelayForward(add: UpdateAddHtlc, originNode: PublicKey)
149149
case class ChannelBalance(remoteNodeId: PublicKey, shortIds: ShortIds, canSend: MilliSatoshi, canReceive: MilliSatoshi, isPublic: Boolean, isEnabled: Boolean)
150150

151151
sealed trait OutgoingChannelParams {

eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
172172
val (ra2, htlca2) = addHtlc(100000000 msat, alice, bob, alice2bob, bob2alice)
173173
val (_, htlca3) = addHtlc(10000 msat, alice, bob, alice2bob, bob2alice)
174174
// for this one we set a non-local upstream to simulate a relayed payment
175-
val (_, htlca4) = addHtlc(30000000 msat, CltvExpiryDelta(144), alice, bob, alice2bob, bob2alice, upstream = Upstream.Hot.Trampoline(Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 42, 30003000 msat, randomBytes32(), CltvExpiry(144), TestConstants.emptyOnionPacket, TlvStream.empty[UpdateAddHtlcTlv]), TimestampMilli(1687345927000L)) :: Nil), replyTo = TestProbe().ref)
175+
val (_, htlca4) = addHtlc(30000000 msat, CltvExpiryDelta(144), alice, bob, alice2bob, bob2alice, upstream = Upstream.Hot.Trampoline(Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 42, 30003000 msat, randomBytes32(), CltvExpiry(144), TestConstants.emptyOnionPacket, TlvStream.empty[UpdateAddHtlcTlv]), TimestampMilli(1687345927000L), TestConstants.Alice.nodeParams.nodeId) :: Nil), replyTo = TestProbe().ref)
176176
val (rb1, htlcb1) = addHtlc(50000000 msat, bob, alice, bob2alice, alice2bob)
177177
val (_, _) = addHtlc(55000000 msat, bob, alice, bob2alice, alice2bob)
178178
crossSign(alice, bob, alice2bob, bob2alice)

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
311311
import f._
312312
val (preimage, add) = addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
313313
crossSign(bob, alice, bob2alice, alice2bob)
314-
alice2relayer.expectMsg(RelayForward(add))
314+
alice2relayer.expectMsg(RelayForward(add, TestConstants.Bob.nodeParams.nodeId))
315315
initiateQuiescence(f, sendInitialStfu = true)
316316
val forbiddenMsg = UpdateFulfillHtlc(channelId(bob), add.id, preimage)
317317
// both parties will respond to a forbidden msg while quiescent with a warning (and disconnect)

0 commit comments

Comments
 (0)