Skip to content

Commit 9973d1e

Browse files
committed
Reputation is recorded from channel events
1 parent da1b1a6 commit 9973d1e

File tree

13 files changed

+136
-111
lines changed

13 files changed

+136
-111
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ class Setup(val datadir: File,
364364
triggerer = system.spawn(Behaviors.supervise(AsyncPaymentTriggerer()).onFailure(typed.SupervisorStrategy.resume), name = "async-payment-triggerer")
365365
peerReadyManager = system.spawn(Behaviors.supervise(PeerReadyManager()).onFailure(typed.SupervisorStrategy.restart), name = "peer-ready-manager")
366366
reputationRecorder_opt = if (nodeParams.relayParams.peerReputationConfig.enabled) {
367-
Some(system.spawn(Behaviors.supervise(ReputationRecorder(nodeParams.relayParams.peerReputationConfig, Map.empty)).onFailure(typed.SupervisorStrategy.resume), name = "reputation-recorder"))
367+
Some(system.spawn(Behaviors.supervise(ReputationRecorder(nodeParams.relayParams.peerReputationConfig)).onFailure(typed.SupervisorStrategy.resume), name = "reputation-recorder"))
368368
} else {
369369
None
370370
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningS
2626
import fr.acinq.eclair.io.Peer
2727
import fr.acinq.eclair.transactions.CommitmentSpec
2828
import fr.acinq.eclair.transactions.Transactions._
29-
import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, CommitSig, FailureMessage, FundingCreated, FundingSigned, Init, LiquidityAds, OnionRoutingPacket, OpenChannel, OpenDualFundedChannel, Shutdown, SpliceInit, Stfu, TxSignatures, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc}
29+
import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, CommitSig, FailureMessage, FundingCreated, FundingSigned, HtlcFailureMessage, Init, LiquidityAds, OnionRoutingPacket, OpenChannel, OpenDualFundedChannel, Shutdown, SpliceInit, Stfu, TxSignatures, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc}
3030
import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, MilliSatoshiLong, RealShortChannelId, TimestampMilli, UInt64}
3131
import scodec.bits.ByteVector
3232

@@ -244,6 +244,10 @@ final case class CMD_GET_CHANNEL_STATE(replyTo: ActorRef) extends HasReplyToComm
244244
final case class CMD_GET_CHANNEL_DATA(replyTo: ActorRef) extends HasReplyToCommand
245245
final case class CMD_GET_CHANNEL_INFO(replyTo: akka.actor.typed.ActorRef[RES_GET_CHANNEL_INFO]) extends Command
246246

