16
16
17
17
package fr .acinq .eclair .payment .relay
18
18
19
- import java .util .UUID
20
-
21
19
import akka .actor .ActorRef
22
20
import akka .actor .typed .Behavior
23
21
import akka .actor .typed .eventstream .EventStream
@@ -41,6 +39,7 @@ import fr.acinq.eclair.router.{BalanceTooLow, RouteCalculation, RouteNotFound}
41
39
import fr .acinq .eclair .wire ._
42
40
import fr .acinq .eclair .{CltvExpiry , Features , Logs , MilliSatoshi , NodeParams , nodeFee , randomBytes32 }
43
41
42
+ import java .util .UUID
44
43
import scala .collection .immutable .Queue
45
44
46
45
/**
@@ -52,6 +51,7 @@ object NodeRelay {
52
51
// @formatter:off
53
52
sealed trait Command
54
53
case class Relay (nodeRelayPacket : IncomingPacket .NodeRelayPacket ) extends Command
54
+ case object Stop extends Command
55
55
private case class WrappedMultiPartExtraPaymentReceived (mppExtraReceived : MultiPartPaymentFSM .ExtraPaymentReceived [HtlcPart ]) extends Command
56
56
private case class WrappedMultiPartPaymentFailed (mppFailed : MultiPartPaymentFSM .MultiPartPaymentFailed ) extends Command
57
57
private case class WrappedMultiPartPaymentSucceeded (mppSucceeded : MultiPartPaymentFSM .MultiPartPaymentSucceeded ) extends Command
@@ -60,13 +60,13 @@ object NodeRelay {
60
60
private case class WrappedPaymentFailed (paymentFailed : PaymentFailed ) extends Command
61
61
// @formatter:on
62
62
63
- def apply (nodeParams : NodeParams , router : ActorRef , register : ActorRef , relayId : UUID , paymentHash : ByteVector32 , fsmFactory : FsmFactory = new FsmFactory ): Behavior [Command ] =
63
+ def apply (nodeParams : NodeParams , parent : akka.actor.typed. ActorRef [ NodeRelayer . Command ], router : ActorRef , register : ActorRef , relayId : UUID , paymentHash : ByteVector32 , fsmFactory : FsmFactory = new FsmFactory ): Behavior [Command ] =
64
64
Behaviors .setup { context =>
65
65
Behaviors .withMdc(Logs .mdc(
66
66
category_opt = Some (Logs .LogCategory .PAYMENT ),
67
67
parentPaymentId_opt = Some (relayId), // for a node relay, we use the same identifier for the whole relay itself, and the outgoing payment
68
68
paymentHash_opt = Some (paymentHash))) {
69
- new NodeRelay (nodeParams, router, register, relayId, paymentHash, context, fsmFactory)()
69
+ new NodeRelay (nodeParams, parent, router, register, relayId, paymentHash, context, fsmFactory)()
70
70
}
71
71
}
72
72
@@ -136,6 +136,7 @@ object NodeRelay {
136
136
* see https://doc.akka.io/docs/akka/current/typed/style-guide.html#passing-around-too-many-parameters
137
137
*/
138
138
class NodeRelay private (nodeParams : NodeParams ,
139
+ parent : akka.actor.typed.ActorRef [NodeRelayer .Command ],
139
140
router : ActorRef ,
140
141
register : ActorRef ,
141
142
relayId : UUID ,
@@ -164,7 +165,7 @@ class NodeRelay private(nodeParams: NodeParams,
164
165
// TODO: @pm: maybe those checks should be done later in the flow (by the mpp FSM?)
165
166
context.log.warn(" rejecting htlcId={}: missing payment secret" , add.id)
166
167
rejectHtlc(add.id, add.channelId, add.amountMsat)
167
- Behaviors .stopped
168
+ stopping()
168
169
case Some (secret) =>
169
170
import akka .actor .typed .scaladsl .adapter ._
170
171
context.log.info(" relaying payment relayId={}" , relayId)
@@ -205,15 +206,15 @@ class NodeRelay private(nodeParams: NodeParams,
205
206
context.log.warn(" could not complete incoming multi-part payment (parts={} paidAmount={} failure={})" , parts.size, parts.map(_.amount).sum, failure)
206
207
Metrics .recordPaymentRelayFailed(failure.getClass.getSimpleName, Tags .RelayType .Trampoline )
207
208
parts.collect { case p : MultiPartPaymentFSM .HtlcPart => rejectHtlc(p.htlc.id, p.htlc.channelId, p.amount, Some (failure)) }
208
- Behaviors .stopped
209
+ stopping()
209
210
case WrappedMultiPartPaymentSucceeded (MultiPartPaymentFSM .MultiPartPaymentSucceeded (_, parts)) =>
210
211
context.log.info(" completed incoming multi-part payment with parts={} paidAmount={}" , parts.size, parts.map(_.amount).sum)
211
212
val upstream = Upstream .Trampoline (htlcs)
212
213
validateRelay(nodeParams, upstream, nextPayload) match {
213
214
case Some (failure) =>
214
215
context.log.warn(s " rejecting trampoline payment reason= $failure" )
215
216
rejectPayment(upstream, Some (failure))
216
- Behaviors .stopped
217
+ stopping()
217
218
case None =>
218
219
doSend(upstream, nextPayload, nextPacket)
219
220
}
@@ -249,16 +250,27 @@ class NodeRelay private(nodeParams: NodeParams,
249
250
case WrappedPaymentSent (paymentSent) =>
250
251
context.log.debug(" trampoline payment fully resolved downstream" )
251
252
success(upstream, fulfilledUpstream, paymentSent)
252
- Behaviors .stopped
253
- case WrappedPaymentFailed (PaymentFailed (id , _, failures, _)) =>
253
+ stopping()
254
+ case WrappedPaymentFailed (PaymentFailed (_ , _, failures, _)) =>
254
255
context.log.debug(s " trampoline payment failed downstream " )
255
256
if (! fulfilledUpstream) {
256
257
rejectPayment(upstream, translateError(nodeParams, failures, upstream, nextPayload))
257
258
}
258
- Behaviors .stopped
259
+ stopping()
259
260
}
260
261
}
261
262
263
+ /**
264
+ * Once the downstream payment is settled (fulfilled or failed), we reject new upstream payments while we wait for our parent to stop us.
265
+ */
266
+ private def stopping (): Behavior [Command ] = {
267
+ parent ! NodeRelayer .RelayComplete (context.self, paymentHash)
268
+ Behaviors .receiveMessagePartial {
269
+ rejectExtraHtlcPartialFunction orElse {
270
+ case Stop => Behaviors .stopped
271
+ }
272
+ }
273
+ }
262
274
263
275
private def relay (upstream : Upstream .Trampoline , payloadOut : Onion .NodeRelayPayload , packetOut : OnionRoutingPacket ): ActorRef = {
264
276
val paymentCfg = SendPaymentConfig (relayId, relayId, None , paymentHash, payloadOut.amountToForward, payloadOut.outgoingNodeId, upstream, None , storeInDb = false , publishEvent = false , Nil )
@@ -297,11 +309,10 @@ class NodeRelay private(nodeParams: NodeParams,
297
309
case Relay (nodeRelayPacket) =>
298
310
rejectExtraHtlc(nodeRelayPacket.add)
299
311
Behaviors .same
300
- // NB: this messages would be sent from the payment FSM which we stopped before going to this state, but all
301
- // this is asynchronous
312
+ // NB: this message would be sent from the payment FSM which we stopped before going to this state, but all this is asynchronous.
302
313
// We always fail extraneous HTLCs. They are a spec violation from the sender, but harmless in the relay case.
303
314
// By failing them fast (before the payment has reached the final recipient) there's a good chance the sender won't lose any money.
304
- // We don't expect to relay pay-to-open payments
315
+ // We don't expect to relay pay-to-open payments.
305
316
case WrappedMultiPartExtraPaymentReceived (extraPaymentReceived) =>
306
317
rejectExtraHtlc(extraPaymentReceived.payment.htlc)
307
318
Behaviors .same
0 commit comments