Skip to content

Commit 9b1d42f

Browse files
committed
Read and write endorsement
1 parent 83d790e commit 9b1d42f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+394
-368
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ sealed trait HasOptionalReplyToCommand extends Command { def replyTo_opt: Option
196196
sealed trait ForbiddenCommandDuringSplice extends Command
197197
sealed trait ForbiddenCommandDuringQuiescence extends Command
198198

199-
final case class CMD_ADD_HTLC(replyTo: ActorRef, amount: MilliSatoshi, paymentHash: ByteVector32, cltvExpiry: CltvExpiry, onion: OnionRoutingPacket, nextBlindingKey_opt: Option[PublicKey], origin: Origin.Hot, commit: Boolean = false) extends HasReplyToCommand with ForbiddenCommandDuringSplice with ForbiddenCommandDuringQuiescence
199+
final case class CMD_ADD_HTLC(replyTo: ActorRef, amount: MilliSatoshi, paymentHash: ByteVector32, cltvExpiry: CltvExpiry, onion: OnionRoutingPacket, nextBlindingKey_opt: Option[PublicKey], confidence: Double, origin: Origin.Hot, commit: Boolean = false) extends HasReplyToCommand with ForbiddenCommandDuringSplice with ForbiddenCommandDuringQuiescence
200200
sealed trait HtlcSettlementCommand extends HasOptionalReplyToCommand with ForbiddenCommandDuringSplice with ForbiddenCommandDuringQuiescence { def id: Long }
201201
final case class CMD_FULFILL_HTLC(id: Long, r: ByteVector32, commit: Boolean = false, replyTo_opt: Option[ActorRef] = None) extends HtlcSettlementCommand
202202
final case class CMD_FAIL_HTLC(id: Long, reason: Either[ByteVector, FailureMessage], delay_opt: Option[FiniteDuration] = None, commit: Boolean = false, replyTo_opt: Option[ActorRef] = None) extends HtlcSettlementCommand

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ case class Commitments(params: ChannelParams,
854854
return Left(HtlcValueTooSmall(params.channelId, minimum = htlcMinimum, actual = cmd.amount))
855855
}
856856

857-
val add = UpdateAddHtlc(channelId, changes.localNextHtlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextBlindingKey_opt)
857+
val add = UpdateAddHtlc(channelId, changes.localNextHtlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextBlindingKey_opt, cmd.confidence)
858858
// we increment the local htlc index and add an entry to the origins map
859859
val changes1 = changes.addLocalProposal(add).copy(localNextHtlcId = changes.localNextHtlcId + 1)
860860
val originChannels1 = originChannels + (add.id -> cmd.origin)

eclair-core/src/main/scala/fr/acinq/eclair/payment/Monitoring.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ object Monitoring {
6767
PaymentNodeOutAmount.withoutTags().record(bucket, amount.truncateToSatoshi.toLong)
6868
PaymentNodeOut.withoutTags().record(bucket)
6969
}
70+
71+
private val RelayConfidence = Kamon.histogram("payment.relay.confidence", "Confidence (in percent) that the relayed HTLC will be fulfilled")
72+
def relaySettleFulfill(confidence: Double) = RelayConfidence.withTag("status", "fulfill").record((confidence * 100).toLong)
73+
def relaySettleFail(confidence: Double) = RelayConfidence.withTag("status", "fail").record((confidence * 100).toLong)
7074
}
7175