247+
case class OutgoingHtlcAdded(add: UpdateAddHtlc, upstream: Upstream.Hot, fee: MilliSatoshi)
248+
case class OutgoingHtlcFailed(fail: HtlcFailureMessage)
249+
case class OutgoingHtlcFulfilled(fulfill: UpdateFulfillHtlc)
250+
247251
/*
248252
88888888b. 8888888888 .d8888b. 88888888b. ,ad8888ba, 888b 88 .d8888b. 8888888888 .d8888b.
249253
88 "8b 88 d88P Y88b 88 "8b d8"' `"8b 8888b 88 d88P Y88b 88 d88P Y88b

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
440440
case Right((commitments1, add)) =>
441441
if (c.commit) self ! CMD_SIGN()
442442
context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1))
443+
context.system.eventStream.publish(OutgoingHtlcAdded(add, c.origin.upstream, nodeFee(d.channelUpdate.relayFees, add.amountMsat)))
443444
handleCommandSuccess(c, d.copy(commitments = commitments1)) sending add
444445
case Left(cause) => handleAddHtlcCommandError(c, cause, Some(d.channelUpdate))
445446
}
@@ -466,6 +467,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
466467
case Right((commitments1, origin, htlc)) =>
467468
// we forward preimages as soon as possible to the upstream channel because it allows us to pull funds
468469
relayer ! RES_ADD_SETTLED(origin, htlc, HtlcResult.RemoteFulfill(fulfill))
470+
context.system.eventStream.publish(OutgoingHtlcFulfilled(fulfill))
469471
stay() using d.copy(commitments = commitments1)
470472
case Left(cause) => handleLocalError(cause, d, Some(fulfill))
471473
}
@@ -499,12 +501,14 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
499501
}
500502

501503
case Event(fail: UpdateFailHtlc, d: DATA_NORMAL) =>
504+
context.system.eventStream.publish(OutgoingHtlcFailed(fail))
502505
d.commitments.receiveFail(fail) match {
503506
case Right((commitments1, _, _)) => stay() using d.copy(commitments = commitments1)
504507
case Left(cause) => handleLocalError(cause, d, Some(fail))
505508
}
506509

507510
case Event(fail: UpdateFailMalformedHtlc, d: DATA_NORMAL) =>
511+
context.system.eventStream.publish(OutgoingHtlcFailed(fail))
508512
d.commitments.receiveFailMalformed(fail) match {
509513
case Right((commitments1, _, _)) => stay() using d.copy(commitments = commitments1)
510514
case Left(cause) => handleLocalError(cause, d, Some(fail))

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

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import fr.acinq.eclair.payment.Monitoring.{Metrics, Tags}
3333
import fr.acinq.eclair.payment.relay.Relayer.{OutgoingChannel, OutgoingChannelParams}
3434
import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket}
3535
import fr.acinq.eclair.reputation.ReputationRecorder
36-
import fr.acinq.eclair.reputation.ReputationRecorder.{CancelRelay, GetConfidence, RecordResult}
36+
import fr.acinq.eclair.reputation.ReputationRecorder.GetConfidence
3737
import fr.acinq.eclair.wire.protocol.FailureMessageCodecs.createBadOnionFailure
3838
import fr.acinq.eclair.wire.protocol.PaymentOnion.IntermediatePayload
3939
import fr.acinq.eclair.wire.protocol._
@@ -65,7 +65,7 @@ object ChannelRelay {
6565

6666
def apply(nodeParams: NodeParams,
6767
register: ActorRef,
68-
reputationRecorder_opt: Option[typed.ActorRef[ReputationRecorder.ChannelRelayCommand]],
68+
reputationRecorder_opt: Option[typed.ActorRef[GetConfidence]],
6969
channels: Map[ByteVector32, Relayer.OutgoingChannel],
7070
originNode: PublicKey,
7171
relayId: UUID,
@@ -79,14 +79,14 @@ object ChannelRelay {
7979
val upstream = Upstream.Hot.Channel(r.add.removeUnknownTlvs(), TimestampMilli.now(), originNode)
8080
reputationRecorder_opt match {
8181
case Some(reputationRecorder) =>
82-
reputationRecorder ! GetConfidence(context.messageAdapter[ReputationRecorder.Confidence](confidence => WrappedConfidence(confidence.value)), originNode, r.add.endorsement, relayId, r.relayFeeMsat)
82+
reputationRecorder ! GetConfidence(context.messageAdapter[ReputationRecorder.Confidence](confidence => WrappedConfidence(confidence.value)), upstream, r.relayFeeMsat)
8383
case None =>
8484
val confidence = (r.add.endorsement + 0.5) / 8
8585
context.self ! WrappedConfidence(confidence)
8686
}
8787
Behaviors.receiveMessagePartial {
8888
case WrappedConfidence(confidence) =>
89-
new ChannelRelay(nodeParams, register, reputationRecorder_opt, channels, r, upstream, confidence, context, relayId).start()
89+
new ChannelRelay(nodeParams, register, channels, r, upstream, confidence, context).start()
9090
}
9191
}
9292
}
@@ -128,13 +128,11 @@ object ChannelRelay {
128128
*/
129129
class ChannelRelay private(nodeParams: NodeParams,
130130
register: ActorRef,
131-
reputationRecorder_opt: Option[typed.ActorRef[ReputationRecorder.ChannelRelayCommand]],
132131
channels: Map[ByteVector32, Relayer.OutgoingChannel],
133132
r: IncomingPaymentPacket.ChannelRelayPacket,
134133
upstream: Upstream.Hot.Channel,
135134
confidence: Double,
136-
context: ActorContext[ChannelRelay.Command],
137-
relayId: UUID) {
135+
context: ActorContext[ChannelRelay.Command]) {
138136

139137
import ChannelRelay._
140138

@@ -200,7 +198,6 @@ class ChannelRelay private(nodeParams: NodeParams,
200198
case RelayFailure(cmdFail) =>
201199
Metrics.recordPaymentRelayFailed(Tags.FailureType(cmdFail), Tags.RelayType.Channel)
202200
context.log.info("rejecting htlc reason={}", cmdFail.reason)
203-
reputationRecorder_opt.foreach(_ ! CancelRelay(upstream.receivedFrom, r.add.endorsement, relayId))
204201
safeSendAndStop(r.add.channelId, cmdFail)
205202
case RelayNeedsFunding(nextNodeId, cmdFail) =>
206203
val cmd = Peer.ProposeOnTheFlyFunding(onTheFlyFundingResponseAdapter, r.amountToForward, r.add.paymentHash, r.outgoingCltv, r.nextPacket, nextBlindingKey_opt, upstream)
@@ -220,7 +217,6 @@ class ChannelRelay private(nodeParams: NodeParams,
220217
context.log.warn(s"couldn't resolve downstream channel $channelId, failing htlc #${upstream.add.id}")
221218
val cmdFail = CMD_FAIL_HTLC(upstream.add.id, Right(UnknownNextPeer()), commit = true)
222219
Metrics.recordPaymentRelayFailed(Tags.FailureType(cmdFail), Tags.RelayType.Channel)
223-
reputationRecorder_opt.foreach(_ ! CancelRelay(upstream.receivedFrom, r.add.endorsement, relayId))
224220
safeSendAndStop(upstream.add.channelId, cmdFail)
225221

226222
case WrappedAddResponse(addFailed: RES_ADD_FAILED[_]) =>
@@ -431,11 +427,9 @@ class ChannelRelay private(nodeParams: NodeParams,
431427
featureOk && liquidityIssue && relayParamsOk
432428
}
433429

434-
private def recordRelayDuration(isSuccess: Boolean): Unit = {
435-
reputationRecorder_opt.foreach(_ ! RecordResult(upstream.receivedFrom, r.add.endorsement, relayId, isSuccess))
430+
private def recordRelayDuration(isSuccess: Boolean): Unit =
436431
Metrics.RelayedPaymentDuration
437432
.withTag(Tags.Relay, Tags.RelayType.Channel)
438433
.withTag(Tags.Success, isSuccess)
439434
.record((TimestampMilli.now() - upstream.receivedAt).toMillis, TimeUnit.MILLISECONDS)
440-
}
441435
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ object ChannelRelayer {
5959

6060
def apply(nodeParams: NodeParams,
6161
register: ActorRef,
62-
reputationRecorder_opt: Option[typed.ActorRef[ReputationRecorder.ChannelRelayCommand]],
62+
reputationRecorder_opt: Option[typed.ActorRef[ReputationRecorder.GetConfidence]],
6363
channels: Map[ByteVector32, Relayer.OutgoingChannel] = Map.empty,
6464
scid2channels: Map[ShortChannelId, ByteVector32] = Map.empty,
6565
node2channels: mutable.MultiDict[PublicKey, ByteVector32] = mutable.MultiDict.empty): Behavior[Command] =

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

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import fr.acinq.eclair.payment.send.PaymentLifecycle.SendPaymentToNode
4040
import fr.acinq.eclair.payment.send._
4141
import fr.acinq.eclair.router.Router.{ChannelHop, HopRelayParams, Route, RouteParams}
4242
import fr.acinq.eclair.reputation.ReputationRecorder
43+
import fr.acinq.eclair.reputation.ReputationRecorder.GetTrampolineConfidence
4344
import fr.acinq.eclair.router.Router.RouteParams
4445
import fr.acinq.eclair.router.{BalanceTooLow, RouteNotFound}
4546
import fr.acinq.eclair.wire.protocol.PaymentOnion.IntermediatePayload
@@ -91,7 +92,7 @@ object NodeRelay {
9192
def apply(nodeParams: NodeParams,
9293
parent: typed.ActorRef[NodeRelayer.Command],
9394
register: ActorRef,
94-
reputationRecorder_opt: Option[typed.ActorRef[ReputationRecorder.TrampolineRelayCommand]],
95+
reputationRecorder_opt: Option[typed.ActorRef[GetTrampolineConfidence]],
9596
relayId: UUID,
9697
nodeRelayPacket: NodeRelayPacket,
9798
outgoingPaymentFactory: OutgoingPaymentFactory,
@@ -215,7 +216,7 @@ object NodeRelay {
215216
class NodeRelay private(nodeParams: NodeParams,
216217
parent: akka.actor.typed.ActorRef[NodeRelayer.Command],
217218
register: ActorRef,
218-
reputationRecorder_opt: Option[typed.ActorRef[ReputationRecorder.TrampolineRelayCommand]],
219+
reputationRecorder_opt: Option[typed.ActorRef[GetTrampolineConfidence]],
219220
relayId: UUID,
220221
paymentHash: ByteVector32,
221222
paymentSecret: ByteVector32,
@@ -336,11 +337,8 @@ class NodeRelay private(nodeParams: NodeParams,
336337
// We only make one try when it's a direct payment to a wallet.
337338
val maxPaymentAttempts = if (walletNodeId_opt.isDefined) 1 else nodeParams.maxPaymentAttempts
338339
val totalFee = upstream.amountIn - payloadOut.amountToForward
339-
val fees = upstream.received.foldLeft(Map.empty[ReputationRecorder.PeerEndorsement, MilliSatoshi])((fees, r) =>
340-
fees.updatedWith(ReputationRecorder.PeerEndorsement(r.receivedFrom, r.add.endorsement))(fee =>
341-
Some(fee.getOrElse(MilliSatoshi(0)) + r.add.amountMsat * totalFee.toLong / upstream.amountIn.toLong)))
342340
reputationRecorder_opt match {
343-
case Some(reputationRecorder) => reputationRecorder ! ReputationRecorder.GetTrampolineConfidence(context.messageAdapter[ReputationRecorder.Confidence](confidence => WrappedConfidence(confidence.value)), fees, relayId)
341+
case Some(reputationRecorder) => reputationRecorder ! GetTrampolineConfidence(context.messageAdapter(confidence => WrappedConfidence(confidence.value)), upstream, totalFee)
344342
case None => context.self ! WrappedConfidence((upstream.received.map(_.add.endorsement).min + 0.5) / 8)
345343
}
346344
Behaviors.receiveMessagePartial {
@@ -396,16 +394,10 @@ class NodeRelay private(nodeParams: NodeParams,
396394
case WrappedPaymentSent(paymentSent) =>
397395
context.log.debug("trampoline payment fully resolved downstream")
398396
success(upstream, fulfilledUpstream, paymentSent)
399-
val totalFee = upstream.amountIn - paymentSent.amountWithFees
400-
val fees = upstream.received.foldLeft(Map.empty[ReputationRecorder.PeerEndorsement, MilliSatoshi])((fees, r) =>
401-
fees.updatedWith(ReputationRecorder.PeerEndorsement(r.receivedFrom, r.add.endorsement))(fee =>
402-
Some(fee.getOrElse(MilliSatoshi(0)) + r.add.amountMsat * totalFee.toLong / upstream.amountIn.toLong)))
403-
reputationRecorder_opt.foreach(_ ! ReputationRecorder.RecordTrampolineSuccess(fees, relayId))
404397
recordRelayDuration(startedAt, isSuccess = true)
405398
stopping()
406399
case _: WrappedPaymentFailed if fulfilledUpstream =>
407400
context.log.warn("trampoline payment failed downstream but was fulfilled upstream")
408-
reputationRecorder_opt.foreach(_ ! ReputationRecorder.RecordTrampolineFailure(upstream.received.map(r => ReputationRecorder.PeerEndorsement(r.receivedFrom, r.add.endorsement)).toSet, relayId))
409401
recordRelayDuration(startedAt, isSuccess = true)
410402
stopping()
411403
case WrappedPaymentFailed(PaymentFailed(_, _, failures, _)) =>
@@ -415,7 +407,6 @@ class NodeRelay private(nodeParams: NodeParams,
415407
attemptOnTheFlyFunding(upstream, walletNodeId, recipient, nextPayload, failures, startedAt)
416408
case _ =>
417409
rejectPayment(upstream, translateError(nodeParams, failures, upstream, nextPayload))
418-
reputationRecorder_opt.foreach(_ ! ReputationRecorder.RecordTrampolineFailure(upstream.received.map(r => ReputationRecorder.PeerEndorsement(r.receivedFrom, r.add.endorsement)).toSet, relayId))
419410
recordRelayDuration(startedAt, isSuccess = false)
420411
stopping()
421412
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ object NodeRelayer {
6060
*/
6161
def apply(nodeParams: NodeParams,
6262
register: akka.actor.ActorRef,
63-
reputationRecorder_opt: Option[ActorRef[ReputationRecorder.TrampolineRelayCommand]],
63+
reputationRecorder_opt: Option[ActorRef[ReputationRecorder.GetTrampolineConfidence]],
6464
outgoingPaymentFactory: NodeRelay.OutgoingPaymentFactory,
6565
router: akka.actor.ActorRef,
6666
children: Map[PaymentKey, ActorRef[NodeRelay.Command]] = Map.empty): Behavior[Command] =

eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@
1616

1717
package fr.acinq.eclair.reputation
1818

19+
import fr.acinq.bitcoin.scalacompat.ByteVector32
20+
import fr.acinq.eclair.reputation.Reputation.HtlcId
1921
import fr.acinq.eclair.{MilliSatoshi, TimestampMilli}
2022

21-
import java.util.UUID
2223
import scala.concurrent.duration.FiniteDuration
2324

2425
/**
2526
* Created by thomash on 21/07/2023.
2627
*/
2728

2829
/**
29-
* Local reputation for a given incoming node, that should be track for each incoming endorsement level.
30+
* Local reputation for a given incoming node, that should be tracked for each incoming endorsement level.
3031
*
3132
* @param pastWeight How much fees we would have collected in the past if all payments had succeeded (exponential moving average).
3233
* @param pastScore How much fees we have collected in the past (exponential moving average).
@@ -36,51 +37,47 @@ import scala.concurrent.duration.FiniteDuration
3637
* @param maxRelayDuration Duration after which payments are penalized for staying pending too long.
3738
* @param pendingMultiplier How much to penalize pending payments.
3839
*/
39-
case class Reputation(pastWeight: Double, pastScore: Double, lastSettlementAt: TimestampMilli, pending: Map[UUID, Reputation.PendingPayment], halfLife: FiniteDuration, maxRelayDuration: FiniteDuration, pendingMultiplier: Double) {
40+
case class Reputation(pastWeight: Double, pastScore: Double, lastSettlementAt: TimestampMilli, pending: Map[HtlcId, Reputation.PendingPayment], halfLife: FiniteDuration, maxRelayDuration: FiniteDuration, pendingMultiplier: Double) {
4041
private def decay(now: TimestampMilli): Double = scala.math.pow(0.5, (now - lastSettlementAt) / halfLife)
4142

4243
private def pendingWeight(now: TimestampMilli): Double = pending.values.map(_.weight(now, maxRelayDuration, pendingMultiplier)).sum
4344

4445
/**
45-
* Register a payment to relay and estimate the confidence that it will succeed.
46-
*
47-
* @return (updated reputation, confidence)
46+
* Estimate the confidence that a payment will succeed.
4847
*/
49-
def attempt(relayId: UUID, fee: MilliSatoshi, now: TimestampMilli = TimestampMilli.now()): (Reputation, Double) = {
48+
def getConfidence(fee: MilliSatoshi, now: TimestampMilli = TimestampMilli.now()): Double = {
5049
val d = decay(now)
51-
val newReputation = copy(pending = pending + (relayId -> Reputation.PendingPayment(fee, now)))
52-
val confidence = d * pastScore / (d * pastWeight + newReputation.pendingWeight(now))
53-
(newReputation, confidence)
50+
d * pastScore / (d * pastWeight + pendingWeight(now) + fee.toLong.toDouble * pendingMultiplier)
5451
}
5552

5653
/**
57-
* Mark a previously registered payment as failed without trying to relay it (usually because its confidence was too low).
54+
* Register a pending relay.
5855
*
5956
* @return updated reputation
6057
*/
61-
def cancel(relayId: UUID): Reputation = copy(pending = pending - relayId)
58+
def attempt(htlcId: HtlcId, fee: MilliSatoshi, now: TimestampMilli = TimestampMilli.now()): Reputation =
59+
copy(pending = pending + (htlcId -> Reputation.PendingPayment(fee, now)))
6260

6361
/**
6462
* When a payment is settled, we record whether it succeeded and how long it took.
6563
*
66-
* @param feeOverride When relaying trampoline payments, the actual fee is only known when the payment succeeds. This
67-
* is used instead of the fee upper bound that was known when first attempting the relay.
6864
* @return updated reputation
6965
*/
70-
def record(relayId: UUID, isSuccess: Boolean, feeOverride: Option[MilliSatoshi] = None, now: TimestampMilli = TimestampMilli.now()): Reputation = {
71-
pending.get(relayId) match {
66+
def record(htlcId: HtlcId, isSuccess: Boolean, now: TimestampMilli = TimestampMilli.now()): Reputation = {
67+
pending.get(htlcId) match {
7268
case Some(p) =>
7369
val d = decay(now)
74-
val p1 = p.copy(fee = feeOverride.getOrElse(p.fee))
75-
val newWeight = d * pastWeight + p1.weight(now, maxRelayDuration, 1.0)
76-
val newScore = d * pastScore + (if (isSuccess) p1.fee.toLong.toDouble else 0)
77-
Reputation(newWeight, newScore, now, pending - relayId, halfLife, maxRelayDuration, pendingMultiplier)
70+
val newWeight = d * pastWeight + p.weight(now, maxRelayDuration, if (isSuccess) 1.0 else 0.0)
71+
val newScore = d * pastScore + (if (isSuccess) p.fee.toLong.toDouble else 0)
72+
Reputation(newWeight, newScore, now, pending - htlcId, halfLife, maxRelayDuration, pendingMultiplier)
7873
case None => this
7974
}
8075
}
8176
}
8277

8378
object Reputation {
79+
case class HtlcId(channelId: ByteVector32, id: Long)
80+
8481
/** We're relaying that payment and are waiting for it to settle. */
8582
case class PendingPayment(fee: MilliSatoshi, startedAt: TimestampMilli) {
8683
def weight(now: TimestampMilli, minDuration: FiniteDuration, multiplier: Double): Double = {

0 commit comments

Comments
 (0)