7276
object Tags {

eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentPacket.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,12 +285,12 @@ object OutgoingPaymentPacket {
285285
}
286286

287287
/** Build the command to add an HTLC for the given recipient using the provided route. */
288-
def buildOutgoingPayment(origin: Origin.Hot, paymentHash: ByteVector32, route: Route, recipient: Recipient): Either[OutgoingPaymentError, OutgoingPaymentPacket] = {
288+
def buildOutgoingPayment(origin: Origin.Hot, paymentHash: ByteVector32, route: Route, recipient: Recipient, confidence: Double): Either[OutgoingPaymentError, OutgoingPaymentPacket] = {
289289
for {
290290
payment <- recipient.buildPayloads(paymentHash, route)
291291
onion <- buildOnion(payment.payloads, paymentHash, Some(PaymentOnionCodecs.paymentOnionPayloadLength)) // BOLT 2 requires that associatedData == paymentHash
292292
} yield {
293-
val cmd = CMD_ADD_HTLC(origin.replyTo, payment.amount, paymentHash, payment.expiry, onion.packet, payment.outerBlinding_opt, origin, commit = true)
293+
val cmd = CMD_ADD_HTLC(origin.replyTo, payment.amount, paymentHash, payment.expiry, onion.packet, payment.outerBlinding_opt, confidence, origin, commit = true)
294294
OutgoingPaymentPacket(cmd, route.hops.head.shortChannelId, onion.sharedSecrets)
295295
}
296296
}

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

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
package fr.acinq.eclair.payment.relay
1818

19-
import akka.actor.ActorRef
2019
import akka.actor.typed.Behavior
2120
import akka.actor.typed.eventstream.EventStream
2221
import akka.actor.typed.scaladsl.adapter.TypedActorRefOps
2322
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
23+
import akka.actor.{ActorRef, typed}
2424
import fr.acinq.bitcoin.scalacompat.ByteVector32
2525
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2626
import fr.acinq.eclair.channel._
@@ -43,7 +43,7 @@ object ChannelRelay {
4343

4444
// @formatter:off
4545
sealed trait Command
46-
private case object DoRelay extends Command
46+
private case class WrappedConfidence(confidence: Double) extends Command
4747
private case class WrappedForwardFailure(failure: Register.ForwardFailure[CMD_ADD_HTLC]) extends Command
4848
private case class WrappedAddResponse(res: CommandResponse[CMD_ADD_HTLC]) extends Command
4949
// @formatter:on
@@ -54,15 +54,20 @@ object ChannelRelay {
5454
case class RelaySuccess(selectedChannelId: ByteVector32, cmdAdd: CMD_ADD_HTLC) extends RelayResult
5555
// @formatter:on
5656

57-
def apply(nodeParams: NodeParams, register: ActorRef, channels: Map[ByteVector32, Relayer.OutgoingChannel], originNode: PublicKey, relayId: UUID, r: IncomingPaymentPacket.ChannelRelayPacket): Behavior[Command] =
57+
def apply(nodeParams: NodeParams,
58+
register: ActorRef,
59+
channels: Map[ByteVector32, Relayer.OutgoingChannel],
60+
originNode:PublicKey,
61+
relayId: UUID,
62+
r: IncomingPaymentPacket.ChannelRelayPacket): Behavior[Command] =
5863
Behaviors.setup { context =>
5964
Behaviors.withMdc(Logs.mdc(
6065
category_opt = Some(Logs.LogCategory.PAYMENT),
6166
parentPaymentId_opt = Some(relayId), // for a channel relay, parent payment id = relay id
6267
paymentHash_opt = Some(r.add.paymentHash),
6368
nodeAlias_opt = Some(nodeParams.alias))) {
6469
val upstream = Upstream.Hot.Channel(r.add.removeUnknownTlvs(), TimestampMilli.now(), originNode)
65-
context.self ! DoRelay
70+
context.self ! WrappedConfidence(0.5)
6671
new ChannelRelay(nodeParams, register, channels, r, upstream, context).relay(Seq.empty)
6772
}
6873
}
@@ -118,60 +123,63 @@ class ChannelRelay private(nodeParams: NodeParams,
118123

119124
def relay(previousFailures: Seq[PreviouslyTried]): Behavior[Command] = {
120125
Behaviors.receiveMessagePartial {
121-
case DoRelay =>
126+
case WrappedConfidence(confidence) =>
122127
if (previousFailures.isEmpty) {
123128
context.log.info("relaying htlc #{} from channelId={} to requestedShortChannelId={} nextNode={}", r.add.id, r.add.channelId, r.payload.outgoingChannelId, nextNodeId_opt.getOrElse(""))
124129
}
125130
context.log.debug("attempting relay previousAttempts={}", previousFailures.size)
126-
handleRelay(previousFailures) match {
131+
handleRelay(previousFailures, confidence) match {
127132
case RelayFailure(cmdFail) =>
128133
Metrics.recordPaymentRelayFailed(Tags.FailureType(cmdFail), Tags.RelayType.Channel)
129134
context.log.info("rejecting htlc reason={}", cmdFail.reason)
130-
safeSendAndStop(r.add.channelId, cmdFail)
135+
safeSendAndStop(r.add.channelId, cmdFail, confidence, None)
131136
case RelaySuccess(selectedChannelId, cmdAdd) =>
132137
context.log.info("forwarding htlc #{} from channelId={} to channelId={}", r.add.id, r.add.channelId, selectedChannelId)
133138
register ! Register.Forward(forwardFailureAdapter, selectedChannelId, cmdAdd)
134-
waitForAddResponse(selectedChannelId, previousFailures)
139+
waitForAddResponse(selectedChannelId, previousFailures, confidence)
135140
}
136141
}
137142
}
138143

139-
def waitForAddResponse(selectedChannelId: ByteVector32, previousFailures: Seq[PreviouslyTried]): Behavior[Command] =
144+
def waitForAddResponse(selectedChannelId: ByteVector32, previousFailures: Seq[PreviouslyTried], confidence: Double): Behavior[Command] =
140145
Behaviors.receiveMessagePartial {
141146
case WrappedForwardFailure(Register.ForwardFailure(Register.Forward(_, channelId, _))) =>
142147
context.log.warn(s"couldn't resolve downstream channel $channelId, failing htlc #${upstream.add.id}")
143148
val cmdFail = CMD_FAIL_HTLC(upstream.add.id, Right(UnknownNextPeer()), commit = true)
144149
Metrics.recordPaymentRelayFailed(Tags.FailureType(cmdFail), Tags.RelayType.Channel)
145-
safeSendAndStop(upstream.add.channelId, cmdFail)
150+
safeSendAndStop(upstream.add.channelId, cmdFail, confidence, Some(channelId))
146151

147152
case WrappedAddResponse(addFailed: RES_ADD_FAILED[_]) =>
148153
context.log.info("attempt failed with reason={}", addFailed.t.getClass.getSimpleName)
149-
context.self ! DoRelay
154+
context.self ! WrappedConfidence(confidence)
150155
relay(previousFailures :+ PreviouslyTried(selectedChannelId, addFailed))
151156

152-
case WrappedAddResponse(_: RES_SUCCESS[_]) =>
157+
case WrappedAddResponse(r: RES_SUCCESS[_]) =>
153158
context.log.debug("sent htlc to the downstream channel")
154-
waitForAddSettled()
159+
waitForAddSettled(confidence, r.channelId)
155160
}
156161

157-
def waitForAddSettled(): Behavior[Command] =
162+
def waitForAddSettled(confidence: Double, channelId: ByteVector32): Behavior[Command] =
158163
Behaviors.receiveMessagePartial {
159164
case WrappedAddResponse(RES_ADD_SETTLED(_, htlc, fulfill: HtlcResult.Fulfill)) =>
160165
context.log.debug("relaying fulfill to upstream")
166+
Metrics.relaySettleFulfill(confidence)
161167
val cmd = CMD_FULFILL_HTLC(upstream.add.id, fulfill.paymentPreimage, commit = true)
162168
context.system.eventStream ! EventStream.Publish(ChannelPaymentRelayed(upstream.amountIn, htlc.amountMsat, htlc.paymentHash, upstream.add.channelId, htlc.channelId, upstream.receivedAt, TimestampMilli.now()))
163169
recordRelayDuration(isSuccess = true)
164-
safeSendAndStop(upstream.add.channelId, cmd)
170+
safeSendAndStop(upstream.add.channelId, cmd, confidence, Some(channelId))
165171

166172
case WrappedAddResponse(RES_ADD_SETTLED(_, _, fail: HtlcResult.Fail)) =>
167173
context.log.debug("relaying fail to upstream")
174+
Metrics.relaySettleFail(confidence)
168175
Metrics.recordPaymentRelayFailed(Tags.FailureType.Remote, Tags.RelayType.Channel)
169176
val cmd = translateRelayFailure(upstream.add.id, fail)
170177
recordRelayDuration(isSuccess = false)
171-
safeSendAndStop(upstream.add.channelId, cmd)
178+
safeSendAndStop(upstream.add.channelId, cmd, confidence, Some(channelId))
172179
}
173180

174-
def safeSendAndStop(channelId: ByteVector32, cmd: channel.HtlcSettlementCommand): Behavior[Command] = {
181+
def safeSendAndStop(channelId: ByteVector32, cmd: channel.HtlcSettlementCommand, confidence: Double, outgoingChannel_opt: Option[ByteVector32]): Behavior[Command] = {
182+
context.log.info("cmd={}, startedAt={}, endedAt={}, confidence={}, originNode={}, outgoingChannel={}", cmd.getClass.getSimpleName, upstream.receivedAt, TimestampMilli.now(), confidence, upstream.receivedFrom, outgoingChannel_opt)
175183
val toSend = cmd match {
176184
case _: CMD_FULFILL_HTLC => cmd
177185
case _: CMD_FAIL_HTLC | _: CMD_FAIL_MALFORMED_HTLC => r.payload match {
@@ -202,9 +210,9 @@ class ChannelRelay private(nodeParams: NodeParams,
202210
* - a CMD_FAIL_HTLC to be sent back upstream
203211
* - a CMD_ADD_HTLC to propagate downstream
204212
*/
205-
def handleRelay(previousFailures: Seq[PreviouslyTried]): RelayResult = {
213+
def handleRelay(previousFailures: Seq[PreviouslyTried], confidence: Double): RelayResult = {
206214
val alreadyTried = previousFailures.map(_.channelId)
207-
selectPreferredChannel(alreadyTried) match {
215+
selectPreferredChannel(alreadyTried, confidence) match {
208216
case None if previousFailures.nonEmpty =>
209217
// no more channels to try
210218
val error = previousFailures
@@ -215,7 +223,7 @@ class ChannelRelay private(nodeParams: NodeParams,
215223
.failure
216224
RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(translateLocalError(error.t, error.channelUpdate)), commit = true))
217225
case outgoingChannel_opt =>
218-
relayOrFail(outgoingChannel_opt)
226+
relayOrFail(outgoingChannel_opt, confidence)
219227
}
220228
}
221229

@@ -234,7 +242,7 @@ class ChannelRelay private(nodeParams: NodeParams,
234242
*
235243
* If no suitable channel is found we default to the originally requested channel.
236244
*/
237-
def selectPreferredChannel(alreadyTried: Seq[ByteVector32]): Option[OutgoingChannel] = {
245+
def selectPreferredChannel(alreadyTried: Seq[ByteVector32], confidence: Double): Option[OutgoingChannel] = {
238246
val requestedShortChannelId = r.payload.outgoingChannelId
239247
context.log.debug("selecting next channel with requestedShortChannelId={}", requestedShortChannelId)
240248
// we filter out channels that we have already tried
@@ -243,7 +251,7 @@ class ChannelRelay private(nodeParams: NodeParams,
243251
candidateChannels
244252
.values
245253
.map { channel =>
246-
val relayResult = relayOrFail(Some(channel))
254+
val relayResult = relayOrFail(Some(channel), confidence)
247255
context.log.debug(s"candidate channel: channelId=${channel.channelId} availableForSend={} capacity={} channelUpdate={} result={}",
248256
channel.commitments.availableBalanceForSend,
249257
channel.commitments.latest.capacity,
@@ -291,7 +299,7 @@ class ChannelRelay private(nodeParams: NodeParams,
291299
* channel, because some parameters don't match with our settings for that channel. In that case we directly fail the
292300
* htlc.
293301
*/
294-
def relayOrFail(outgoingChannel_opt: Option[OutgoingChannelParams]): RelayResult = {
302+
def relayOrFail(outgoingChannel_opt: Option[OutgoingChannelParams], confidence: Double): RelayResult = {
295303
outgoingChannel_opt match {
296304
case None =>
297305
RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true))
@@ -312,7 +320,7 @@ class ChannelRelay private(nodeParams: NodeParams,
312320
case payload: IntermediatePayload.ChannelRelay.Blinded => Some(payload.nextBlinding)
313321
case _: IntermediatePayload.ChannelRelay.Standard => None
314322
}
315-
RelaySuccess(c.channelId, CMD_ADD_HTLC(addResponseAdapter.toClassic, r.amountToForward, r.add.paymentHash, r.outgoingCltv, r.nextPacket, nextBlindingKey_opt, origin, commit = true))
323+
RelaySuccess(c.channelId, CMD_ADD_HTLC(addResponseAdapter.toClassic, r.amountToForward, r.add.paymentHash, r.outgoingCltv, r.nextPacket, nextBlindingKey_opt, confidence, origin, commit = true))
316324
}
317325
}
318326

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ object NodeRelay {
6464
private case class WrappedPaymentFailed(paymentFailed: PaymentFailed) extends Command
6565
private[relay] case class WrappedPeerReadyResult(result: AsyncPaymentTriggerer.Result) extends Command
6666
private case class WrappedResolvedPaths(resolved: Seq[ResolvedPath]) extends Command
67+
private case class WrappedConfidence(confidence: Double) extends Command
6768
// @formatter:on
6869

6970
trait OutgoingPaymentFactory {
@@ -259,7 +260,7 @@ class NodeRelay private(nodeParams: NodeParams,
259260

260261
private def doSend(upstream: Upstream.Hot.Trampoline, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket]): Behavior[Command] = {
261262
context.log.debug(s"relaying trampoline payment (amountIn=${upstream.amountIn} expiryIn=${upstream.expiryIn} amountOut=${nextPayload.amountToForward} expiryOut=${nextPayload.outgoingCltv})")
262-
relay(upstream, nextPayload, nextPacket_opt)
263+
relay(upstream, nextPayload, nextPacket_opt, confidence = 0.5)
263264
}
264265

265266
/**
@@ -316,12 +317,12 @@ class NodeRelay private(nodeParams: NodeParams,
316317
context.messageAdapter[PaymentFailed](WrappedPaymentFailed)
317318
}.toClassic
318319

319-
private def relay(upstream: Upstream.Hot.Trampoline, payloadOut: IntermediatePayload.NodeRelay, packetOut_opt: Option[OnionRoutingPacket]): Behavior[Command] = {
320+
private def relay(upstream: Upstream.Hot.Trampoline, payloadOut: IntermediatePayload.NodeRelay, packetOut_opt: Option[OnionRoutingPacket], confidence: Double): Behavior[Command] = {
320321
val displayNodeId = payloadOut match {
321322
case payloadOut: IntermediatePayload.NodeRelay.Standard => payloadOut.outgoingNodeId
322323
case _: IntermediatePayload.NodeRelay.ToBlindedPaths => randomKey().publicKey
323324
}
324-
val paymentCfg = SendPaymentConfig(relayId, relayId, None, paymentHash, displayNodeId, upstream, None, None, storeInDb = false, publishEvent = false, recordPathFindingMetrics = true)
325+
val paymentCfg = SendPaymentConfig(relayId, relayId, None, paymentHash, displayNodeId, upstream, None, None, storeInDb = false, publishEvent = false, recordPathFindingMetrics = true, confidence)
325326
val routeParams = computeRouteParams(nodeParams, upstream.amountIn, upstream.expiryIn, payloadOut.amountToForward, payloadOut.outgoingCltv)
326327
payloadOut match {
327328
case payloadOut: IntermediatePayload.NodeRelay.Standard =>

0 commit comments

Comments
 (0)