What changed, and why it matters
This commit replaces an older 'endorsement' anti-jamming signal with a new 'accountable' signal for Lightning HTLCs, following a draft specification update. It changes how invoices, payment onions, and channel messages carry and propagate this signal. Importantly, the release notes state the protections are not yet enforced: the node only collects data and logs what it would have rejected. The change is a protocol update and data-collection feature, not an active enforcement that directly blocks payments today.
Review the new TLV type numbers and codec choices for compatibility with the draft BOLTs PR 1280 and other implementations. Confirm that the disabled-enforcement shadow mode is intentional and that metrics/logging do not leak sensitive routing data. Before enabling enforcement, validate the reputation algorithm and reserved bucket thresholds with collected data. No immediate patch is required because active protections are not enforced.
Security signals we found
Protocol-level anti-jamming signal redesign (endorsement -> accountable)
New TLVs added to HTLC, payment onion, route blinding, and invoice codecs
Reputation algorithm simplified to binary accountability and outgoing-only scoring
Active jamming rejection is explicitly disabled; only data collection and logging
IncomingConfidenceTooLow exception removed; OutgoingConfidenceTooLow retained but not enforced in relay path
Non-regression test data updated to treat old Endorsement TLV as unknown
Evidence from the diff
The commit implements ‘Accountable HTLCs’ per BOLTs PR 1280. It removes the 8-level Endorsement TLV (type 106823) from update_add_htlc and replaces it with a binary Accountable TLV (type 1). It adds an UpgradeAccountability TLV to payment onion payloads and route blinding encrypted data, and an InvoiceAccountable TLV to Bolt 12 invoices, plus an ‘accountable’ bit in Bolt 11 invoices via tag 31. Reputation scoring is simplified to binary accountability and outgoing-reputation only; incoming reputation tracking is removed. Channel jamming checks are disabled in production: the code logs and records metrics for payments that would be rejected, but still relays them. Configuration adds relay.reserved-for-accountable and peer-reputation.enabled.
Changed components
eclair-core wire protocol codecs (UpdateAddHtlc, PaymentOnion, RouteBlinding, OfferCodecs)Invoice handling (Bolt11Invoice, Bolt12Invoice, MinimalBolt12Invoice)Payment relay (ChannelRelay, NodeRelay, Relayer, OnTheFlyFunding)Payment sending (PaymentInitiator, PaymentLifecycle, TrampolinePaymentLifecycle, Recipient)Reputation system (Reputation, ReputationRecorder)Channel commitments and channel FSMConfiguration (reference.conf, NodeParams, TestConstants)Inspect captured patch +827 / −914
diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md
index 062aeb0..d1bd1d0 100644
--- a/docs/release-notes/eclair-vnext.md
+++ b/docs/release-notes/eclair-vnext.md
@@ -9,6 +9,19 @@
We remove the code used to support legacy channels that don't use anchor outputs or taproot.
If you still have such channels, eclair won't start: you will need to close those channels, and will only be able to update eclair once they have been successfully closed.
+### Channel jamming accountability
+
+We update our channel jamming mitigation to match the latest draft of the [spec](https://github.com/lightning/bolts/pull/1280).
+Note that we use a different architecture for channel bucketing and confidence scoring than what is described in the BOLTs.
+We don't yet fail HTLCs that don't meet these restrictions: we're only collecting data so far to evaluate how the algorithm performs.
+
+If you want to disable this feature entirely, you can set the following values in `eclair.conf`:
+
+```conf
+eclair.relay.peer-reputation.enabled = false
+eclair.relay.reserved-for-accountable = 0.0
+```
+
### Configuration changes
<insert changes>
diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf
index 5391654..8ce57a4 100644
--- a/eclair-core/src/main/resources/reference.conf
+++ b/eclair-core/src/main/resources/reference.conf
@@ -255,14 +255,15 @@ eclair {
// We assign reputation to our peers to prioritize payments during congestion.
// The reputation is computed as fees paid divided by what should have been paid if all payments were successful.
peer-reputation {
- // Set this parameter to false to disable the reputation algorithm and simply relay the incoming endorsement
- // value, as described by https://github.com/lightning/blips/blob/master/blip-0004.md,
+ // Set this parameter to false to disable the reputation algorithm and simply relay the incoming accountability.
enabled = true
// Reputation decays with the following half life to emphasize recent behavior.
half-life = 30 days
// Payments that stay pending for longer than this get penalized.
max-relay-duration = 5 minutes
}
+
+ reserved-for-accountable = 0.5 // Fraction of a channel slots and usable liquidity that is reserved for accountable HTLCs
}
on-chain-fees {
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala
index c3ce569..9b95375 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala
@@ -474,7 +474,7 @@ class EclairImpl(val appKit: Kit) extends Eclair with Logging with SpendFromChan
override def findRouteBetween(sourceNodeId: PublicKey, targetNodeId: PublicKey, amount: MilliSatoshi, pathFindingExperimentName_opt: Option[String], extraEdges: Seq[Invoice.ExtraEdge] = Seq.empty, includeLocalChannelCost: Boolean = false, ignoreNodeIds: Seq[PublicKey] = Seq.empty, ignoreShortChannelIds: Seq[ShortChannelId] = Seq.empty, maxFee_opt: Option[MilliSatoshi] = None, maxCltvExpiryDelta_opt: Option[CltvExpiryDelta] = None)(implicit timeout: Timeout): Future[RouteResponse] = {
getRouteParams(pathFindingExperimentName_opt) match {
case Right(routeParams) =>
- val target = ClearRecipient(targetNodeId, Features.empty, amount, CltvExpiry(appKit.nodeParams.currentBlockHeight), ByteVector32.Zeroes, extraEdges)
+ val target = ClearRecipient(targetNodeId, Features.empty, amount, CltvExpiry(appKit.nodeParams.currentBlockHeight), ByteVector32.Zeroes, upgradeAccountability = true, extraEdges)
val routeParams1 = routeParams.copy(
includeLocalChannelCost = includeLocalChannelCost,
boundaries = routeParams.boundaries.copy(
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
index 2bee26c..be25030 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
@@ -645,6 +645,7 @@ object NodeParams extends Logging {
halfLife = FiniteDuration(config.getDuration("relay.peer-reputation.half-life").getSeconds, TimeUnit.SECONDS),
maxRelayDuration = FiniteDuration(config.getDuration("relay.peer-reputation.max-relay-duration").getSeconds, TimeUnit.SECONDS),
),
+ reservedBucket = config.getDouble("relay.reserved-for-accountable"),
),
db = database,
autoReconnect = config.getBoolean("auto-reconnect"),
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala
index 81772a8..8a3b7bd 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala
@@ -160,11 +160,12 @@ object Upstream {
override val amountIn: MilliSatoshi = add.amountMsat
val expiryIn: CltvExpiry = add.cltvExpiry
- override def toString: String = s"Channel(amountIn=$amountIn, receivedAt=${receivedAt.toLong}, receivedFrom=${receivedFrom.toHex}, endorsement=${add.endorsement}, incomingChannelOccupancy=$incomingChannelOccupancy)"
+ override def toString: String = s"Channel(amountIn=$amountIn, receivedAt=${receivedAt.toLong}, receivedFrom=${receivedFrom.toHex}, accountable=${add.accountable}, incomingChannelOccupancy=$incomingChannelOccupancy)"
}
/** Our node is forwarding a payment based on a set of HTLCs from potentially multiple upstream channels. */
case class Trampoline(received: List[Channel]) extends Hot {
override val amountIn: MilliSatoshi = received.map(_.add.amountMsat).sum
+ val accountable: Boolean = received.exists(_.add.accountable)
// We must use the lowest expiry of the incoming HTLC set.
val expiryIn: CltvExpiry = received.map(_.add.cltvExpiry).min
val receivedAt: TimestampMilli = received.map(_.receivedAt).max
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala
index 9421fe5..806b583 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala
@@ -105,7 +105,7 @@ case class LocalCommitConfirmed(channel: ActorRef, remoteNodeId: PublicKey, chan
case class ChannelClosed(channel: ActorRef, channelId: ByteVector32, closingType: ClosingType, commitments: Commitments) extends ChannelEvent
-case class OutgoingHtlcAdded(add: UpdateAddHtlc, remoteNodeId: PublicKey, upstream: Upstream.Hot, fee: MilliSatoshi)
+case class OutgoingHtlcAdded(add: UpdateAddHtlc, remoteNodeId: PublicKey, fee: MilliSatoshi)
sealed trait OutgoingHtlcSettled
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala
index 884ed42..0e644cd 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala
@@ -152,7 +152,6 @@ case class ForbiddenDuringSplice (override val channelId: Byte
case class ForbiddenDuringQuiescence (override val channelId: ByteVector32, command: String) extends ChannelException(channelId, s"cannot process $command while quiescent")
case class ConcurrentRemoteSplice (override val channelId: ByteVector32) extends ChannelException(channelId, "splice attempt canceled, remote initiated splice before us")
case class TooManySmallHtlcs (override val channelId: ByteVector32, number: Long, below: MilliSatoshi) extends ChannelJammingException(channelId, s"too many small htlcs: $number HTLCs below $below")
-case class IncomingConfidenceTooLow (override val channelId: ByteVector32, confidence: Double, occupancy: Double) extends ChannelJammingException(channelId, s"incoming confidence too low: confidence=$confidence occupancy=$occupancy")
case class OutgoingConfidenceTooLow (override val channelId: ByteVector32, confidence: Double, occupancy: Double) extends ChannelJammingException(channelId, s"outgoing confidence too low: confidence=$confidence occupancy=$occupancy")
case class MissingCommitNonce (override val channelId: ByteVector32, fundingTxId: TxId, commitmentNumber: Long) extends ChannelException(channelId, s"commit nonce for funding tx $fundingTxId and commitmentNumber=$commitmentNumber is missing")
case class InvalidCommitNonce (override val channelId: ByteVector32, fundingTxId: TxId, commitmentNumber: Long) extends ChannelException(channelId, s"commit nonce for funding tx $fundingTxId and commitmentNumber=$commitmentNumber is not valid")
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
index 42b9a65..383c031 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
@@ -523,9 +523,7 @@ case class Commitment(fundingTxIndex: Long,
return Left(RemoteDustHtlcExposureTooHigh(params.channelId, maxDustExposure, remoteDustExposureAfterAdd))
}
- // Jamming protection
- // Must be the last checks so that they can be ignored for shadow deployment.
- reputationScore.checkOutgoingChannelOccupancy(params.channelId, this, outgoingHtlcs.toSeq)
+ Right(())
}
def canReceiveAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges): Either[ChannelException, Unit] = {
@@ -898,7 +896,7 @@ case class Commitments(channelParams: ChannelParams,
return Left(HtlcValueTooSmall(channelId, minimum = htlcMinimum, actual = cmd.amount))
}
- val add = UpdateAddHtlc(channelId, changes.localNextHtlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextPathKey_opt, cmd.reputationScore.endorsement, cmd.fundingFee_opt)
+ val add = UpdateAddHtlc(channelId, changes.localNextHtlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextPathKey_opt, cmd.reputationScore.accountable, cmd.fundingFee_opt)
// we increment the local htlc index and add an entry to the origins map
val changes1 = changes.addLocalProposal(add).copy(localNextHtlcId = changes.localNextHtlcId + 1)
val originChannels1 = originChannels + (add.id -> cmd.origin)
@@ -915,7 +913,6 @@ case class Commitments(channelParams: ChannelParams,
Metrics.dropHtlc(failure, Tags.Directions.Outgoing)
failure match {
case f: TooManySmallHtlcs => log.info("TooManySmallHtlcs: {} outgoing HTLCs are below {}", f.number, f.below)
- case f: IncomingConfidenceTooLow => log.info("IncomingConfidenceTooLow: confidence is {}% while channel is {}% full", (100 * f.confidence).toInt, (100 * f.occupancy).toInt)
case f: OutgoingConfidenceTooLow => log.info("OutgoingConfidenceTooLow: confidence is {}% while channel is {}% full", (100 * f.confidence).toInt, (100 * f.occupancy).toInt)
case _ => ()
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
index 4c82bac..b845af3 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
@@ -535,8 +535,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
if (c.commit) self ! CMD_SIGN()
context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.aliases, commitments1, d.lastAnnouncement_opt))
val relayFee = nodeFee(d.channelUpdate.relayFees, add.amountMsat)
- context.system.eventStream.publish(OutgoingHtlcAdded(add, remoteNodeId, c.origin.upstream, relayFee))
- log.info("OutgoingHtlcAdded: channelId={}, id={}, endorsement={}, remoteNodeId={}, upstream={}, fee={}, now={}, blockHeight={}, expiry={}", Array(add.channelId.toHex, add.id, add.endorsement, remoteNodeId.toHex, c.origin.upstream.toString, relayFee, TimestampMilli.now().toLong, nodeParams.currentBlockHeight.toLong, add.cltvExpiry))
+ context.system.eventStream.publish(OutgoingHtlcAdded(add, remoteNodeId, relayFee))
+ log.info("OutgoingHtlcAdded: channelId={}, id={}, accountable={}, remoteNodeId={}, upstream={}, fee={}, now={}, blockHeight={}, expiry={}", Array(add.channelId.toHex, add.id, add.accountable, remoteNodeId.toHex, c.origin.upstream.toString, relayFee, TimestampMilli.now().toLong, nodeParams.currentBlockHeight.toLong, add.cltvExpiry))
handleCommandSuccess(c, d.copy(commitments = commitments1)) sending add
case Left(cause) => handleAddHtlcCommandError(c, cause, Some(d.channelUpdate))
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Bolt11Invoice.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Bolt11Invoice.scala
index d779b6d..d439135 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Bolt11Invoice.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Bolt11Invoice.scala
@@ -73,6 +73,8 @@ case class Bolt11Invoice(prefix: String, amount_opt: Option[MilliSatoshi], creat
override lazy val features: Features[InvoiceFeature] = tags.collectFirst { case f: InvoiceFeatures => f.features.invoiceFeatures() }.getOrElse(Features.empty)
+ override lazy val accountable: Boolean = tags.collectFirst { case a: Accountable => a }.nonEmpty
+
/**
* @return the hash of this payment invoice
*/
@@ -148,6 +150,7 @@ object Bolt11Invoice {
fallbackAddress.map(FallbackAddress(_)),
expirySeconds.map(Expiry(_)),
Some(MinFinalCltvExpiry(minFinalCltvExpiryDelta.toInt)),
+ Some(Accountable()),
// We want to keep invoices as small as possible, so we explicitly remove unknown features.
Some(InvoiceFeatures(features.copy(unknown = Set.empty).unscoped()))
).flatten
@@ -197,7 +200,7 @@ object Bolt11Invoice {
case class UnknownTag28(data: BitVector) extends UnknownTaggedField
case class UnknownTag29(data: BitVector) extends UnknownTaggedField
case class UnknownTag30(data: BitVector) extends UnknownTaggedField
- case class UnknownTag31(data: BitVector) extends UnknownTaggedField
+ case class InvalidTag31(data: BitVector) extends InvalidTaggedField
// @formatter:on
/**
@@ -283,6 +286,11 @@ object Bolt11Invoice {
}
}
+ /**
+ * Present if the recipient is willing to be held accountable for the timely resolution of HTLCs.
+ */
+ case class Accountable() extends TaggedField
+
/**
* This returns a bitvector with the minimum size necessary to encode the long, left padded to have a length (in bits)
* that is a multiple of 5.
@@ -439,7 +447,10 @@ object Bolt11Invoice {
.typecase(28, dataCodec(bits).as[UnknownTag28])
.typecase(29, dataCodec(bits).as[UnknownTag29])
.typecase(30, dataCodec(bits).as[UnknownTag30])
- .typecase(31, dataCodec(bits).as[UnknownTag31])
+ .\(31) {
+ case _: Accountable => Accountable()
+ case a: InvalidTag31 => a: TaggedField
+ }(choice(dataCodec(provide(Accountable()), expectedLength = Some(0)).upcast[TaggedField], dataCodec(bits).as[InvalidTag31].upcast[TaggedField]))
private def fixedSizeTrailingCodec[A](codec: Codec[A], size: Int): Codec[A] = Codec[A](
(data: A) => codec.encode(data),
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Bolt12Invoice.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Bolt12Invoice.scala
index ae65046..48e0ea6 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Bolt12Invoice.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Bolt12Invoice.scala
@@ -51,6 +51,7 @@ case class Bolt12Invoice(records: TlvStream[InvoiceTlv]) extends Invoice {
val blindedPaths: Seq[PaymentBlindedRoute] = records.get[InvoicePaths].get.paths.zip(records.get[InvoiceBlindedPay].get.paymentInfo).map { case (route, info) => PaymentBlindedRoute(route, info) }
val fallbacks: Option[Seq[FallbackAddress]] = records.get[InvoiceFallbacks].map(_.addresses)
val signature: ByteVector64 = records.get[Signature].get.signature
+ override val accountable: Boolean = records.get[InvoiceAccountable].nonEmpty
// It is assumed that the request is valid for this offer.
def validateFor(request: InvoiceRequest, pathNodeId: PublicKey): Either[String, Unit] = {
@@ -109,6 +110,7 @@ object Bolt12Invoice {
val amount = request.amount
val tlvs: Set[InvoiceTlv] = removeSignature(request.records).records ++ Set(
Some(InvoicePaths(paths.map(_.route))),
+ Some(InvoiceAccountable()),
Some(InvoiceBlindedPay(paths.map(_.paymentInfo))),
Some(InvoiceCreatedAt(TimestampSecond.now())),
Some(InvoiceRelativeExpiry(invoiceExpiry.toSeconds)),
@@ -168,6 +170,7 @@ case class MinimalBolt12Invoice(records: TlvStream[InvoiceTlv]) extends Invoice
override val createdAt: TimestampSecond = records.get[InvoiceCreatedAt].get.timestamp
override val relativeExpiry: FiniteDuration = FiniteDuration(records.get[InvoiceRelativeExpiry].map(_.seconds).getOrElse(Bolt12Invoice.DEFAULT_EXPIRY_SECONDS), TimeUnit.SECONDS)
override val features: Features[InvoiceFeature] = records.get[InvoiceFeatures].map(f => Features(f.features).invoiceFeatures()).getOrElse(Features[InvoiceFeature](Features.BasicMultiPartPayment -> FeatureSupport.Optional))
+ override val accountable: Boolean = true
override def toString: String = {
val data = OfferCodecs.invoiceTlvCodec.encode(records).require.bytes
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Invoice.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Invoice.scala
index b6f4e66..7fa4511 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Invoice.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Invoice.scala
@@ -35,6 +35,7 @@ trait Invoice {
def features: Features[InvoiceFeature]
def isExpired(now: TimestampSecond = TimestampSecond.now()): Boolean = createdAt + relativeExpiry.toSeconds <= now
def toString: String
+ def accountable: Boolean
// @formatter:on
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Monitoring.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Monitoring.scala
index 6aa201a..3ffa7e0 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Monitoring.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Monitoring.scala
@@ -126,6 +126,7 @@ object Monitoring {
val WakeUp = "WakeUp"
val Remote = "Remote"
val Malformed = "MalformedHtlc"
+ val Jamming = "Jamming"
def apply(cmdFail: CMD_FAIL_HTLC): String = cmdFail.reason match {
case _: FailureReason.EncryptedDownstreamFailure => Remote
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentPacket.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentPacket.scala
index 9a77355..04d99fc 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentPacket.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentPacket.scala
@@ -23,7 +23,7 @@ import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.payment.send.Recipient
import fr.acinq.eclair.reputation.Reputation
import fr.acinq.eclair.router.Router.Route
-import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv.{InvoiceRoutingInfo, OutgoingBlindedPaths}
+import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv.{InvoiceRoutingInfo, OutgoingBlindedPaths, UpgradeAccountability}
import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload, PerHopPayload}
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, Features, MilliSatoshi, ShortChannelId, TimestampMilli, UInt64, randomBytes32, randomKey}
@@ -144,6 +144,7 @@ object IncomingPaymentPacket {
}
}
case None if add.pathKey_opt.isDefined => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
+ case None if add.accountable && payload.get[UpgradeAccountability].isEmpty => Left(InvalidOnionPayload(UInt64(19), 0))
case None =>
// We are not inside a blinded path: channel relay information is directly available.
IntermediatePayload.ChannelRelay.Standard.validate(payload).left.map(_.failureMessage).map(payload => ChannelRelayPacket(add, payload, nextPacket, TimestampMilli.now()))
@@ -160,6 +161,7 @@ object IncomingPaymentPacket {
case DecodedEncryptedRecipientData(blindedPayload, _) => validateBlindedFinalPayload(add, payload, blindedPayload)
}
case None if add.pathKey_opt.isDefined => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
+ case None if add.accountable && payload.get[UpgradeAccountability].isEmpty => Left(InvalidOnionPayload(UInt64(19), 0))
case None =>
// We check if the payment is using trampoline: if it is, we may not be the final recipient.
payload.get[OnionPaymentPayloadTlv.TrampolineOnion] match {
@@ -219,6 +221,7 @@ object IncomingPaymentPacket {
case payload if add.amountMsat < payload.paymentRelayData.paymentConstraints.minAmount => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
case payload if add.cltvExpiry > payload.paymentRelayData.paymentConstraints.maxCltvExpiry => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
case payload if !Features.areCompatible(Features.empty, payload.paymentRelayData.allowedFeatures) => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
+ case _ if add.accountable && blindedPayload.get[RouteBlindingEncryptedDataTlv.UpgradeAccountability].isEmpty => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
case payload => Right(ChannelRelayPacket(add, payload, nextPacket, TimestampMilli.now()))
}
}
@@ -237,6 +240,7 @@ object IncomingPaymentPacket {
case payload if payload.paymentConstraints_opt.exists(c => c.maxCltvExpiry < add.cltvExpiry) => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
case payload if !Features.areCompatible(Features.empty, payload.allowedFeatures) => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
case payload if add.cltvExpiry < payload.expiry => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
+ case _ if add.accountable && blindedPayload.get[RouteBlindingEncryptedDataTlv.UpgradeAccountability].isEmpty => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)))
case payload => Right(FinalPacket(add, payload, TimestampMilli.now()))
}
}
@@ -253,7 +257,7 @@ object IncomingPaymentPacket {
// We merge contents from the outer and inner payloads.
// We must use the inner payload's total amount and payment secret because the payment may be split between multiple trampoline payments (#reckless).
val trampolinePacket = outerPayload.records.get[OnionPaymentPayloadTlv.TrampolineOnion].map(_.packet)
- Right(FinalPacket(add, FinalPayload.Standard.createPayload(outerPayload.amount, innerPayload.totalAmount, innerPayload.expiry, innerPayload.paymentSecret, innerPayload.paymentMetadata, trampolinePacket), TimestampMilli.now()))
+ Right(FinalPacket(add, FinalPayload.Standard.createPayload(outerPayload.amount, innerPayload.totalAmount, innerPayload.expiry, innerPayload.paymentSecret, innerPayload.paymentMetadata, trampolinePacket, upgradeAccountability = outerPayload.upgradeAccountability), TimestampMilli.now()))
}
}
}
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 d4c1ae4..bdae4a2 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
@@ -16,11 +16,11 @@
package fr.acinq.eclair.payment.relay
-import akka.actor.{ActorRef, typed}
import akka.actor.typed.Behavior
import akka.actor.typed.eventstream.EventStream
import akka.actor.typed.scaladsl.adapter.TypedActorRefOps
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
+import akka.actor.{ActorRef, typed}
import fr.acinq.bitcoin.scalacompat.ByteVector32
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.eclair.channel._
@@ -77,11 +77,22 @@ object ChannelRelay {
paymentHash_opt = Some(r.add.paymentHash),
nodeAlias_opt = Some(nodeParams.alias))) {
val upstream = Upstream.Hot.Channel(r.add.removeUnknownTlvs(), r.receivedAt, originNode, incomingChannelOccupancy)
- reputationRecorder_opt match {
- case Some(reputationRecorder) =>
- reputationRecorder ! GetConfidence(context.messageAdapter(WrappedReputationScore(_)), upstream, channels.values.headOption.map(_.nextNodeId), r.relayFeeMsat, nodeParams.currentBlockHeight, r.outgoingCltv)
- case None =>
- context.self ! WrappedReputationScore(Reputation.Score.fromEndorsement(r.add.endorsement))
+ val accountable0 = r.add.accountable || nodeParams.relayParams.incomingChannelCongested(upstream.incomingChannelOccupancy)
+ val accountable = if (accountable0 && !r.payload.upgradeAccountability) {
+ // We don't yet enforce channel jamming protections: we log and update metrics as if we had failed that payment,
+ // but we currently relay it anyway. This will let us analyze data before actually activating jamming protection.
+ Metrics.recordPaymentRelayFailed(Tags.FailureType.Jamming, Tags.RelayType.Channel)
+ context.log.info("payment would have been rejected if jamming protection was activated")
+ false
+ } else {
+ accountable0
+ }
+ val nextNodeId_opt = channels.values.headOption.map(_.nextNodeId)
+ (reputationRecorder_opt, nextNodeId_opt) match {
+ case (Some(reputationRecorder), Some(nextNodeId)) =>
+ reputationRecorder ! GetConfidence(context.messageAdapter(WrappedReputationScore(_)), nextNodeId, r.relayFeeMsat, nodeParams.currentBlockHeight, r.outgoingCltv, accountable)
+ case _ =>
+ context.self ! WrappedReputationScore(Reputation.Score.max(accountable))
}
Behaviors.receiveMessagePartial {
case WrappedReputationScore(score) =>
@@ -234,8 +245,8 @@ class ChannelRelay private(nodeParams: NodeParams,
private def waitForAddSettled(): Behavior[Command] =
Behaviors.receiveMessagePartial {
case WrappedAddResponse(RES_ADD_SETTLED(_, htlc, fulfill: HtlcResult.Fulfill)) =>
- context.log.info("relaying fulfill to upstream, receivedAt={}, endedAt={}, confidence={}, originNode={}, outgoingChannel={}", upstream.receivedAt, r.receivedAt, reputationScore.incomingConfidence, upstream.receivedFrom, htlc.channelId)
- Metrics.relayFulfill(reputationScore.incomingConfidence)
+ context.log.info("relaying fulfill to upstream, receivedAt={}, endedAt={}, confidence={}, originNode={}, outgoingChannel={}", upstream.receivedAt, r.receivedAt, reputationScore.outgoingConfidence, upstream.receivedFrom, htlc.channelId)
+ Metrics.relayFulfill(reputationScore.outgoingConfidence)
val downstreamAttribution_opt = fulfill match {
case HtlcResult.RemoteFulfill(fulfill) => fulfill.attribution_opt
case HtlcResult.OnChainFulfill(_) => None
@@ -247,8 +258,8 @@ class ChannelRelay private(nodeParams: NodeParams,
safeSendAndStop(upstream.add.channelId, cmd)
case WrappedAddResponse(RES_ADD_SETTLED(_, htlc, fail: HtlcResult.Fail)) =>
- context.log.info("relaying fail to upstream, receivedAt={}, endedAt={}, confidence={}, originNode={}, outgoingChannel={}", upstream.receivedAt, r.receivedAt, reputationScore.incomingConfidence, upstream.receivedFrom, htlc.channelId)
- Metrics.relayFail(reputationScore.incomingConfidence)
+ context.log.info("relaying fail to upstream, receivedAt={}, endedAt={}, confidence={}, originNode={}, outgoingChannel={}", upstream.receivedAt, r.receivedAt, reputationScore.outgoingConfidence, upstream.receivedFrom, htlc.channelId)
+ Metrics.relayFail(reputationScore.outgoingConfidence)
Metrics.recordPaymentRelayFailed(Tags.FailureType.Remote, Tags.RelayType.Channel)
val cmd = translateRelayFailure(upstream.add.id, fail, Some(upstream.receivedAt))
recordRelayDuration(isSuccess = false)
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 07fb03c..3b8c451 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
@@ -115,7 +115,7 @@ object NodeRelay {
case _: IncomingPaymentPacket.RelayToBlindedPathsPacket => None
}
new NodeRelay(nodeParams, parent, register, relayId, paymentHash, nodeRelayPacket.outerPayload.paymentSecret, context, outgoingPaymentFactory, router)
- .receiving(Queue.empty, nodeRelayPacket.innerPayload, nextPacket_opt, incomingPaymentHandler)
+ .receiving(Queue.empty, nodeRelayPacket.innerPayload, nextPacket_opt, incomingPaymentHandler, upgradeAccountability = true)
}
}
@@ -221,13 +221,13 @@ class NodeRelay private(nodeParams: NodeParams,
* @param nextPacket_opt trampoline onion to relay to the next trampoline node.
* @param handler actor handling the aggregation of the incoming HTLC set.
*/
- private def receiving(htlcs: Queue[Upstream.Hot.Channel], nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket], handler: ActorRef): Behavior[Command] =
+ private def receiving(htlcs: Queue[Upstream.Hot.Channel], nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket], handler: ActorRef, upgradeAccountability: Boolean): Behavior[Command] =
Behaviors.receiveMessagePartial {
case Relay(packet: IncomingPaymentPacket.NodeRelayPacket, originNode, incomingChannelOccupancy) =>
require(packet.outerPayload.paymentSecret == paymentSecret, "payment secret mismatch")
context.log.debug("forwarding incoming htlc #{} from channel {} to the payment FSM", packet.add.id, packet.add.channelId)
handler ! MultiPartPaymentFSM.HtlcPart(packet.outerPayload.totalAmount, packet.add, packet.receivedAt)
- receiving(htlcs :+ Upstream.Hot.Channel(packet.add.removeUnknownTlvs(), packet.receivedAt, originNode, incomingChannelOccupancy), nextPayload, nextPacket_opt, handler)
+ receiving(htlcs :+ Upstream.Hot.Channel(packet.add.removeUnknownTlvs(), packet.receivedAt, originNode, incomingChannelOccupancy), nextPayload, nextPacket_opt, handler, upgradeAccountability && packet.innerPayload.upgradeAccountability)
case WrappedMultiPartPaymentFailed(MultiPartPaymentFSM.MultiPartPaymentFailed(_, failure, parts)) =>
context.log.warn("could not complete incoming multi-part payment (parts={} paidAmount={} failure={})", parts.size, parts.map(_.amount).sum, failure)
Metrics.recordPaymentRelayFailed(failure.getClass.getSimpleName, Tags.RelayType.Trampoline)
@@ -242,25 +242,25 @@ class NodeRelay private(nodeParams: NodeParams,
rejectPayment(upstream, Some(failure))
stopping()
case None =>
- resolveNextNode(upstream, nextPayload, nextPacket_opt)
+ resolveNextNode(upstream, nextPayload, nextPacket_opt, upgradeAccountability)
}
}
/** Once we've fully received the incoming HTLC set, we must identify the next node before forwarding the payment. */
- private def resolveNextNode(upstream: Upstream.Hot.Trampoline, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket]): Behavior[Command] = {
+ private def resolveNextNode(upstream: Upstream.Hot.Trampoline, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket], upgradeAccountability: Boolean): Behavior[Command] = {
nextPayload match {
case payloadOut: IntermediatePayload.NodeRelay.Standard =>
val paymentSecret = randomBytes32() // we generate a new secret to protect against probing attacks
- val recipient = ClearRecipient(payloadOut.outgoingNodeId, Features.empty, payloadOut.amountToForward, payloadOut.outgoingCltv, paymentSecret, nextTrampolineOnion_opt = nextPacket_opt)
+ val recipient = ClearRecipient(payloadOut.outgoingNodeId, Features.empty, payloadOut.amountToForward, payloadOut.outgoingCltv, paymentSecret, nextTrampolineOnion_opt = nextPacket_opt, upgradeAccountability = payloadOut.upgradeAccountability)
context.log.debug("forwarding payment to the next trampoline node {}", recipient.nodeId)
- attemptWakeUpIfRecipientIsWallet(upstream, recipient, nextPayload, nextPacket_opt)
+ attemptWakeUpIfRecipientIsWallet(upstream, recipient, nextPayload, nextPacket_opt, upgradeAccountability)
case payloadOut: IntermediatePayload.NodeRelay.ToNonTrampoline =>
val paymentSecret = payloadOut.paymentSecret
val features = Features(payloadOut.invoiceFeatures).invoiceFeatures()
val extraEdges = payloadOut.invoiceRoutingInfo.flatMap(Bolt11Invoice.toExtraEdges(_, payloadOut.outgoingNodeId))
- val recipient = ClearRecipient(payloadOut.outgoingNodeId, features, payloadOut.amountToForward, payloadOut.outgoingCltv, paymentSecret, extraEdges, payloadOut.paymentMetadata)
+ val recipient = ClearRecipient(payloadOut.outgoingNodeId, features, payloadOut.amountToForward, payloadOut.outgoingCltv, paymentSecret, payloadOut.upgradeAccountability, extraEdges, payloadOut.paymentMetadata)
context.log.debug("forwarding payment to non-trampoline recipient {}", recipient.nodeId)
- attemptWakeUpIfRecipientIsWallet(upstream, recipient, nextPayload, None)
+ attemptWakeUpIfRecipientIsWallet(upstream, recipient, nextPayload, None, upgradeAccountability)
case payloadOut: IntermediatePayload.NodeRelay.ToBlindedPaths =>
// Blinded paths in Bolt 12 invoices may encode the introduction node with an scid and a direction: we need to
// resolve that to a nodeId in order to reach that introduction node and use the blinded path.
@@ -275,14 +275,14 @@ class NodeRelay private(nodeParams: NodeParams,
case WrappedResolvedPaths(resolved) =>
// We don't have access to the invoice: we use the only node_id that somewhat makes sense for the recipient.
val blindedNodeId = resolved.head.route.blindedNodeIds.last
- val recipient = BlindedRecipient.fromPaths(blindedNodeId, Features(payloadOut.invoiceFeatures).invoiceFeatures(), payloadOut.amountToForward, payloadOut.outgoingCltv, resolved, Set.empty)
+ val recipient = BlindedRecipient.fromPaths(blindedNodeId, Features(payloadOut.invoiceFeatures).invoiceFeatures(), payloadOut.amountToForward, payloadOut.outgoingCltv, resolved, payloadOut.upgradeAccountability, Set.empty)
resolved.head.route match {
case BlindedPathsResolver.PartialBlindedRoute(walletNodeId: EncodedNodeId.WithPublicKey.Wallet, _, _) if nodeParams.peerWakeUpConfig.enabled =>
context.log.debug("forwarding payment to blinded peer {}", walletNodeId.publicKey)
- attemptWakeUp(upstream, walletNodeId.publicKey, recipient, nextPayload, nextPacket_opt)
+ attemptWakeUp(upstream, walletNodeId.publicKey, recipient, nextPayload, nextPacket_opt, upgradeAccountability)
case _ =>
context.log.debug("forwarding payment to blinded recipient {}", recipient.nodeId)
- relay(upstream, recipient, None, None, nextPayload, nextPacket_opt)
+ relay(upstream, recipient, None, None, nextPayload, nextPacket_opt, upgradeAccountability)
}
}
}
@@ -290,7 +290,7 @@ class NodeRelay private(nodeParams: NodeParams,
}
/** The next node may be a mobile wallet directly connected to us: in that case, we'll need to wake them up before relaying the payment. */
- private def attemptWakeUpIfRecipientIsWallet(upstream: Upstream.Hot.Trampoline, recipient: Recipient, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket]): Behavior[Command] = {
+ private def attemptWakeUpIfRecipientIsWallet(upstream: Upstream.Hot.Trampoline, recipient: Recipient, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket], upgradeAccountability: Boolean): Behavior[Command] = {
val forwardNodeIdFailureAdapter = context.messageAdapter[Register.ForwardNodeIdFailure[Peer.GetPeerInfo]](_ => WrappedPeerInfo(remoteFeatures_opt = None))
val peerInfoAdapter = context.messageAdapter[Peer.PeerInfoResponse] {
case _: Peer.PeerNotFound => WrappedPeerInfo(remoteFeatures_opt = None)
@@ -306,8 +306,8 @@ class NodeRelay private(nodeParams: NodeParams,
None
}
walletNodeId_opt match {
- case Some(walletNodeId) if nodeParams.peerWakeUpConfig.enabled => attemptWakeUp(upstream, walletNodeId, recipient, nextPayload, nextPacket_opt)
- case _ => relay(upstream, recipient, walletNodeId_opt, None, nextPayload, nextPacket_opt)
+ case Some(walletNodeId) if nodeParams.peerWakeUpConfig.enabled => attemptWakeUp(upstream, walletNodeId, recipient, nextPayload, nextPacket_opt, upgradeAccountability)
+ case _ => relay(upstream, recipient, walletNodeId_opt, None, nextPayload, nextPacket_opt, upgradeAccountability)
}
}
}
@@ -317,7 +317,7 @@ class NodeRelay private(nodeParams: NodeParams,
* The next node is the payment recipient. They are directly connected to us and may be offline. We try to wake them
* up and will relay the payment once they're connected and channels are reestablished.
*/
- private def attemptWakeUp(upstream: Upstream.Hot.Trampoline, walletNodeId: PublicKey, recipient: Recipient, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket]): Behavior[Command] = {
+ private def attemptWakeUp(upstream: Upstream.Hot.Trampoline, walletNodeId: PublicKey, recipient: Recipient, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket], upgradeAccountability: Boolean): Behavior[Command] = {
context.log.info("trying to wake up next peer (nodeId={})", walletNodeId)
val notifier = context.spawnAnonymous(PeerReadyNotifier(walletNodeId, timeout_opt = Some(Left(nodeParams.peerWakeUpConfig.timeout))))
notifier ! PeerReadyNotifier.NotifyWhenPeerReady(context.messageAdapter(WrappedPeerReadyResult))
@@ -328,19 +328,28 @@ class NodeRelay private(nodeParams: NodeParams,
rejectPayment(upstream, Some(UnknownNextPeer()))
stopping()
case WrappedPeerReadyResult(r: PeerReadyNotifier.PeerReady) =>
- relay(upstream, recipient, Some(walletNodeId), Some(r.remoteFeatures), nextPayload, nextPacket_opt)
+ relay(upstream, recipient, Some(walletNodeId), Some(r.remoteFeatures), nextPayload, nextPacket_opt, upgradeAccountability)
}
}
}
/** 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, walletNodeId_opt: Option[PublicKey], recipientFeatures_opt: Option[Features[InitFeature]], payloadOut: IntermediatePayload.NodeRelay, packetOut_opt: Option[OnionRoutingPacket]): Behavior[Command] = {
+ private def relay(upstream: Upstream.Hot.Trampoline, recipient: Recipient, walletNodeId_opt: Option[PublicKey], recipientFeatures_opt: Option[Features[InitFeature]], payloadOut: IntermediatePayload.NodeRelay, packetOut_opt: Option[OnionRoutingPacket], upgradeAccountability: Boolean): Behavior[Command] = {
val amountOut = outgoingAmount(upstream, payloadOut)
val expiryOut = outgoingExpiry(upstream, payloadOut)
context.log.debug("relaying trampoline payment (amountIn={} expiryIn={} amountOut={} expiryOut={} isWallet={})", upstream.amountIn, upstream.expiryIn, amountOut, expiryOut, walletNodeId_opt.isDefined)
// We only make one try when it's a direct payment to a wallet.
val maxPaymentAttempts = if (walletNodeId_opt.isDefined) 1 else nodeParams.maxPaymentAttempts
- val paymentCfg = SendPaymentConfig(relayId, relayId, None, paymentHash, recipient.nodeId, upstream, None, None, storeInDb = false, publishEvent = false, recordPathFindingMetrics = true)
+ val accountable0 = upstream.accountable || nodeParams.relayParams.incomingChannelCongested(upstream.incomingChannelOccupancy)
+ val accountable = if (accountable0 && !upgradeAccountability) {
+ // We don't yet enforce channel jamming protections: we log that we would have failed that payment, but we
+ // currently relay it anyway. This will let us analyze data before actually activating jamming protection.
+ context.log.info("payment would have been rejected if jamming protection was activated")
+ false
+ } else {
+ accountable0
+ }
+ val paymentCfg = SendPaymentConfig(relayId, relayId, None, paymentHash, recipient.nodeId, upstream, None, None, storeInDb = false, publishEvent = false, recordPathFindingMetrics = true, accountable)
val routeParams = computeRouteParams(nodeParams, upstream.amountIn, upstream.expiryIn, amountOut, expiryOut)
// If the next node is using trampoline, we assume that they support MPP.
val useMultiPart = recipient.features.hasFeature(Features.BasicMultiPartPayment) || packetOut_opt.nonEmpty
@@ -356,7 +365,7 @@ class NodeRelay private(nodeParams: NodeParams,
}
val payFSM = outgoingPaymentFactory.spawnOutgoingPayFSM(context, paymentCfg, useMultiPart)
payFSM ! payment
- sending(upstream, recipient, walletNodeId_opt, recipientFeatures_opt, payloadOut, TimestampMilli.now(), fulfilledUpstream = false)
+ sending(upstream, recipient, walletNodeId_opt, recipientFeatures_opt, payloadOut, TimestampMilli.now(), fulfilledUpstream = false, accountable)
}
/**
@@ -372,7 +381,8 @@ class NodeRelay private(nodeParams: NodeParams,
recipientFeatures_opt: Option[Features[InitFeature]],
nextPayload: IntermediatePayload.NodeRelay,
startedAt: TimestampMilli,
- fulfilledUpstream: Boolean): Behavior[Command] =
+ fulfilledUpstream: Boolean,
+ accountable: Boolean): Behavior[Command] =
Behaviors.receiveMessagePartial {
rejectExtraHtlcPartialFunction orElse {
// this is the fulfill that arrives from downstream channels
@@ -381,7 +391,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, attribution_opt)
- sending(upstream, recipient, walletNodeId_opt, recipientFeatures_opt, nextPayload, startedAt, fulfilledUpstream = true)
+ sending(upstream, recipient, walletNodeId_opt, recipientFeatures_opt, nextPayload, startedAt, fulfilledUpstream = true, accountable)
} else {
// we don't want to fulfill multiple times
Behaviors.same
@@ -421,7 +431,7 @@ class NodeRelay private(nodeParams: NodeParams,
case r: BlindedRecipient => r.blindedHops.headOption
}
val dummyRoute = Route(amountOut, Seq(dummyHop), finalHop_opt)
- OutgoingPaymentPacket.buildOutgoingPayment(Origin.Hot(ActorRef.noSender, upstream), paymentHash, dummyRoute, recipient, Reputation.Score.max) match {
+ OutgoingPaymentPacket.buildOutgoingPayment(Origin.Hot(ActorRef.noSender, upstream), paymentHash, dummyRoute, recipient, Reputation.Score.max(accountable = false)) match {
case Left(f) =>
context.log.warn("could not create payment onion for on-the-fly funding: {}", f.getMessage)
rejectPayment(upstream, translateError(nodeParams, failures, upstream, nextPayload))
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala
index ed791b7..a196fcf 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala
@@ -310,7 +310,7 @@ object OnTheFlyFunding {
// This lets us detect that this HTLC is an on-the-fly funded HTLC.
val htlcFees = LiquidityAds.FundingFee(remainingFees.min(p.maxFees(htlcMinimum)), cmd.status.txId)
val origin = Origin.Hot(htlcSettledAdapter.toClassic, p.upstream)
- val add = CMD_ADD_HTLC(cmdAdapter.toClassic, p.htlc.amount - htlcFees.amount, paymentHash, p.htlc.expiry, p.htlc.finalPacket, p.htlc.pathKey_opt, Reputation.Score.max, Some(htlcFees), origin, commit = true)
+ val add = CMD_ADD_HTLC(cmdAdapter.toClassic, p.htlc.amount - htlcFees.amount, paymentHash, p.htlc.expiry, p.htlc.finalPacket, p.htlc.pathKey_opt, Reputation.Score.max(accountable = false), Some(htlcFees), origin, commit = true)
cmd.channel ! add
remainingFees - htlcFees.amount
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala
index 920274b..7f46092 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala
@@ -146,7 +146,8 @@ object Relayer extends Logging {
minTrampolineFees: RelayFees,
enforcementDelay: FiniteDuration,
asyncPaymentsParams: AsyncPaymentsParams,
- peerReputationConfig: Reputation.Config) {
+ peerReputationConfig: Reputation.Config,
+ reservedBucket: Double) {
def defaultFees(announceChannel: Boolean): RelayFees = {
if (announceChannel) {
publicChannelFees
@@ -154,6 +155,8 @@ object Relayer extends Logging {
privateChannelFees
}
}
+
+ def incomingChannelCongested(incomingChannelOccupancy: Double): Boolean = incomingChannelOccupancy > 1 - reservedBucket
}
case class RelayForward(add: UpdateAddHtlc, originNode: PublicKey, incomingChannelOccupancy: Double)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala
index f7ed6d6..3a7170e 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala
@@ -50,7 +50,7 @@ class PaymentInitiator(nodeParams: NodeParams, outgoingPaymentFactory: PaymentIn
// Immediately return the paymentId
replyTo ! paymentId
}
- val paymentCfg = SendPaymentConfig(paymentId, paymentId, r.externalId, r.paymentHash, r.invoice.nodeId, Upstream.Local(paymentId), Some(r.invoice), r.payerKey_opt, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true)
+ val paymentCfg = SendPaymentConfig(paymentId, paymentId, r.externalId, r.paymentHash, r.invoice.nodeId, Upstream.Local(paymentId), Some(r.invoice), r.payerKey_opt, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true, accountable = false)
val finalExpiry = r.finalExpiry(nodeParams)
val recipient = r.invoice match {
case invoice: Bolt11Invoice => ClearRecipient(invoice, r.recipientAmount, finalExpiry, r.userCustomTlvs)
@@ -71,7 +71,7 @@ class PaymentInitiator(nodeParams: NodeParams, outgoingPaymentFactory: PaymentIn
case r: SendSpontaneousPayment =>
val paymentId = UUID.randomUUID()
sender() ! paymentId
- val paymentCfg = SendPaymentConfig(paymentId, paymentId, r.externalId, r.paymentHash, r.recipientNodeId, Upstream.Local(paymentId), None, None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = r.recordPathFindingMetrics)
+ val paymentCfg = SendPaymentConfig(paymentId, paymentId, r.externalId, r.paymentHash, r.recipientNodeId, Upstream.Local(paymentId), None, None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = r.recordPathFindingMetrics, accountable = false)
val finalExpiry = nodeParams.paymentFinalExpiry.computeFinalExpiry(nodeParams.currentBlockHeight, Channel.MIN_CLTV_EXPIRY_DELTA)
val recipient = SpontaneousRecipient(r.recipientNodeId, r.recipientAmount, finalExpiry, r.paymentPreimage, r.userCustomTlvs)
val fsm = outgoingPaymentFactory.spawnOutgoingPayment(context, paymentCfg)
@@ -99,7 +99,7 @@ class PaymentInitiator(nodeParams: NodeParams, outgoingPaymentFactory: PaymentIn
sender() ! PaymentFailed(paymentId, r.paymentHash, LocalFailure(r.recipientAmount, Nil, UnsupportedFeatures(r.invoice.features)) :: Nil)
} else {
sender() ! SendPaymentToRouteResponse(paymentId, parentPaymentId)
- val paymentCfg = SendPaymentConfig(paymentId, parentPaymentId, r.externalId, r.paymentHash, r.recipientNodeId, Upstream.Local(paymentId), Some(r.invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = false)
+ val paymentCfg = SendPaymentConfig(paymentId, parentPaymentId, r.externalId, r.paymentHash, r.recipientNodeId, Upstream.Local(paymentId), Some(r.invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = false, accountable = false)
val finalExpiry = r.finalExpiry(nodeParams)
val recipient = r.invoice match {
case invoice: Bolt11Invoice => ClearRecipient(invoice, r.recipientAmount, finalExpiry, Set.empty)
@@ -316,6 +316,7 @@ object PaymentInitiator {
* @param publishEvent whether to publish a [[fr.acinq.eclair.payment.PaymentEvent]] on success/failure (e.g. for
* multi-part child payments, we don't want to emit events for each child, only for the whole payment).
* @param recordPathFindingMetrics We don't record metrics for payments that don't use path finding or that are a part of a bigger payment.
+ * @param accountable whether the outgoing HTLCs should be accountable in case of channel jamming.
*/
case class SendPaymentConfig(id: UUID,
parentId: UUID,
@@ -327,7 +328,8 @@ object PaymentInitiator {
payerKey_opt: Option[PrivateKey],
storeInDb: Boolean, // e.g. for trampoline we don't want to store in the DB when we're relaying payments
publishEvent: Boolean,
- recordPathFindingMetrics: Boolean) {
+ recordPathFindingMetrics: Boolean,
+ accountable: Boolean) {
val paymentContext: PaymentContext = PaymentContext(id, parentId, paymentHash)
val paymentType = invoice match {
case Some(_: Bolt12Invoice) => PaymentType.Blinded
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala
index f1aacc9..2de7489 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala
@@ -21,7 +21,6 @@ import akka.actor.{ActorRef, FSM, Props, typed}
import akka.event.Logging.MDC
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.eclair._
-import fr.acinq.eclair.channel.Upstream.Hot
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.{Sphinx, TransportHandler}
import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus}
@@ -79,14 +78,9 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
reputationRecorder_opt match {
case Some(reputationRecorder) =>
val cltvExpiry = route.fullRoute.map(_.cltvExpiryDelta).foldLeft(request.recipient.expiry)(_ + _)
- reputationRecorder ! GetConfidence(self, cfg.upstream, Some(route.hops.head.nextNodeId), route.hops.head.fee(request.amount), nodeParams.currentBlockHeight, cltvExpiry)
+ reputationRecorder ! GetConfidence(self, route.hops.head.nextNodeId, route.hops.head.fee(request.amount), nodeParams.currentBlockHeight, cltvExpiry, cfg.accountable)
case None =>
- val endorsement = cfg.upstream match {
- case Hot.Channel(add, _, _, _) => add.endorsement
- case Hot.Trampoline(received) => received.map(_.add.endorsement).min
- case Upstream.Local(_) => Reputation.maxEndorsement
- }
- self ! Reputation.Score.fromEndorsement(endorsement)
+ self ! Reputation.Score.max(cfg.accountable)
}
goto(WAITING_FOR_CONFIDENCE) using WaitingForConfidence(request, failures, ignore, route)
@@ -484,7 +478,7 @@ object PaymentLifecycle {
require(route.fold(r => !r.isEmpty, r => r.hops.nonEmpty || r.finalHop_opt.nonEmpty), "payment route must not be empty")
override val maxAttempts: Int = 1
- override val amount = route.fold(_.amount, _.amount)
+ override val amount: MilliSatoshi = route.fold(_.amount, _.amount)
def printRoute(): String = route match {
case Left(PredefinedChannelRoute(_, _, channels, _)) => channels.mkString("->")
@@ -502,7 +496,7 @@ object PaymentLifecycle {
*/
case class SendPaymentToNode(replyTo: ActorRef, recipient: Recipient, maxAttempts: Int, routeParams: RouteParams) extends SendPayment {
require(recipient.totalAmount > 0.msat, "amount must be > 0")
- override val amount = recipient.totalAmount
+ override val amount: MilliSatoshi = recipient.totalAmount
}
// @formatter:off
@@ -511,7 +505,7 @@ object PaymentLifecycle {
case class WaitingForRoute(request: SendPayment, failures: Seq[PaymentFailure], ignore: Ignore) extends Data
case class WaitingForConfidence(request: SendPayment, failures: Seq[PaymentFailure], ignore: Ignore, route: Route) extends Data
case class WaitingForComplete(request: SendPayment, cmd: CMD_ADD_HTLC, failures: Seq[PaymentFailure], sharedSecrets: Seq[Sphinx.SharedSecret], ignore: Ignore, route: Route) extends Data {
- val recipient = request.recipient
+ val recipient: Recipient = request.recipient
}
sealed trait State
@@ -521,9 +515,9 @@ object PaymentLifecycle {
case object WAITING_FOR_PAYMENT_COMPLETE extends State
/** custom exceptions to handle corner cases */
- case object UpdateMalformedException extends RuntimeException("first hop returned an UpdateFailMalformedHtlc message")
- case object ChannelFailureException extends RuntimeException("a channel failure occurred with the first hop")
- case object DisconnectedException extends RuntimeException("a disconnection occurred with the first hop")
+ private case object UpdateMalformedException extends RuntimeException("first hop returned an UpdateFailMalformedHtlc message")
+ private case object ChannelFailureException extends RuntimeException("a channel failure occurred with the first hop")
+ private case object DisconnectedException extends RuntimeException("a disconnection occurred with the first hop")
// @formatter:on
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Recipient.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Recipient.scala
index 09b46a4..f7914f5 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Recipient.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Recipient.scala
@@ -54,11 +54,11 @@ sealed trait Recipient {
object Recipient {
/** Iteratively build all the payloads for a payment relayed through channel hops. */
- def buildPayloads(finalPayloads: PaymentPayloads, hops: Seq[ChannelHop]): PaymentPayloads = {
+ def buildPayloads(finalPayloads: PaymentPayloads, hops: Seq[ChannelHop], upgradeAccountability: Boolean): PaymentPayloads = {
// We ignore the first hop since the route starts at our node.
hops.tail.foldRight(finalPayloads) {
case (hop, current) =>
- val payload = NodePayload(hop.nodeId, IntermediatePayload.ChannelRelay.Standard(hop.shortChannelId, current.amount, current.expiry))
+ val payload = NodePayload(hop.nodeId, IntermediatePayload.ChannelRelay.Standard(hop.shortChannelId, current.amount, current.expiry, upgradeAccountability))
PaymentPayloads(current.amount + hop.fee(current.amount), current.expiry + hop.cltvExpiryDelta, payload +: current.payloads, None)
}
}
@@ -70,6 +70,7 @@ case class ClearRecipient(nodeId: PublicKey,
totalAmount: MilliSatoshi,
expiry: CltvExpiry,
paymentSecret: ByteVector32,
+ upgradeAccountability: Boolean,
extraEdges: Seq[ExtraEdge] = Nil,
paymentMetadata_opt: Option[ByteVector] = None,
nextTrampolineOnion_opt: Option[OnionRoutingPacket] = None,
@@ -77,17 +78,17 @@ case class ClearRecipient(nodeId: PublicKey,
override def buildPayloads(paymentHash: ByteVector32, route: Route): Either[OutgoingPaymentError, PaymentPayloads] = {
ClearRecipient.validateRoute(nodeId, route).map(_ => {
val finalPayload = nextTrampolineOnion_opt match {
- case Some(trampolinePacket) => NodePayload(nodeId, FinalPayload.Standard.createTrampolinePayload(route.amount, totalAmount, expiry, paymentSecret, trampolinePacket))
- case None => NodePayload(nodeId, FinalPayload.Standard.createPayload(route.amount, totalAmount, expiry, paymentSecret, paymentMetadata_opt, trampolineOnion_opt = None, customTlvs = customTlvs))
+ case Some(trampolinePacket) => NodePayload(nodeId, FinalPayload.Standard.createTrampolinePayload(route.amount, totalAmount, expiry, paymentSecret, trampolinePacket, upgradeAccountability))
+ case None => NodePayload(nodeId, FinalPayload.Standard.createPayload(route.amount, totalAmount, expiry, paymentSecret, paymentMetadata_opt, trampolineOnion_opt = None, customTlvs = customTlvs, upgradeAccountability))
}
- Recipient.buildPayloads(PaymentPayloads(route.amount, expiry, Seq(finalPayload), None), route.hops)
+ Recipient.buildPayloads(PaymentPayloads(route.amount, expiry, Seq(finalPayload), None), route.hops, upgradeAccountability)
})
}
}
object ClearRecipient {
def apply(invoice: Bolt11Invoice, totalAmount: MilliSatoshi, expiry: CltvExpiry, customTlvs: Set[GenericTlv]): ClearRecipient = {
- ClearRecipient(invoice.nodeId, invoice.features, totalAmount, expiry, invoice.paymentSecret, invoice.extraEdges, invoice.paymentMetadata, None, customTlvs)
+ ClearRecipient(invoice.nodeId, invoice.features, totalAmount, expiry, invoice.paymentSecret, invoice.accountable, invoice.extraEdges, invoice.paymentMetadata, None, customTlvs)
}
def validateRoute(nodeId: PublicKey, route: Route): Either[OutgoingPaymentError, Route] = {
@@ -105,13 +106,13 @@ case class SpontaneousRecipient(nodeId: PublicKey,
expiry: CltvExpiry,
preimage: ByteVector32,
customTlvs: Set[GenericTlv] = Set.empty) extends Recipient {
- override val features = Features.empty
- override val extraEdges = Nil
+ override val features: Features[InvoiceFeature] = Features.empty
+ override val extraEdges: Seq[ExtraEdge] = Nil
override def buildPayloads(paymentHash: ByteVector32, route: Route): Either[OutgoingPaymentError, PaymentPayloads] = {
ClearRecipient.validateRoute(nodeId, route).map(_ => {
val finalPayload = NodePayload(nodeId, FinalPayload.Standard.createKeySendPayload(route.amount, expiry, preimage, customTlvs))
- Recipient.buildPayloads(PaymentPayloads(totalAmount, expiry, Seq(finalPayload), None), route.hops)
+ Recipient.buildPayloads(PaymentPayloads(totalAmount, expiry, Seq(finalPayload), None), route.hops, upgradeAccountability = true)
})
}
}
@@ -122,10 +123,11 @@ case class BlindedRecipient(nodeId: PublicKey,
totalAmount: MilliSatoshi,
expiry: CltvExpiry,
blindedHops: Seq[BlindedHop],
+ upgradeAccountability: Boolean,
customTlvs: Set[GenericTlv]) extends Recipient {
require(blindedHops.nonEmpty, "blinded routes must be provided")
- override val extraEdges = blindedHops.map { h =>
+ override val extraEdges: Seq[ExtraEdge] = blindedHops.map { h =>
ExtraEdge(h.nodeId, nodeId, h.dummyId, h.paymentInfo.feeBase, h.paymentInfo.feeProportionalMillionths, h.paymentInfo.cltvExpiryDelta, h.paymentInfo.minHtlc, Some(h.paymentInfo.maxHtlc))
}
@@ -166,7 +168,7 @@ case class BlindedRecipient(nodeId: PublicKey,
override def buildPayloads(paymentHash: ByteVector32, route: Route): Either[OutgoingPaymentError, PaymentPayloads] = {
validateRoute(route).flatMap(blindedHop => {
val blindedPayloads = buildBlindedPayloads(route.amount, blindedHop)
- Right(Recipient.buildPayloads(blindedPayloads, route.hops))
+ Right(Recipient.buildPayloads(blindedPayloads, route.hops, upgradeAccountability))
})
}
}
@@ -177,9 +179,9 @@ object BlindedRecipient {
* @param paths Payment paths to use to reach the recipient.
*/
def apply(invoice: Bolt12Invoice, paths: Seq[ResolvedPath], totalAmount: MilliSatoshi, expiry: CltvExpiry, customTlvs: Set[GenericTlv], duplicatePaths: Int = 3): BlindedRecipient =
- BlindedRecipient.fromPaths(invoice.nodeId, invoice.features, totalAmount, expiry, paths, customTlvs, duplicatePaths)
+ BlindedRecipient.fromPaths(invoice.nodeId, invoice.features, totalAmount, expiry, paths, invoice.accountable, customTlvs, duplicatePaths)
- def fromPaths(nodeId: PublicKey, features: Features[InvoiceFeature], totalAmount: MilliSatoshi, expiry: CltvExpiry, paths: Seq[ResolvedPath], customTlvs: Set[GenericTlv], duplicatePaths: Int = 3): BlindedRecipient = {
+ def fromPaths(nodeId: PublicKey, features: Features[InvoiceFeature], totalAmount: MilliSatoshi, expiry: CltvExpiry, paths: Seq[ResolvedPath], upgradeAccountability: Boolean, customTlvs: Set[GenericTlv], duplicatePaths: Int = 3): BlindedRecipient = {
val blindedHops = paths.flatMap(resolved => {
// We want to be able to split payments *inside* a blinded route, because nodes inside the route may be connected
// by multiple channels which may be imbalanced. A simple trick for that is to clone each blinded path three times,
@@ -191,6 +193,6 @@ object BlindedRecipient {
BlindedHop(dummyId, resolved)
})
})
- BlindedRecipient(nodeId, features, totalAmount, expiry, blindedHops, customTlvs)
+ BlindedRecipient(nodeId, features, totalAmount, expiry, blindedHops, upgradeAccountability, customTlvs)
}
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/TrampolinePaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/TrampolinePaymentLifecycle.scala
index bf99fb5..465b611 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/TrampolinePaymentLifecycle.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/TrampolinePaymentLifecycle.scala
@@ -110,7 +110,7 @@ object TrampolinePaymentLifecycle {
def start(amount: MilliSatoshi, channelInfo: Peer.ChannelInfo, expiry: CltvExpiry, trampolinePaymentSecret: ByteVector32, attemptNumber: Int): Behavior[PartHandler.Command] = {
val origin = Origin.Hot(htlcSettledAdapter.toClassic, Upstream.Local(cmd.paymentId))
val outgoing = buildOutgoingPayment(cmd.trampolineNodeId, cmd.invoice, amount, expiry, Some(trampolinePaymentSecret), attemptNumber)
- val add = CMD_ADD_HTLC(addHtlcAdapter.toClassic, outgoing.trampolineAmount, paymentHash, outgoing.trampolineExpiry, outgoing.onion.packet, None, Reputation.Score.max, None, origin, commit = true)
+ val add = CMD_ADD_HTLC(addHtlcAdapter.toClassic, outgoing.trampolineAmount, paymentHash, outgoing.trampolineExpiry, outgoing.onion.packet, None, Reputation.Score.max(accountable = false), None, origin, commit = true)
channelInfo.channel ! add
val channelId = channelInfo.data.asInstanceOf[DATA_NORMAL].channelId
val part = PartialPayment(cmd.paymentId, amount, computeFees(amount, attemptNumber), channelId, None)
@@ -280,8 +280,8 @@ object TrampolinePayment {
val totalAmount = invoice.amount_opt.get
val trampolineOnion = invoice match {
case invoice: Bolt11Invoice if invoice.features.hasFeature(Features.TrampolinePaymentPrototype) =>
- val finalPayload = PaymentOnion.FinalPayload.Standard.createPayload(amount, totalAmount, expiry, invoice.paymentSecret, invoice.paymentMetadata)
- val trampolinePayload = PaymentOnion.IntermediatePayload.NodeRelay.Standard(totalAmount, expiry, invoice.nodeId)
+ val finalPayload = PaymentOnion.FinalPayload.Standard.createPayload(amount, totalAmount, expiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = invoice.accountable)
+ val trampolinePayload = PaymentOnion.IntermediatePayload.NodeRelay.Standard(totalAmount, expiry, invoice.nodeId, upgradeAccountability = invoice.accountable)
buildOnion(NodePayload(trampolineNodeId, trampolinePayload) :: NodePayload(invoice.nodeId, finalPayload) :: Nil, invoice.paymentHash, None).toOption.get
case invoice: Bolt11Invoice =>
val trampolinePayload = PaymentOnion.IntermediatePayload.NodeRelay.ToNonTrampoline(totalAmount, totalAmount, expiry, invoice.nodeId, invoice)
@@ -294,8 +294,8 @@ object TrampolinePayment {
val trampolineTotalAmount = computeTrampolineAmount(totalAmount, attemptNumber)
val trampolineExpiry = computeTrampolineExpiry(expiry, attemptNumber)
val payload = trampolinePaymentSecret_opt match {
- case Some(trampolinePaymentSecret) => PaymentOnion.FinalPayload.Standard.createTrampolinePayload(trampolineAmount, trampolineTotalAmount, trampolineExpiry, trampolinePaymentSecret, trampolineOnion.packet)
- case None => PaymentOnion.TrampolineWithoutMppPayload.create(trampolineAmount, trampolineExpiry, trampolineOnion.packet)
+ case Some(trampolinePaymentSecret) => PaymentOnion.FinalPayload.Standard.createTrampolinePayload(trampolineAmount, trampolineTotalAmount, trampolineExpiry, trampolinePaymentSecret, trampolineOnion.packet, upgradeAccountability = invoice.accountable)
+ case None => PaymentOnion.TrampolineWithoutMppPayload.create(trampolineAmount, trampolineExpiry, trampolineOnion.packet, upgradeAccountability = invoice.accountable)
}
val paymentOnion = buildOnion(NodePayload(trampolineNodeId, payload) :: Nil, invoice.paymentHash, Some(PaymentOnionCodecs.paymentOnionPayloadLength)).toOption.get
OutgoingPayment(trampolineAmount, trampolineExpiry, paymentOnion, trampolineOnion)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala
index 43b8816..57dd7ea 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala
@@ -25,7 +25,7 @@ import fr.acinq.eclair.{BlockHeight, CltvExpiry, MilliSatoshi, TimestampMilli}
import scala.concurrent.duration.{DurationInt, FiniteDuration}
/**
- * Reputation score per endorsement level.
+ * Reputation score per accountability level (we currently only support two levels, 0 or 1).
*
* @param weight How much fees we would have collected in the past if all HTLCs had succeeded (exponential moving average).
* @param score How much fees we have collected in the past (exponential moving average).
@@ -34,7 +34,7 @@ import scala.concurrent.duration.{DurationInt, FiniteDuration}
case class PastScore(weight: Double, score: Double, lastSettlementAt: TimestampMilli)
/** We're relaying that HTLC and are waiting for it to settle. */
-case class PendingHtlc(fee: MilliSatoshi, endorsement: Int, startedAt: TimestampMilli, expiry: CltvExpiry) {
+case class PendingHtlc(fee: MilliSatoshi, accountability: Int, startedAt: TimestampMilli, expiry: CltvExpiry) {
def weight(now: TimestampMilli, minDuration: FiniteDuration, currentBlockHeight: BlockHeight): Double = {
val alreadyPending = now - startedAt
val untilExpiry = (expiry.toLong - currentBlockHeight.toLong) * 10.minutes
@@ -50,12 +50,13 @@ case object HtlcId {
}
/**
- * Local reputation for a given node.
+ * Local reputation for a given node. Note that we use a different algorithm than what the BOLTs recommend, because we
+ * may want to support more than a binary accountability in the future.
*
- * @param pastScores Scores from past HTLCs for each endorsement level.
- * @param pending Set of pending HTLCs.
- * @param halfLife Half life for the exponential moving average.
- * @param maxRelayDuration Duration after which HTLCs are penalized for staying pending too long.
+ * @param pastScores Scores from past HTLCs for each accountability level.
+ * @param pending Set of pending HTLCs.
+ * @param halfLife Half life for the exponential moving average.
+ * @param maxRelayDuration Duration after which HTLCs are penalized for staying pending too long.
*/
case class Reputation(pastScores: Map[Int, PastScore], pending: Map[HtlcId, PendingHtlc], halfLife: FiniteDuration, maxRelayDuration: FiniteDuration) {
private def decay(now: TimestampMilli, lastSettlementAt: TimestampMilli): Double = scala.math.pow(0.5, (now - lastSettlementAt) / halfLife)
@@ -63,29 +64,29 @@ case class Reputation(pastScores: Map[Int, PastScore], pending: Map[HtlcId, Pend
/**
* Estimate the confidence that a payment will succeed.
*/
- def getConfidence(fee: MilliSatoshi, endorsement: Int, currentBlockHeight: BlockHeight, expiry: CltvExpiry, now: TimestampMilli = TimestampMilli.now()): Double = {
- val weights = Array.fill(Reputation.endorsementLevels)(0.0)
- val scores = Array.fill(Reputation.endorsementLevels)(0.0)
- for (e <- 0 until Reputation.endorsementLevels) {
+ def getConfidence(fee: MilliSatoshi, accountability: Int, currentBlockHeight: BlockHeight, expiry: CltvExpiry, now: TimestampMilli = TimestampMilli.now()): Double = {
+ val weights = Array.fill(Reputation.accountabilityLevels)(0.0)
+ val scores = Array.fill(Reputation.accountabilityLevels)(0.0)
+ for (e <- 0 until Reputation.accountabilityLevels) {
val d = decay(now, pastScores(e).lastSettlementAt)
weights(e) += d * pastScores(e).weight
scores(e) += d * pastScores(e).score
}
for (p <- pending.values) {
- weights(p.endorsement) += p.weight(now, maxRelayDuration, currentBlockHeight)
+ weights(p.accountability) += p.weight(now, maxRelayDuration, currentBlockHeight)
}
- weights(endorsement) += PendingHtlc(fee, endorsement, now, expiry).weight(now, maxRelayDuration, currentBlockHeight)
+ weights(accountability) += PendingHtlc(fee, accountability, now, expiry).weight(now, maxRelayDuration, currentBlockHeight)
/*
- Higher endorsement buckets may have fewer payments which makes the weight of pending payments disproportionately
+ Higher accountability buckets may have fewer payments which makes the weight of pending payments disproportionately
important. To counter this effect, we try adding payments from the lower buckets to see if it gives us a higher
confidence score.
- It is acceptable to use payments with lower endorsements to increase the confidence score but not payments with
- higher endorsements.
+ It is acceptable to use payments with lower accountability to increase the confidence score but not payments with
+ higher accountability.
*/
- var score = scores(endorsement)
- var weight = weights(endorsement)
+ var score = scores(accountability)
+ var weight = weights(accountability)
var confidence = score / weight
- for (e <- Range.inclusive(endorsement - 1, 0, step = -1)) {
+ for (e <- Range.inclusive(accountability - 1, 0, step = -1)) {
score += scores(e)
weight += weights(e)
confidence = confidence.max(score / weight)
@@ -96,85 +97,51 @@ case class Reputation(pastScores: Map[Int, PastScore], pending: Map[HtlcId, Pend
/**
* Register a pending relay.
*/
- def addPendingHtlc(add: UpdateAddHtlc, fee: MilliSatoshi, endorsement: Int, now: TimestampMilli = TimestampMilli.now()): Reputation =
- copy(pending = pending + (HtlcId(add) -> PendingHtlc(fee, endorsement, now, add.cltvExpiry)))
+ def addPendingHtlc(add: UpdateAddHtlc, fee: MilliSatoshi, accountability: Int, now: TimestampMilli = TimestampMilli.now()): Reputation =
+ copy(pending = pending + (HtlcId(add) -> PendingHtlc(fee, accountability, now, add.cltvExpiry)))
/**
* When a HTLC is settled, we record whether it succeeded and how long it took.
*/
def settlePendingHtlc(htlcId: HtlcId, isSuccess: Boolean, now: TimestampMilli = TimestampMilli.now()): Reputation = {
val newScores = pending.get(htlcId).map(p => {
- val d = decay(now, pastScores(p.endorsement).lastSettlementAt)
+ val d = decay(now, pastScores(p.accountability).lastSettlementAt)
val duration = now - p.startedAt
val (weight, score) = if (isSuccess) {
(p.fee.toLong.toDouble * (duration / maxRelayDuration).max(1.0), p.fee.toLong.toDouble)
} else {
(p.fee.toLong.toDouble * (duration / maxRelayDuration), 0.0)
}
- val newWeight = d * pastScores(p.endorsement).weight + weight
- val newScore = d * pastScores(p.endorsement).score + score
- pastScores + (p.endorsement -> PastScore(newWeight, newScore, now))
+ val newWeight = d * pastScores(p.accountability).weight + weight
+ val newScore = d * pastScores(p.accountability).score + score
+ pastScores + (p.accountability -> PastScore(newWeight, newScore, now))
}).getOrElse(pastScores)
copy(pending = pending - htlcId, pastScores = newScores)
}
}
object Reputation {
- private val endorsementLevels = 8
- val maxEndorsement: Int = endorsementLevels - 1
+ // We only support binary accountability to match the BOLTs, but may use more levels in the future.
+ private val accountabilityLevels = 2
case class Config(enabled: Boolean, halfLife: FiniteDuration, maxRelayDuration: FiniteDuration)
def init(config: Config): Reputation = Reputation(Map.empty.withDefaultValue(PastScore(0.0, 0.0, TimestampMilli.min)), Map.empty, config.halfLife, config.maxRelayDuration)
- /**
- * @param incomingConfidence Confidence that the outgoing HTLC will succeed given the reputation of the incoming peer
- */
- case class Score(incomingConfidence: Double, outgoingConfidence: Double) {
- val endorsement: Int = toEndorsement(incomingConfidence)
-
- def checkOutgoingChannelOccupancy(channelId: ByteVector32, commitment: Commitment, outgoingHtlcs: Seq[UpdateAddHtlc]): Either[ChannelJammingException, Unit] = {
- val maxAcceptedHtlcs = Seq(commitment.localCommitParams.maxAcceptedHtlcs, commitment.remoteCommitParams.maxAcceptedHtlcs).min
-
- for ((amountMsat, i) <- outgoingHtlcs.map(_.amountMsat).sorted.zipWithIndex) {
- // We want to allow some small HTLCs but still keep slots for larger ones.
- // We never want to reject HTLCs of size above `maxHtlcAmount / maxAcceptedHtlcs` as too small because we want to allow filling all the slots with HTLCs of equal sizes.
- // We use exponentially spaced thresholds in between.
- if (amountMsat.toLong < 1 || amountMsat.toLong.toDouble < math.pow(commitment.maxHtlcValueInFlight.toLong.toDouble / maxAcceptedHtlcs, i / maxAcceptedHtlcs)) {
- return Left(TooManySmallHtlcs(channelId, number = i + 1, below = amountMsat))
- }
- }
-
- val htlcValueInFlight = outgoingHtlcs.map(_.amountMsat).sum
- val slotsOccupancy = outgoingHtlcs.size.toDouble / maxAcceptedHtlcs
- val valueOccupancy = htlcValueInFlight.toLong.toDouble / commitment.maxHtlcValueInFlight.toLong.toDouble
- val occupancy = slotsOccupancy max valueOccupancy
- // Because there are only 8 endorsement levels, the highest endorsement corresponds to a confidence between 87.5% and 100%.
- // So even for well-behaved peers setting the highest endorsement we still expect a confidence of less than 93.75%.
- // To compensate for that we add a tolerance of 10% that's also useful for nodes without history.
- if (incomingConfidence + 0.1 < occupancy) {
- return Left(IncomingConfidenceTooLow(channelId, incomingConfidence, occupancy))
- }
-
- Right(())
- }
-
+ case class Score(outgoingConfidence: Double, accountable: Boolean) {
def checkIncomingChannelOccupancy(incomingChannelOccupancy: Double, outgoingChannelId: ByteVector32): Either[ChannelJammingException, Unit] = {
if (outgoingConfidence + 0.1 < incomingChannelOccupancy) {
- return Left(OutgoingConfidenceTooLow(outgoingChannelId, incomingConfidence, incomingChannelOccupancy))
+ return Left(OutgoingConfidenceTooLow(outgoingChannelId, outgoingConfidence, incomingChannelOccupancy))
}
Right(())
}
}
case object Score {
- val max: Score = Score(1.0, 1.0)
-
- def fromEndorsement(endorsement: Int): Score = Score((endorsement + 0.5) / 8, 1.0)
+ def min: Score = Score(0.0, accountable = false)
+ def max(accountable: Boolean): Score = Score(1.0, accountable)
}
- def toEndorsement(confidence: Double): Int = (confidence * endorsementLevels).toInt.min(maxEndorsement)
-
def incomingOccupancy(commitments: Commitments): Double = {
commitments.active.map(commitment => {
val incomingHtlcs = commitment.localCommit.spec.htlcs.collect(DirectedHtlc.incoming)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/ReputationRecorder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/ReputationRecorder.scala
index df52a56..d89465d 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/ReputationRecorder.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/ReputationRecorder.scala
@@ -20,11 +20,10 @@ import akka.actor.typed.eventstream.EventStream
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
import akka.actor.typed.{ActorRef, Behavior}
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
-import fr.acinq.eclair.{BlockHeight, CltvExpiry, MilliSatoshi}
-import fr.acinq.eclair.channel.Upstream.Hot
-import fr.acinq.eclair.channel.{OutgoingHtlcAdded, OutgoingHtlcFailed, OutgoingHtlcFulfilled, OutgoingHtlcSettled, Upstream}
+import fr.acinq.eclair.channel.{OutgoingHtlcAdded, OutgoingHtlcFailed, OutgoingHtlcFulfilled, OutgoingHtlcSettled}
import fr.acinq.eclair.reputation.ReputationRecorder._
import fr.acinq.eclair.wire.protocol.{UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc}
+import fr.acinq.eclair.{BlockHeight, CltvExpiry, MilliSatoshi}
import scala.collection.mutable
import scala.concurrent.duration.DurationInt
@@ -32,7 +31,7 @@ import scala.concurrent.duration.DurationInt
object ReputationRecorder {
// @formatter:off
sealed trait Command
- case class GetConfidence(replyTo: ActorRef[Reputation.Score], upstream: Upstream.Hot, downstream_opt: Option[PublicKey], fee: MilliSatoshi, currentBlockHeight: BlockHeight, expiry: CltvExpiry) extends Command
+ case class GetConfidence(replyTo: ActorRef[Reputation.Score], downstream: PublicKey, fee: MilliSatoshi, currentBlockHeight: BlockHeight, expiry: CltvExpiry, accountable: Boolean) extends Command
case class WrappedOutgoingHtlcAdded(added: OutgoingHtlcAdded) extends Command
case class WrappedOutgoingHtlcSettled(settled: OutgoingHtlcSettled) extends Command
private case object TickAudit extends Command
@@ -51,15 +50,13 @@ object ReputationRecorder {
/**
* A pending outgoing HTLC.
*
- * @param add UpdateAddHtlc that contains an id for the HTLC and an endorsement value.
- * @param upstream The incoming node or nodes.
- * @param downstream The outgoing node.
+ * @param add UpdateAddHtlc that contains an id for the HTLC and an accountability value.
+ * @param remoteNodeId The outgoing node.
*/
- case class PendingHtlc(add: UpdateAddHtlc, upstream: Upstream.Hot, downstream: PublicKey)
+ case class PendingHtlc(add: UpdateAddHtlc, remoteNodeId: PublicKey)
}
class ReputationRecorder(context: ActorContext[ReputationRecorder.Command], config: Reputation.Config) {
- private val incomingReputations: mutable.Map[PublicKey, Reputation] = mutable.HashMap.empty.withDefaultValue(Reputation.init(config))
private val outgoingReputations: mutable.Map[PublicKey, Reputation] = mutable.HashMap.empty.withDefaultValue(Reputation.init(config))
private val pending: mutable.Map[HtlcId, PendingHtlc] = mutable.HashMap.empty
@@ -67,49 +64,17 @@ class ReputationRecorder(context: ActorContext[ReputationRecorder.Command], conf
def run(): Behavior[Command] =
Behaviors.receiveMessage {
- case GetConfidence(replyTo, _: Upstream.Local, _, _, _, _) =>
- replyTo ! Reputation.Score.max
- Behaviors.same
-
- case GetConfidence(replyTo, upstream: Upstream.Hot.Channel, downstream_opt, fee, currentBlockHeight, expiry) =>
- val incomingConfidence = incomingReputations.get(upstream.receivedFrom).map(_.getConfidence(fee, upstream.add.endorsement, currentBlockHeight, expiry)).getOrElse(0.0)
- val outgoingConfidence = downstream_opt.flatMap(outgoingReputations.get).map(_.getConfidence(fee, Reputation.toEndorsement(incomingConfidence), currentBlockHeight, expiry)).getOrElse(0.0)
- replyTo ! Reputation.Score(incomingConfidence, outgoingConfidence)
+ case GetConfidence(replyTo, remoteNodeId, fee, currentBlockHeight, expiry, accountable) =>
+ val accountability = if (accountable) 1 else 0
+ val outgoingConfidence = outgoingReputations.get(remoteNodeId).map(_.getConfidence(fee, accountability, currentBlockHeight, expiry)).getOrElse(0.0)
+ replyTo ! Reputation.Score(outgoingConfidence, accountable)
Behaviors.same
- case GetConfidence(replyTo, upstream: Upstream.Hot.Trampoline, downstream_opt, totalFee, currentBlockHeight, expiry) =>
- val incomingConfidence =
- upstream.received
- .groupMapReduce(_.receivedFrom)(r => (r.add.amountMsat, r.add.endorsement)) {
- case ((amount1, endorsement1), (amount2, endorsement2)) => (amount1 + amount2, endorsement1 min endorsement2)
- }
- .map {
- case (nodeId, (amount, endorsement)) =>
- val fee = amount * totalFee.toLong / upstream.amountIn.toLong
- incomingReputations.get(nodeId).map(_.getConfidence(fee, endorsement, currentBlockHeight, expiry)).getOrElse(0.0)
- }
- .min
- val outgoingConfidence = downstream_opt.flatMap(outgoingReputations.get).map(_.getConfidence(totalFee, Reputation.toEndorsement(incomingConfidence), currentBlockHeight, expiry)).getOrElse(0.0)
- replyTo ! Reputation.Score(incomingConfidence, outgoingConfidence)
- Behaviors.same
-
- case WrappedOutgoingHtlcAdded(OutgoingHtlcAdded(add, remoteNodeId, upstream, fee)) =>
+ case WrappedOutgoingHtlcAdded(OutgoingHtlcAdded(add, remoteNodeId, fee)) =>
val htlcId = HtlcId(add)
- upstream match {
- case channel: Hot.Channel =>
- incomingReputations(channel.receivedFrom) = incomingReputations(channel.receivedFrom).addPendingHtlc(add, fee, channel.add.endorsement)
- case trampoline: Hot.Trampoline =>
- trampoline.received
- .groupMapReduce(_.receivedFrom)(r => (r.add.amountMsat, r.add.endorsement)) {
- case ((amount1, endorsement1), (amount2, endorsement2)) => (amount1 + amount2, endorsement1 min endorsement2)
- }
- .foreach { case (nodeId, (amount, endorsement)) =>
- incomingReputations(nodeId) = incomingReputations(nodeId).addPendingHtlc(add, fee * amount.toLong / trampoline.amountIn.toLong, endorsement)
- }
- case _: Upstream.Local => ()
- }
- outgoingReputations(remoteNodeId) = outgoingReputations(remoteNodeId).addPendingHtlc(add, fee, add.endorsement)
- pending(htlcId) = PendingHtlc(add, upstream, remoteNodeId)
+ val accountability = if (add.accountable) 1 else 0
+ outgoingReputations(remoteNodeId) = outgoingReputations(remoteNodeId).addPendingHtlc(add, fee, accountability)
+ pending(htlcId) = PendingHtlc(add, remoteNodeId)
Behaviors.same
case WrappedOutgoingHtlcSettled(settled) =>
@@ -123,23 +88,13 @@ class ReputationRecorder(context: ActorContext[ReputationRecorder.Command], conf
case _: OutgoingHtlcFulfilled => true
}
pending.remove(htlcId).foreach(p => {
- p.upstream match {
- case Hot.Channel(_, _, receivedFrom, _) =>
- incomingReputations(receivedFrom) = incomingReputations(receivedFrom).settlePendingHtlc(htlcId, isSuccess)
- case Hot.Trampoline(received) =>
- received.foreach(channel =>
- incomingReputations(channel.receivedFrom) = incomingReputations(channel.receivedFrom).settlePendingHtlc(htlcId, isSuccess)
- )
- case _: Upstream.Local => ()
- }
- outgoingReputations(p.downstream) = outgoingReputations(p.downstream).settlePendingHtlc(htlcId, isSuccess)
+ outgoingReputations(p.remoteNodeId) = outgoingReputations(p.remoteNodeId).settlePendingHtlc(htlcId, isSuccess)
})
Behaviors.same
case TickAudit =>
- val totalIncomingPending = incomingReputations.values.map(_.pending.size).sum
val totalOutgoingPending = outgoingReputations.values.map(_.pending.size).sum
- log.info("{} pending HTLCs: {} tracked for incoming reputation and {} tracked for outgoing reputation", pending.size, totalIncomingPending, totalOutgoingPending)
+ log.info("{} pending HTLCs: {} tracked for outgoing reputation", pending.size, totalOutgoingPending)
Behaviors.same
}
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/BlindedRouteCreation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/BlindedRouteCreation.scala
index 7b07aba..9efbb65 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/router/BlindedRouteCreation.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/BlindedRouteCreation.scala
@@ -21,7 +21,7 @@ import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.router.Router.ChannelHop
import fr.acinq.eclair.wire.protocol.OfferTypes.PaymentInfo
import fr.acinq.eclair.wire.protocol.{RouteBlindingEncryptedDataCodecs, RouteBlindingEncryptedDataTlv, TlvStream}
-import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, EncodedNodeId, Features, MilliSatoshi, MilliSatoshiLong, randomKey}
+import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, EncodedNodeId, MilliSatoshi, MilliSatoshiLong, randomKey}
import scodec.bits.ByteVector
object BlindedRouteCreation {
@@ -48,12 +48,14 @@ object BlindedRouteCreation {
val finalPayload = RouteBlindingEncryptedDataCodecs.blindedRouteDataCodec.encode(TlvStream(
RouteBlindingEncryptedDataTlv.PaymentConstraints(routeExpiry, routeMinAmount),
RouteBlindingEncryptedDataTlv.PathId(pathId),
+ RouteBlindingEncryptedDataTlv.UpgradeAccountability(),
)).require.bytes
val payloads = hops.map(channel =>
TlvStream[RouteBlindingEncryptedDataTlv](
RouteBlindingEncryptedDataTlv.OutgoingChannelId(channel.shortChannelId),
RouteBlindingEncryptedDataTlv.PaymentRelay(channel.cltvExpiryDelta, channel.params.relayFees.feeProportionalMillionths, channel.params.relayFees.feeBase),
RouteBlindingEncryptedDataTlv.PaymentConstraints(routeExpiry, routeMinAmount),
+ RouteBlindingEncryptedDataTlv.UpgradeAccountability(),
)
)
/*
@@ -73,9 +75,12 @@ object BlindedRouteCreation {
- length: 1 byte
- max_cltv_expiry: 4 bytes
- htlc_minimum_msat: 0 to 8 bytes
- Total: 24 to 36 bytes
+ - UpgradeAccountability:
+ - tag: 1 byte
+ - length: 1 byte
+ Total: 26 to 38 bytes
*/
- val targetLength = 36
+ val targetLength = 38
val paddedPayloads = payloads.map(tlvs => {
val payloadLength = RouteBlindingEncryptedDataCodecs.blindedRouteDataCodec.encode(tlvs).require.bytes.length
tlvs.copy(records = tlvs.records + RouteBlindingEncryptedDataTlv.Padding(ByteVector.fill(targetLength - payloadLength)(0)))
@@ -91,11 +96,13 @@ object BlindedRouteCreation {
val finalPayload = RouteBlindingEncryptedDataCodecs.blindedRouteDataCodec.encode(TlvStream(
RouteBlindingEncryptedDataTlv.PaymentConstraints(routeExpiry, minAmount),
RouteBlindingEncryptedDataTlv.PathId(pathId),
+ RouteBlindingEncryptedDataTlv.UpgradeAccountability(),
)).require.bytes
val intermediatePayload = RouteBlindingEncryptedDataCodecs.blindedRouteDataCodec.encode(TlvStream[RouteBlindingEncryptedDataTlv](
RouteBlindingEncryptedDataTlv.OutgoingNodeId(EncodedNodeId.WithPublicKey.Wallet(hop.nextNodeId)),
RouteBlindingEncryptedDataTlv.PaymentRelay(hop.cltvExpiryDelta, hop.params.relayFees.feeProportionalMillionths, hop.params.relayFees.feeBase),
RouteBlindingEncryptedDataTlv.PaymentConstraints(routeExpiry, minAmount),
+ RouteBlindingEncryptedDataTlv.UpgradeAccountability(),
)).require.bytes
Sphinx.RouteBlinding.create(randomKey(), Seq(hop.nodeId, hop.nextNodeId), Seq(intermediatePayload, finalPayload))
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala
index 2a62118..e42c216 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala
@@ -24,9 +24,9 @@ import fr.acinq.eclair.channel.ChannelSpendSignature.PartialSignatureWithNonce
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.wire.protocol.CommonCodecs._
import fr.acinq.eclair.wire.protocol.TlvCodecs.{tlvField, tlvStream, tu16}
+import scodec.Codec
import scodec.bits.{ByteVector, HexStringSyntax}
import scodec.codecs._
-import scodec.{Attempt, Codec, Err}
/**
* Created by t-bast on 19/07/2021.
@@ -40,9 +40,15 @@ object UpdateAddHtlcTlv {
private val pathKey: Codec[PathKey] = (("length" | constant(hex"21")) :: ("pathKey" | publicKey)).as[PathKey]
- case class Endorsement(level: Int) extends UpdateAddHtlcTlv
+ /**
+ * When set, this field tells the receiving node that they will be held accountable if the corresponding HTLC doesn't
+ * resolve quickly, and their reputation will be lowered. They should propagate this signal when relaying to ensure
+ * that their downstream nodes are also accountable, which protects against channel jamming.
+ * See github.com/lightning/bolts/pull/1280 for more details.
+ */
+ case class Accountable() extends UpdateAddHtlcTlv
- private val endorsement: Codec[Endorsement] = tlvField(uint8.narrow[Endorsement](n => if (n >= 8) Attempt.failure(Err(s"invalid endorsement level: $n")) else Attempt.successful(Endorsement(n)), _.level))
+ private val accountable: Codec[Accountable] = tlvField(provide(Accountable()))
/** When on-the-fly funding is used, the liquidity fees may be taken from HTLCs relayed after funding. */
case class FundingFeeTlv(fee: LiquidityAds.FundingFee) extends UpdateAddHtlcTlv
@@ -51,8 +57,8 @@ object UpdateAddHtlcTlv {
val addHtlcTlvCodec: Codec[TlvStream[UpdateAddHtlcTlv]] = tlvStream(discriminated[UpdateAddHtlcTlv].by(varint)
.typecase(UInt64(0), pathKey)
+ .typecase(UInt64(1), accountable)
.typecase(UInt64(41041), fundingFee)
- .typecase(UInt64(106823), endorsement)
)
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala
index ba4d08f..099ebaf 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala
@@ -486,7 +486,7 @@ case class UpdateAddHtlc(channelId: ByteVector32,
val pathKey_opt: Option[PublicKey] = tlvStream.get[UpdateAddHtlcTlv.PathKey].map(_.publicKey)
val fundingFee_opt: Option[LiquidityAds.FundingFee] = tlvStream.get[UpdateAddHtlcTlv.FundingFeeTlv].map(_.fee)
- val endorsement: Int = tlvStream.get[UpdateAddHtlcTlv.Endorsement].map(_.level).getOrElse(0)
+ val accountable: Boolean = tlvStream.get[UpdateAddHtlcTlv.Accountable].nonEmpty
/** When storing in our DB, we avoid wasting storage with unknown data. */
def removeUnknownTlvs(): UpdateAddHtlc = this.copy(tlvStream = tlvStream.copy(unknown = Set.empty))
@@ -500,12 +500,12 @@ object UpdateAddHtlc {
cltvExpiry: CltvExpiry,
onionRoutingPacket: OnionRoutingPacket,
pathKey_opt: Option[PublicKey],
- endorsement: Int,
+ accountable: Boolean,
fundingFee_opt: Option[LiquidityAds.FundingFee]): UpdateAddHtlc = {
val tlvs = Set(
pathKey_opt.map(UpdateAddHtlcTlv.PathKey),
fundingFee_opt.map(UpdateAddHtlcTlv.FundingFeeTlv),
- Some(UpdateAddHtlcTlv.Endorsement(endorsement)),
+ if (accountable) Some(UpdateAddHtlcTlv.Accountable()) else None,
).flatten[UpdateAddHtlcTlv]
UpdateAddHtlc(channelId, id, amountMsat, paymentHash, cltvExpiry, onionRoutingPacket, TlvStream(tlvs))
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferCodecs.scala
index 73e3d1e..45dfcf5 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferCodecs.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferCodecs.scala
@@ -22,8 +22,8 @@ import fr.acinq.eclair.wire.protocol.CommonCodecs._
import fr.acinq.eclair.wire.protocol.OfferTypes._
import fr.acinq.eclair.wire.protocol.TlvCodecs.{tlvField, tmillisatoshi, tu32, tu64overflow}
import fr.acinq.eclair.{EncodedNodeId, TimestampSecond, UInt64}
-import scodec.{Attempt, Codec}
import scodec.codecs._
+import scodec.{Attempt, Codec}
import java.util.Currency
import scala.util.Try
@@ -34,7 +34,7 @@ object OfferCodecs {
private val offerMetadata: Codec[OfferMetadata] = tlvField(bytes)
val offerCurrency: Codec[OfferCurrency] =
- tlvField(utf8.narrow[Currency](s => Attempt.fromTry(Try{
+ tlvField(utf8.narrow[Currency](s => Attempt.fromTry(Try {
val c = Currency.getInstance(s)
require(c.getDefaultFractionDigits() >= 0) // getDefaultFractionDigits may return -1 for things that are not currencies
c
@@ -139,6 +139,8 @@ object OfferCodecs {
private val invoicePaths: Codec[InvoicePaths] = tlvField(nonEmptyList(blindedRouteCodec, "invoice_paths"))
+ private val invoiceAccountable: Codec[InvoiceAccountable] = tlvField(provide(InvoiceAccountable()))
+
val paymentInfo: Codec[PaymentInfo] =
(("fee_base_msat" | millisatoshi32) ::
("fee_proportional_millionths" | uint32) ::
@@ -187,6 +189,7 @@ object OfferCodecs {
.typecase(UInt64(89), invoiceRequestPayerNote)
// Invoice part
.typecase(UInt64(160), invoicePaths)
+ .typecase(UInt64(161), invoiceAccountable)
.typecase(UInt64(162), invoiceBlindedPay)
.typecase(UInt64(164), invoiceCreatedAt)
.typecase(UInt64(166), invoiceRelativeExpiry)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferTypes.scala
index d911e1e..3963518 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferTypes.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferTypes.scala
@@ -159,6 +159,12 @@ object OfferTypes {
*/
case class InvoicePaths(paths: Seq[BlindedRoute]) extends InvoiceTlv
+ /**
+ * By setting this field, we let the payer know that we will resolve the payment quickly once we receive it.
+ * If we don't, our reputation will be negatively impacted (channel jamming protection).
+ */
+ case class InvoiceAccountable() extends InvoiceTlv
+
case class PaymentInfo(feeBase: MilliSatoshi,
feeProportionalMillionths: Long,
cltvExpiryDelta: CltvExpiryDelta,
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PaymentOnion.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PaymentOnion.scala
index f32933e..bde3923 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PaymentOnion.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PaymentOnion.scala
@@ -188,6 +188,9 @@ object OnionPaymentPayloadTlv {
/** Blinded paths to relay the payment to */
case class OutgoingBlindedPaths(paths: Seq[PaymentBlindedRoute]) extends OnionPaymentPayloadTlv
+
+ /** Flag to allow forwarding nodes to set `accountable` in their `update_add_htlc` */
+ case class UpgradeAccountability() extends OnionPaymentPayloadTlv
}
object PaymentOnion {
@@ -219,6 +222,14 @@ object PaymentOnion {
/** Per-hop payload from an HTLC's payment onion (after decryption and decoding). */
sealed trait PerHopPayload {
def records: TlvStream[OnionPaymentPayloadTlv]
+
+ def upgradeAccountability: Boolean = records.get[UpgradeAccountability].nonEmpty
+ }
+
+ sealed trait BlindedPayload extends PerHopPayload {
+ def blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv]
+
+ override def upgradeAccountability: Boolean = blindedRecords.get[RouteBlindingEncryptedDataTlv.UpgradeAccountability].nonEmpty
}
/** Per-hop payload for an intermediate node. */
@@ -246,8 +257,14 @@ object PaymentOnion {
}
object Standard {
- def apply(outgoingChannelId: ShortChannelId, amountToForward: MilliSatoshi, outgoingCltv: CltvExpiry): Standard = {
- Standard(TlvStream(OnionPaymentPayloadTlv.AmountToForward(amountToForward), OnionPaymentPayloadTlv.OutgoingCltv(outgoingCltv), OnionPaymentPayloadTlv.OutgoingChannelId(outgoingChannelId)))
+ def apply(outgoingChannelId: ShortChannelId, amountToForward: MilliSatoshi, outgoingCltv: CltvExpiry, upgradeAccountability: Boolean): Standard = {
+ val tlvs: Set[OnionPaymentPayloadTlv] = Set(
+ Some(AmountToForward(amountToForward)),
+ Some(OutgoingCltv(outgoingCltv)),
+ Some(OutgoingChannelId(outgoingChannelId)),
+ if (upgradeAccountability) Some(UpgradeAccountability()) else None
+ ).flatten
+ Standard(TlvStream(tlvs))
}
def validate(records: TlvStream[OnionPaymentPayloadTlv]): Either[InvalidTlvPayload, Standard] = {
@@ -262,11 +279,12 @@ object PaymentOnion {
* @param paymentRelayData decrypted relaying data from the encrypted_recipient_data tlv.
* @param nextPathKey path key that must be forwarded to the next hop.
*/
- case class Blinded(records: TlvStream[OnionPaymentPayloadTlv], paymentRelayData: PaymentRelayData, nextPathKey: PublicKey) extends ChannelRelay {
+ case class Blinded(records: TlvStream[OnionPaymentPayloadTlv], paymentRelayData: PaymentRelayData, nextPathKey: PublicKey) extends ChannelRelay with BlindedPayload {
// @formatter:off
override val outgoing = paymentRelayData.outgoing
override def amountToForward(incomingAmount: MilliSatoshi): MilliSatoshi = paymentRelayData.amountToForward(incomingAmount)
override def outgoingCltv(incomingCltv: CltvExpiry): CltvExpiry = paymentRelayData.outgoingCltv(incomingCltv)
+ override val blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv] = paymentRelayData.records
// @formatter:on
}
@@ -309,8 +327,14 @@ object PaymentOnion {
}
object Standard {
- def apply(amount: MilliSatoshi, expiry: CltvExpiry, nextNodeId: PublicKey): Standard = {
- Standard(TlvStream(AmountToForward(amount), OutgoingCltv(expiry), OutgoingNodeId(nextNodeId)))
+ def apply(amount: MilliSatoshi, expiry: CltvExpiry, nextNodeId: PublicKey, upgradeAccountability: Boolean): Standard = {
+ val tlvs: Set[OnionPaymentPayloadTlv] = Set(
+ Some(AmountToForward(amount)),
+ Some(OutgoingCltv(expiry)),
+ Some(OutgoingNodeId(nextNodeId)),
+ if (upgradeAccountability) Some(UpgradeAccountability()) else None
+ ).flatten
+ Standard(TlvStream(tlvs))
}
def validate(records: TlvStream[OnionPaymentPayloadTlv]): Either[InvalidTlvPayload, Standard] = {
@@ -323,8 +347,15 @@ object PaymentOnion {
}
/** Create a standard trampoline inner payload instructing the trampoline node to wait for a trigger before sending an async payment. */
- def createNodeRelayForAsyncPayment(amount: MilliSatoshi, expiry: CltvExpiry, nextNodeId: PublicKey): Standard = {
- Standard(TlvStream(AmountToForward(amount), OutgoingCltv(expiry), OutgoingNodeId(nextNodeId), AsyncPayment()))
+ def createNodeRelayForAsyncPayment(amount: MilliSatoshi, expiry: CltvExpiry, nextNodeId: PublicKey, upgradeAccountability: Boolean): Standard = {
+ val tlvs: Set[OnionPaymentPayloadTlv] = Set(
+ Some(AmountToForward(amount)),
+ Some(OutgoingCltv(expiry)),
+ Some(OutgoingNodeId(nextNodeId)),
+ Some(AsyncPayment()),
+ if (upgradeAccountability) Some(UpgradeAccountability()) else None
+ ).flatten
+ Standard(TlvStream(tlvs))
}
}
@@ -357,7 +388,8 @@ object PaymentOnion {
invoice.paymentMetadata.map(m => PaymentMetadata(m)),
Some(OutgoingNodeId(targetNodeId)),
Some(InvoiceFeatures(invoice.features.toByteVector)),
- Some(InvoiceRoutingInfo(invoice.routingInfo.toList.map(_.toList)))
+ Some(InvoiceRoutingInfo(invoice.routingInfo.toList.map(_.toList))),
+ if (invoice.accountable) Some(UpgradeAccountability()) else None
).flatten
ToNonTrampoline(TlvStream(tlvs))
}
@@ -394,6 +426,7 @@ object PaymentOnion {
Some(OutgoingCltv(expiry)),
Some(OutgoingBlindedPaths(invoice.blindedPaths)),
Some(InvoiceFeatures(invoice.features.toByteVector)),
+ if (invoice.accountable) Some(UpgradeAccountability()) else None
).flatten
ToBlindedPaths(TlvStream(tlvs))
}
@@ -443,13 +476,14 @@ object PaymentOnion {
Right(Standard(records))
}
- def createPayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, paymentSecret: ByteVector32, paymentMetadata: Option[ByteVector] = None, trampolineOnion_opt: Option[OnionRoutingPacket] = None, customTlvs: Set[GenericTlv] = Set.empty): Standard = {
+ def createPayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, paymentSecret: ByteVector32, paymentMetadata: Option[ByteVector] = None, trampolineOnion_opt: Option[OnionRoutingPacket] = None, customTlvs: Set[GenericTlv] = Set.empty, upgradeAccountability: Boolean): Standard = {
val tlvs: Set[OnionPaymentPayloadTlv] = Set(
Some(AmountToForward(amount)),
Some(OutgoingCltv(expiry)),
Some(PaymentData(paymentSecret, totalAmount)),
paymentMetadata.map(m => PaymentMetadata(m)),
trampolineOnion_opt.map(o => TrampolineOnion(o)),
+ if (upgradeAccountability) Some(UpgradeAccountability()) else None
).flatten
Standard(TlvStream(tlvs, customTlvs))
}
@@ -464,15 +498,22 @@ object PaymentOnion {
}
/** Create a trampoline outer payload. */
- def createTrampolinePayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, paymentSecret: ByteVector32, trampolinePacket: OnionRoutingPacket): Standard = {
- Standard(TlvStream(AmountToForward(amount), OutgoingCltv(expiry), PaymentData(paymentSecret, totalAmount), TrampolineOnion(trampolinePacket)))
+ def createTrampolinePayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, paymentSecret: ByteVector32, trampolinePacket: OnionRoutingPacket, upgradeAccountability: Boolean): Standard = {
+ val tlvs: Set[OnionPaymentPayloadTlv] = Set(
+ Some(AmountToForward(amount)),
+ Some(OutgoingCltv(expiry)),
+ Some(PaymentData(paymentSecret, totalAmount)),
+ Some(TrampolineOnion(trampolinePacket)),
+ if (upgradeAccountability) Some(UpgradeAccountability()) else None
+ ).flatten
+ Standard(TlvStream(tlvs))
}
}
/**
* @param blindedRecords decrypted tlv stream from the encrypted_recipient_data tlv.
*/
- case class Blinded(records: TlvStream[OnionPaymentPayloadTlv], blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv]) extends FinalPayload {
+ case class Blinded(records: TlvStream[OnionPaymentPayloadTlv], blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv]) extends FinalPayload with BlindedPayload {
override val amount = records.get[AmountToForward].get.amount
override val totalAmount = records.get[TotalAmount].map(_.totalAmount).getOrElse(amount)
override val expiry = records.get[OutgoingCltv].get.cltv
@@ -543,8 +584,14 @@ object PaymentOnion {
}
object TrampolineWithoutMppPayload {
- def create(amount: MilliSatoshi, expiry: CltvExpiry, trampolinePacket: OnionRoutingPacket): TrampolineWithoutMppPayload = {
- TrampolineWithoutMppPayload(TlvStream(AmountToForward(amount), OutgoingCltv(expiry), TrampolineOnion(trampolinePacket)))
+ def create(amount: MilliSatoshi, expiry: CltvExpiry, trampolinePacket: OnionRoutingPacket, upgradeAccountability: Boolean): TrampolineWithoutMppPayload = {
+ val tlvs: Set[OnionPaymentPayloadTlv] = Set(
+ Some(AmountToForward(amount)),
+ Some(OutgoingCltv(expiry)),
+ Some(TrampolineOnion(trampolinePacket)),
+ if (upgradeAccountability) Some(UpgradeAccountability()) else None
+ ).flatten
+ TrampolineWithoutMppPayload(TlvStream(tlvs))
}
}
@@ -586,6 +633,8 @@ object PaymentOnionCodecs {
private val totalAmount: Codec[TotalAmount] = tlvField(tmillisatoshi)
+ private val upgradeAccountability: Codec[UpgradeAccountability] = tlvField(provide(UpgradeAccountability()))
+
private val invoiceFeatures: Codec[InvoiceFeatures] = tlvField(bytes)
private val invoiceRoutingInfo: Codec[InvoiceRoutingInfo] = tlvField(list(listOfN(uint8, Bolt11Invoice.Codecs.extraHopCodec)))
@@ -612,6 +661,7 @@ object PaymentOnionCodecs {
.typecase(UInt64(12), pathKey)
.typecase(UInt64(16), paymentMetadata)
.typecase(UInt64(18), totalAmount)
+ .typecase(UInt64(19), upgradeAccountability)
// Types below aren't specified - use cautiously when deploying (be careful with backwards-compatibility).
.typecase(UInt64(66097), invoiceFeatures)
.typecase(UInt64(66098), outgoingNodeId)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RouteBlinding.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RouteBlinding.scala
index 591f762..aeaac7d 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RouteBlinding.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RouteBlinding.scala
@@ -40,6 +40,9 @@ object RouteBlindingEncryptedDataTlv {
/** Id of the outgoing channel, used to identify the next node. */
case class OutgoingChannelId(shortChannelId: ShortChannelId) extends RouteBlindingEncryptedDataTlv
+ /** Flag to allow forwarding nodes to set `accountable` in their `update_add_htlc` */
+ case class UpgradeAccountability() extends RouteBlindingEncryptedDataTlv
+
/**
* Id of the next node.
*
@@ -138,6 +141,7 @@ object RouteBlindingEncryptedDataCodecs {
private val padding: Codec[Padding] = tlvField(bytes)
private val outgoingChannelId: Codec[OutgoingChannelId] = tlvField(shortchannelid)
+ private val upgradeAccountability: Codec[UpgradeAccountability] = tlvField(provide(UpgradeAccountability()))
private val outgoingNodeId: Codec[OutgoingNodeId] = tlvField(encodedNodeIdCodec)
private val pathId: Codec[PathId] = tlvField(bytes)
private val nextPathKey: Codec[NextPathKey] = fixedLengthTlvField(33, publicKey)
@@ -148,6 +152,7 @@ object RouteBlindingEncryptedDataCodecs {
private val encryptedDataTlvCodec = discriminated[RouteBlindingEncryptedDataTlv].by(varint)
.typecase(UInt64(1), padding)
.typecase(UInt64(2), outgoingChannelId)
+ .typecase(UInt64(3), upgradeAccountability)
.typecase(UInt64(4), outgoingNodeId)
.typecase(UInt64(6), pathId)
.typecase(UInt64(8), nextPathKey)
diff --git a/eclair-core/src/test/resources/nonreg/codecs/050006-DATA_NORMAL/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/050006-DATA_NORMAL/funder/data.json
index bca7cfc..fcb84b4 100644
--- a/eclair-core/src/test/resources/nonreg/codecs/050006-DATA_NORMAL/funder/data.json
+++ b/eclair-core/src/test/resources/nonreg/codecs/050006-DATA_NORMAL/funder/data.json
@@ -80,9 +80,7 @@
"hmac" : "5ee09d8f0b0ae937a4307b4c7dfeb299a15d09a15fd3f0998867ddf1f6d042fa"
},
"tlvStream" : {
- "Endorsement" : {
- "level" : 7
- }
+ "Unknown106823" : "07"
}
}, {
"channelId" : "4b851a62adaa0c0c003027706666d9e860253463d81971eb22089bb534f9b526",
@@ -97,9 +95,7 @@
"hmac" : "5cf7762cbc8ece0a15ba576d4b1d1910b376f2d19f524671275e6e4be33b4d8d"
},
"tlvStream" : {
- "Endorsement" : {
- "level" : 7
- }
+ "Unknown106823" : "07"
}
}, {
"channelId" : "4b851a62adaa0c0c003027706666d9e860253463d81971eb22089bb534f9b526",
@@ -114,9 +110,7 @@
"hmac" : "e56ad161c160fecc76fd4668459464898800acbb4e99c2f96219380189b059bd"
},
"tlvStream" : {
- "Endorsement" : {
- "level" : 7
- }
+ "Unknown106823" : "07"
}
}, {
"channelId" : "4b851a62adaa0c0c003027706666d9e860253463d81971eb22089bb534f9b526",
@@ -131,9 +125,7 @@
"hmac" : "f489b7419edba57764791019ee303acc27b6a6171c1bc4d296576186c47e2249"
},
"tlvStream" : {
- "Endorsement" : {
- "level" : 7
- }
+ "Unknown106823" : "07"
}
} ],
"acked" : [ ]
@@ -152,9 +144,7 @@
"hmac" : "fc171d9f300e254a9ffa2b3a23eae54cf8bc03f7ab239142469b11179cefd72d"
},
"tlvStream" : {
- "Endorsement" : {
- "level" : 7
- }
+ "Unknown106823" : "07"
}
}, {
"channelId" : "4b851a62adaa0c0c003027706666d9e860253463d81971eb22089bb534f9b526",
@@ -169,9 +159,7 @@
"hmac" : "ad3279099f3968fee2b1cc21d383e3771116b9e334ef766049b43d782a7fbe51"
},
"tlvStream" : {
- "Endorsement" : {
- "level" : 7
- }
+ "Unknown106823" : "07"
}
}, {
"channelId" : "4b851a62adaa0c0c003027706666d9e860253463d81971eb22089bb534f9b526",
@@ -186,9 +174,7 @@
"hmac" : "710e9ee11b2463fe48ee38f0f144dc2f750e5ed7598e722e5894008361093522"
},
"tlvStream" : {
- "Endorsement" : {
- "level" : 7
- }
+ "Unknown106823" : "07"
}
} ],
"acked" : [ ],
diff --git a/eclair-core/src/test/resources/nonreg/codecs/05000a-DATA_CLOSING/next-remote/data.json b/eclair-core/src/test/resources/nonreg/codecs/05000a-DATA_CLOSING/next-remote/data.json
index 8a2b872..e76c48f 100644
--- a/eclair-core/src/test/resources/nonreg/codecs/05000a-DATA_CLOSING/next-remote/data.json
+++ b/eclair-core/src/test/resources/nonreg/codecs/05000a-DATA_CLOSING/next-remote/data.json
@@ -90,9 +90,7 @@
"hmac" : "3ae1ea4bbe67becc89c99d333c245e44e8d94be0e29248a1c27d0fc9d1f0b004"
},
"tlvStream" : {
- "Endorsement" : {
- "level" : 7
- }
+ "Unknown106823" : "07"
}
} ],
"acked" : [ ]
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala
index 97b8a51..3a43f34 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala
@@ -183,6 +183,7 @@ object TestConstants {
enforcementDelay = 10 minutes,
asyncPaymentsParams = AsyncPaymentsParams(1008, CltvExpiryDelta(144)),
peerReputationConfig = Reputation.Config(enabled = true, 1 day, 10 minutes),
+ reservedBucket = 0.4,
),
db = TestDatabases.inMemoryDb(),
autoReconnect = false,
@@ -374,6 +375,7 @@ object TestConstants {
enforcementDelay = 10 minutes,
asyncPaymentsParams = AsyncPaymentsParams(1008, CltvExpiryDelta(144)),
peerReputationConfig = Reputation.Config(enabled = true, 2 day, 20 minutes),
+ reservedBucket = 0.3,
),
db = TestDatabases.inMemoryDb(),
autoReconnect = false,
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala
index 3aaa7f2..6b03a0a 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala
@@ -409,7 +409,7 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("can receive availableForReceive") { f =>
for (isInitiator <- Seq(true, false)) {
val c = CommitmentsSpec.makeCommitments(31000000 msat, 702000000 msat, FeeratePerKw(2679 sat), 546 sat, isInitiator)
- val add = UpdateAddHtlc(randomBytes32(), c.changes.remoteNextHtlcId, c.availableBalanceForReceive, randomBytes32(), CltvExpiry(f.currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add = UpdateAddHtlc(randomBytes32(), c.changes.remoteNextHtlcId, c.availableBalanceForReceive, randomBytes32(), CltvExpiry(f.currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
c.receiveAdd(add)
}
}
@@ -460,14 +460,14 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// Add some initial HTLCs to the pending list (bigger commit tx).
for (_ <- 1 to t.pendingHtlcs) {
val amount = Random.nextInt(maxPendingHtlcAmount.toLong.toInt).msat.max(1 msat)
- val add = UpdateAddHtlc(randomBytes32(), c.changes.remoteNextHtlcId, amount, randomBytes32(), CltvExpiry(f.currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add = UpdateAddHtlc(randomBytes32(), c.changes.remoteNextHtlcId, amount, randomBytes32(), CltvExpiry(f.currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
c.receiveAdd(add) match {
case Right(cc) => c = cc
case Left(e) => ignore(s"$t -> could not setup initial htlcs: $e")
}
}
if (c.availableBalanceForReceive > 0.msat) {
- val add = UpdateAddHtlc(randomBytes32(), c.changes.remoteNextHtlcId, c.availableBalanceForReceive, randomBytes32(), CltvExpiry(f.currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add = UpdateAddHtlc(randomBytes32(), c.changes.remoteNextHtlcId, c.availableBalanceForReceive, randomBytes32(), CltvExpiry(f.currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
c.receiveAdd(add) match {
case Right(_) => ()
case Left(e) => fail(s"$t -> $e")
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/DustExposureSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/DustExposureSpec.scala
index 6c94ae4..9e72e22 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/DustExposureSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/DustExposureSpec.scala
@@ -27,7 +27,7 @@ import org.scalatest.funsuite.AnyFunSuiteLike
class DustExposureSpec extends AnyFunSuiteLike {
def createHtlc(id: Long, amount: MilliSatoshi): UpdateAddHtlc = {
- UpdateAddHtlc(ByteVector32.Zeroes, id, amount, randomBytes32(), CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ UpdateAddHtlc(ByteVector32.Zeroes, id, amount, randomBytes32(), CltvExpiry(500), TestConstants.emptyOnionPacket, None, accountable = false, None)
}
test("compute dust exposure") {
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala
index 0c48fa8..86c3d4b 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala
@@ -122,7 +122,7 @@ class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Channe
// allow overpaying (no more than 2 times the required amount)
val amount = requiredAmount + Random.nextInt(requiredAmount.toLong.toInt).msat
val expiry = (Channel.MIN_CLTV_EXPIRY_DELTA + 1).toCltvExpiry(currentBlockHeight = BlockHeight(400000))
- val Right(payment) = OutgoingPaymentPacket.buildOutgoingPayment(localOrigin(self), invoice.paymentHash, makeSingleHopRoute(amount, invoice.nodeId), ClearRecipient(invoice, amount, expiry, Set.empty), Reputation.Score.max)
+ val Right(payment) = OutgoingPaymentPacket.buildOutgoingPayment(localOrigin(self), invoice.paymentHash, makeSingleHopRoute(amount, invoice.nodeId), ClearRecipient(invoice, amount, expiry, Set.empty), Reputation.Score.max(accountable = false))
payment.cmd
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala
index d0234a8..349b6d4 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala
@@ -69,14 +69,14 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat
awaitCond(bob.stateName == NORMAL)
// We have two identical HTLCs (MPP):
val (_, htlca1a) = addHtlc(15_000_000 msat, alice, bob, alice2bob, bob2alice)
- val aliceMppCmd = CMD_ADD_HTLC(TestProbe().ref, 15_000_000 msat, htlca1a.paymentHash, htlca1a.cltvExpiry, htlca1a.onionRoutingPacket, None, Reputation.Score.max, None, Origin.Hot(TestProbe().ref, Upstream.Local(UUID.randomUUID())))
+ val aliceMppCmd = CMD_ADD_HTLC(TestProbe().ref, 15_000_000 msat, htlca1a.paymentHash, htlca1a.cltvExpiry, htlca1a.onionRoutingPacket, None, Reputation.Score.max(accountable = false), None, Origin.Hot(TestProbe().ref, Upstream.Local(UUID.randomUUID())))
val htlca1b = addHtlc(aliceMppCmd, alice, bob, alice2bob, bob2alice)
val (ra2, htlca2) = addHtlc(16_000_000 msat, alice, bob, alice2bob, bob2alice)
addHtlc(500_000 msat, alice, bob, alice2bob, bob2alice) // below dust
crossSign(alice, bob, alice2bob, bob2alice)
// We have two identical HTLCs (MPP):
val (_, htlcb1a) = addHtlc(17_000_000 msat, bob, alice, bob2alice, alice2bob)
- val bobMppCmd = CMD_ADD_HTLC(TestProbe().ref, 17_000_000 msat, htlcb1a.paymentHash, htlcb1a.cltvExpiry, htlcb1a.onionRoutingPacket, None, Reputation.Score.max, None, Origin.Hot(TestProbe().ref, Upstream.Local(UUID.randomUUID())))
+ val bobMppCmd = CMD_ADD_HTLC(TestProbe().ref, 17_000_000 msat, htlcb1a.paymentHash, htlcb1a.cltvExpiry, htlcb1a.onionRoutingPacket, None, Reputation.Score.max(accountable = false), None, Origin.Hot(TestProbe().ref, Upstream.Local(UUID.randomUUID())))
val htlcb1b = addHtlc(bobMppCmd, bob, alice, bob2alice, alice2bob)
val (rb2, htlcb2) = addHtlc(18_000_000 msat, bob, alice, bob2alice, alice2bob)
addHtlc(400_000 msat, bob, alice, bob2alice, alice2bob) // below dust
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala
index a2aa6ae..331e089 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala
@@ -490,7 +490,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
val paymentHash = Crypto.sha256(paymentPreimage)
val expiry = cltvExpiryDelta.toCltvExpiry(currentBlockHeight)
val recipient = SpontaneousRecipient(destination, amount, expiry, paymentPreimage)
- val Right(payment) = OutgoingPaymentPacket.buildOutgoingPayment(Origin.Hot(replyTo, upstream), paymentHash, makeSingleHopRoute(amount, destination), recipient, Reputation.Score.max)
+ val Right(payment) = OutgoingPaymentPacket.buildOutgoingPayment(Origin.Hot(replyTo, upstream), paymentHash, makeSingleHopRoute(amount, destination), recipient, Reputation.Score.max(accountable = false))
(paymentPreimage, payment.cmd.copy(commit = false))
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala
index 668f445..a7d7e00 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala
@@ -148,7 +148,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
// initiator should reject commands that change the commitment once it became quiescent
val sender1, sender2, sender3 = TestProbe()
val cmds = Seq(
- CMD_ADD_HTLC(sender1.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender1.ref)),
+ CMD_ADD_HTLC(sender1.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender1.ref)),
CMD_UPDATE_FEE(FeeratePerKw(100 sat), replyTo_opt = Some(sender2.ref)),
CMD_CLOSE(sender3.ref, None, None)
)
@@ -164,7 +164,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
// both should reject commands that change the commitment while quiescent
val sender1, sender2, sender3 = TestProbe()
val cmds = Seq(
- CMD_ADD_HTLC(sender1.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender1.ref)),
+ CMD_ADD_HTLC(sender1.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender1.ref)),
CMD_UPDATE_FEE(FeeratePerKw(100 sat), replyTo_opt = Some(sender2.ref)),
CMD_CLOSE(sender3.ref, None, None)
)
@@ -352,7 +352,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
import f._
initiateQuiescence(f, sendInitialStfu = true)
// have to build a htlc manually because eclair would refuse to accept this command as it's forbidden
- val forbiddenMsg = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, endorsement = Reputation.maxEndorsement, fundingFee_opt = None)
+ val forbiddenMsg = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, accountable = false, fundingFee_opt = None)
// both parties will respond to a forbidden msg while quiescent with a warning (and disconnect)
bob2alice.forward(alice, forbiddenMsg)
alice2bob.expectMsg(Warning(channelId(alice), ForbiddenDuringSplice(channelId(alice), "UpdateAddHtlc").getMessage))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala
index 0ed9ef6..f9bd932 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala
@@ -1194,7 +1194,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
val sender = TestProbe()
// command for a large payment (larger than local balance pre-slice)
- val cmd = CMD_ADD_HTLC(sender.ref, 1_000_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val cmd = CMD_ADD_HTLC(sender.ref, 1_000_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
// first attempt at payment fails (not enough balance)
alice ! cmd
sender.expectMsgType[RES_ADD_FAILED[_]]
@@ -1634,7 +1634,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
import f._
initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)))
val sender = TestProbe()
- alice ! CMD_ADD_HTLC(sender.ref, 500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
@@ -1658,7 +1658,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)))
bob2blockchain.expectWatchFundingConfirmed(spliceTx.txid)
val sender = TestProbe()
- alice ! CMD_ADD_HTLC(sender.ref, 500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
@@ -1685,7 +1685,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)))
val sender = TestProbe()
val preimage = randomBytes32()
- alice ! CMD_ADD_HTLC(sender.ref, 500_000 msat, Crypto.sha256(preimage), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 500_000 msat, Crypto.sha256(preimage), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val add = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
@@ -1719,7 +1719,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice ! cmd
exchangeStfu(f)
alice2bob.expectMsgType[SpliceInit]
- alice ! CMD_ADD_HTLC(sender.ref, 500000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 500000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_ADD_FAILED[_]]
alice2bob.expectNoMessage(100 millis)
}
@@ -1735,7 +1735,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
bob2alice.expectMsgType[SpliceAck]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxAddInput]
- alice ! CMD_ADD_HTLC(sender.ref, 500000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 500000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_ADD_FAILED[_]]
alice2bob.expectNoMessage(100 millis)
}
@@ -1753,7 +1753,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2bob.expectMsgType[TxAddInput]
// have to build a htlc manually because eclair would refuse to accept this command as it's forbidden
- val fakeHtlc = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, endorsement = Reputation.maxEndorsement, fundingFee_opt = None)
+ val fakeHtlc = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, accountable = false, fundingFee_opt = None)
bob2alice.forward(alice, fakeHtlc)
// alice returns a warning and schedules a disconnect after receiving UpdateAddHtlc
alice2bob.expectMsg(Warning(channelId(alice), ForbiddenDuringSplice(channelId(alice), "UpdateAddHtlc").getMessage))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala
index afe94bf..d0e8027 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala
@@ -79,7 +79,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val listener = TestProbe()
alice.underlying.system.eventStream.subscribe(listener.ref, classOf[AvailableBalanceChanged])
val h = randomBytes32()
- val add = CMD_ADD_HTLC(sender.ref, 50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val e = listener.expectMsgType[AvailableBalanceChanged]
@@ -105,7 +105,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val sender = TestProbe()
val h = randomBytes32()
for (i <- 0 until 10) {
- alice ! CMD_ADD_HTLC(sender.ref, 500000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 500000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
assert(htlc.id == i && htlc.paymentHash == h)
@@ -117,9 +117,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val sender = TestProbe()
val h = randomBytes32()
- val originHtlc = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = h, onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, endorsement = Reputation.maxEndorsement, fundingFee_opt = None)
+ val originHtlc = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = h, onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, accountable = false, fundingFee_opt = None)
val origin = Origin.Hot(sender.ref, Upstream.Hot.Channel(originHtlc, TimestampMilli.now(), randomKey().publicKey, 0.1))
- val cmd = CMD_ADD_HTLC(sender.ref, originHtlc.amountMsat - 10_000.msat, h, originHtlc.cltvExpiry - CltvExpiryDelta(7), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, origin)
+ val cmd = CMD_ADD_HTLC(sender.ref, originHtlc.amountMsat - 10_000.msat, h, originHtlc.cltvExpiry - CltvExpiryDelta(7), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, origin)
alice ! cmd
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
@@ -135,10 +135,10 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val sender = TestProbe()
val h = randomBytes32()
- val originHtlc1 = UpdateAddHtlc(randomBytes32(), 47, 30000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- val originHtlc2 = UpdateAddHtlc(randomBytes32(), 32, 20000000 msat, h, CltvExpiryDelta(160).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val originHtlc1 = UpdateAddHtlc(randomBytes32(), 47, 30000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
+ val originHtlc2 = UpdateAddHtlc(randomBytes32(), 32, 20000000 msat, h, CltvExpiryDelta(160).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
val origin = Origin.Hot(sender.ref, Upstream.Hot.Trampoline(List(originHtlc1, originHtlc2).map(htlc => Upstream.Hot.Channel(htlc, TimestampMilli.now(), randomKey().publicKey, 0.1))))
- val cmd = CMD_ADD_HTLC(sender.ref, originHtlc1.amountMsat + originHtlc2.amountMsat - 10000.msat, h, originHtlc2.cltvExpiry - CltvExpiryDelta(7), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, origin)
+ val cmd = CMD_ADD_HTLC(sender.ref, originHtlc1.amountMsat + originHtlc2.amountMsat - 10000.msat, h, originHtlc2.cltvExpiry - CltvExpiryDelta(7), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, origin)
alice ! cmd
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
@@ -154,7 +154,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val expiryTooSmall = CltvExpiry(currentBlockHeight)
- val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, randomBytes32(), expiryTooSmall, TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, randomBytes32(), expiryTooSmall, TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = ExpiryTooSmall(channelId(alice), CltvExpiry(currentBlockHeight + 3), expiryTooSmall, currentBlockHeight)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -167,7 +167,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val maxAllowedExpiryDelta = alice.underlyingActor.nodeParams.channelConf.maxExpiryDelta
val expiryTooBig = (maxAllowedExpiryDelta + 1).toCltvExpiry(currentBlockHeight)
- val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, randomBytes32(), expiryTooBig, TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, randomBytes32(), expiryTooBig, TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = ExpiryTooBig(channelId(alice), maximum = maxAllowedExpiryDelta.toCltvExpiry(currentBlockHeight), actual = expiryTooBig, blockHeight = currentBlockHeight)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -178,7 +178,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
- val add = CMD_ADD_HTLC(sender.ref, 50 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 50 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = HtlcValueTooSmall(channelId(alice), 1000 msat, 50 msat)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -191,7 +191,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// Alice has a minimum set to 0 msat (which should be invalid, but may mislead Bob into relaying 0-value HTLCs which is forbidden by the spec).
assert(alice.commitments.latest.localCommitParams.htlcMinimum == 0.msat)
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
- val add = CMD_ADD_HTLC(sender.ref, 0 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 0 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
bob ! add
val error = HtlcValueTooSmall(channelId(bob), 1 msat, 0 msat)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -203,7 +203,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val sender = TestProbe()
// channel starts with all funds on alice's side, alice sends some funds to bob, but not enough to make it go above reserve
val h = randomBytes32()
- val add = CMD_ADD_HTLC(sender.ref, 50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
}
@@ -212,7 +212,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
- val add = CMD_ADD_HTLC(sender.ref, initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = InsufficientFunds(channelId(alice), amount = add.amount, missing = 0 sat, reserve = 20000 sat, fees = 3900 sat)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -223,7 +223,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
- val add = CMD_ADD_HTLC(sender.ref, initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
bob ! add
val error = InsufficientFunds(channelId(alice), amount = add.amount, missing = 0 sat, reserve = 10000 sat, fees = 0 sat)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -240,7 +240,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// At this point alice has the minimal amount to sustain a channel.
// Alice maintains an extra reserve to accommodate for a few more HTLCs, so the first few HTLCs should be allowed.
val htlcs = (1 to 9).map { _ =>
- bob ! CMD_ADD_HTLC(sender.ref, 12_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ bob ! CMD_ADD_HTLC(sender.ref, 12_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val add = bob2alice.expectMsgType[UpdateAddHtlc]
bob2alice.forward(alice, add)
@@ -248,7 +248,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}
// But this one will dip alice below her reserve: we must wait for the previous HTLCs to settle before sending any more.
- val failedAdd = CMD_ADD_HTLC(sender.ref, 11_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val failedAdd = CMD_ADD_HTLC(sender.ref, 11_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
bob ! failedAdd
val error = RemoteCannotAffordFeesForNewHtlc(channelId(bob), failedAdd.amount, missing = 200 sat, 20_000 sat, 8_200 sat)
sender.expectMsg(RES_ADD_FAILED(failedAdd, error, Some(bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate)))
@@ -274,7 +274,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// At this point alice has the minimal amount to sustain a channel.
// Alice maintains an extra reserve to accommodate for a few more HTLCs, so the first few HTLCs should be allowed.
val htlcs = (1 to 4).map { _ =>
- bob ! CMD_ADD_HTLC(sender.ref, 25_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ bob ! CMD_ADD_HTLC(sender.ref, 25_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val add = bob2alice.expectMsgType[UpdateAddHtlc]
bob2alice.forward(alice, add)
@@ -282,7 +282,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}
// But this one will dip alice below her reserve: we must wait for the previous HTLCs to settle before sending any more.
- val failedAdd = CMD_ADD_HTLC(sender.ref, 25_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val failedAdd = CMD_ADD_HTLC(sender.ref, 25_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
bob ! failedAdd
val error = RemoteCannotAffordFeesForNewHtlc(channelId(bob), failedAdd.amount, missing = 206 sat, 20_000 sat, 8_206 sat)
sender.expectMsg(RES_ADD_FAILED(failedAdd, error, Some(bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate)))
@@ -298,16 +298,16 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
- alice ! CMD_ADD_HTLC(sender.ref, 500_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 500_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
- alice ! CMD_ADD_HTLC(sender.ref, 200_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 200_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
- alice ! CMD_ADD_HTLC(sender.ref, 70_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 70_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
- val add = CMD_ADD_HTLC(sender.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = InsufficientFunds(channelId(alice), amount = 1_000_000 msat, missing = 1580 sat, reserve = 20_000 sat, fees = 5_190 sat)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -318,13 +318,13 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
- alice ! CMD_ADD_HTLC(sender.ref, 300_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 300_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
- alice ! CMD_ADD_HTLC(sender.ref, 300_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 300_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
- val add = CMD_ADD_HTLC(sender.ref, 500_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 500_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = InsufficientFunds(channelId(alice), amount = 500_000_000 msat, missing = 329_720 sat, reserve = 20_000 sat, fees = 4_760 sat)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -337,7 +337,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
assert(initialState.commitments.latest.localCommitParams.maxHtlcValueInFlight == UInt64(initialState.commitments.latest.capacity.toMilliSatoshi.toLong))
assert(initialState.commitments.latest.remoteCommitParams.maxHtlcValueInFlight == UInt64(150_000_000))
- val add = CMD_ADD_HTLC(sender.ref, 151_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 151_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
bob ! add
val error = HtlcValueTooHighInFlight(channelId(bob), maximum = UInt64(150_000_000), actual = 151_000_000 msat)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -350,11 +350,11 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
assert(initialState.commitments.latest.localCommitParams.maxHtlcValueInFlight == UInt64(initialState.commitments.latest.capacity.toMilliSatoshi.toLong))
assert(initialState.commitments.latest.remoteCommitParams.maxHtlcValueInFlight == UInt64(150_000_000))
- val add = CMD_ADD_HTLC(sender.ref, 75_500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 75_500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
bob ! add
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
bob2alice.expectMsgType[UpdateAddHtlc]
- val add1 = CMD_ADD_HTLC(sender.ref, 75_500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add1 = CMD_ADD_HTLC(sender.ref, 75_500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
bob ! add1
val error = HtlcValueTooHighInFlight(channelId(bob), maximum = UInt64(150_000_000), actual = 151_000_000 msat)
sender.expectMsg(RES_ADD_FAILED(add1, error, Some(initialState.channelUpdate)))
@@ -367,7 +367,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
assert(initialState.commitments.latest.localCommitParams.maxHtlcValueInFlight == UInt64(150_000_000))
assert(initialState.commitments.latest.remoteCommitParams.maxHtlcValueInFlight == UInt64(initialState.commitments.latest.capacity.toMilliSatoshi.toLong))
- val add = CMD_ADD_HTLC(sender.ref, 151_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 151_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = HtlcValueTooHighInFlight(channelId(alice), maximum = UInt64(150_000_000), actual = 151_000_000 msat)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -381,11 +381,11 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(initialState.commitments.latest.localCommitParams.maxAcceptedHtlcs == 100)
assert(initialState.commitments.latest.remoteCommitParams.maxAcceptedHtlcs == 30) // Bob accepts a maximum of 30 htlcs
for (_ <- 0 until 30) {
- alice ! CMD_ADD_HTLC(sender.ref, 10_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 10_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
}
- val add = CMD_ADD_HTLC(sender.ref, 10_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 10_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = TooManyAcceptedHtlcs(channelId(alice), maximum = 30)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -399,11 +399,11 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(initialState.commitments.latest.localCommitParams.maxAcceptedHtlcs == 30) // Bob accepts a maximum of 30 htlcs
assert(initialState.commitments.latest.remoteCommitParams.maxAcceptedHtlcs == 100) // Alice accepts more, but Bob will stop at 30 HTLCs
for (_ <- 0 until 30) {
- bob ! CMD_ADD_HTLC(sender.ref, 500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ bob ! CMD_ADD_HTLC(sender.ref, 500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
bob2alice.expectMsgType[UpdateAddHtlc]
}
- val add = CMD_ADD_HTLC(sender.ref, 500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 500_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
bob ! add
val error = TooManyAcceptedHtlcs(channelId(bob), maximum = 30)
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -432,12 +432,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
crossSign(bob, alice, bob2alice, alice2bob)
// HTLCs that take Alice's dust exposure above her threshold are rejected.
- val dustAdd = CMD_ADD_HTLC(sender.ref, 501.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val dustAdd = CMD_ADD_HTLC(sender.ref, 501.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! dustAdd
sender.expectMsg(RES_ADD_FAILED(dustAdd, LocalDustHtlcExposureTooHigh(channelId(alice), 25_000.sat, 25_001.sat.toMilliSatoshi), Some(initialState.channelUpdate)))
// HTLCs that don't contribute to dust exposure are accepted.
- alice ! CMD_ADD_HTLC(sender.ref, 25_000.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 25_000.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
}
@@ -460,7 +460,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
addHtlc(900.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice)
// HTLCs that take Alice's dust exposure above her threshold are rejected.
- val add = CMD_ADD_HTLC(sender.ref, 701.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 701.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
sender.expectMsg(RES_ADD_FAILED(add, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 25001.sat.toMilliSatoshi), Some(initialState.channelUpdate)))
}
@@ -484,7 +484,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
(1 to 3).foreach(_ => addHtlc(1050.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice))
// HTLCs that take Alice's dust exposure above her threshold are rejected.
- val add = CMD_ADD_HTLC(sender.ref, 1050.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 1050.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
sender.expectMsg(RES_ADD_FAILED(add, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 25200.sat.toMilliSatoshi), Some(initialState.channelUpdate)))
}
@@ -508,7 +508,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
(1 to 8).foreach(_ => addHtlc(1050.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob))
// HTLCs that take Bob's dust exposure above his threshold are rejected.
- val add = CMD_ADD_HTLC(sender.ref, 1050.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 1050.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
bob ! add
sender.expectMsg(RES_ADD_FAILED(add, RemoteDustHtlcExposureTooHigh(channelId(bob), 30000.sat, 30450.sat.toMilliSatoshi), Some(initialState.channelUpdate)))
}
@@ -517,14 +517,14 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
- val add1 = CMD_ADD_HTLC(sender.ref, TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add1 = CMD_ADD_HTLC(sender.ref, TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add1
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
alice ! CMD_SIGN()
alice2bob.expectMsgType[CommitSig]
// this is over channel-capacity
- val add2 = CMD_ADD_HTLC(sender.ref, TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add2 = CMD_ADD_HTLC(sender.ref, TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add2
val error = InsufficientFunds(channelId(alice), add2.amount, 562193 sat, 20000 sat, 4330 sat)
sender.expectMsg(RES_ADD_FAILED(add2, error, Some(initialState.channelUpdate)))
@@ -541,7 +541,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined && alice.stateData.asInstanceOf[DATA_NORMAL].remoteShutdown.isEmpty)
// actual test starts here
- val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = NoMoreHtlcsClosingInProgress(channelId(alice))
sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate)))
@@ -553,14 +553,14 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
// let's make alice send an htlc
- val add1 = CMD_ADD_HTLC(sender.ref, 50000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add1 = CMD_ADD_HTLC(sender.ref, 50000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add1
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
// at the same time bob initiates a closing
bob ! CMD_CLOSE(sender.ref, None, None)
sender.expectMsgType[RES_SUCCESS[CMD_CLOSE]]
// this command will be received by alice right after having received the shutdown
- val add2 = CMD_ADD_HTLC(sender.ref, 10000000 msat, randomBytes32(), CltvExpiry(300000), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add2 = CMD_ADD_HTLC(sender.ref, 10000000 msat, randomBytes32(), CltvExpiry(300000), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
// messages cross
alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
@@ -574,7 +574,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv UpdateAddHtlc") { f =>
import f._
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
- val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, 150000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, 150000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
bob ! htlc
awaitCond(bob.stateData == initialState
.modify(_.commitments.changes.remoteChanges.proposed).using(_ :+ htlc)
@@ -586,7 +586,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv UpdateAddHtlc (unexpected id)") { f =>
import f._
val tx = bob.signCommitTx()
- val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 42, 150000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 42, 150000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
bob ! htlc.copy(id = 0)
bob ! htlc.copy(id = 1)
bob ! htlc.copy(id = 2)
@@ -604,7 +604,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv UpdateAddHtlc (value too small)") { f =>
import f._
val tx = bob.signCommitTx()
- val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, 150 msat, randomBytes32(), cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, 150 msat, randomBytes32(), cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
alice2bob.forward(bob, htlc)
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data.toArray) == HtlcValueTooSmall(channelId(bob), minimum = 1000 msat, actual = 150 msat).getMessage)
@@ -620,7 +620,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv UpdateAddHtlc (insufficient funds)") { f =>
import f._
val tx = bob.signCommitTx()
- val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(Long.MaxValue), randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(Long.MaxValue), randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
alice2bob.forward(bob, htlc)
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = MilliSatoshi(Long.MaxValue), missing = 9223372036078675L sat, reserve = 20000 sat, fees = 3900 sat).getMessage)
@@ -636,9 +636,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs)") { f =>
import f._
val tx = bob.signCommitTx()
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 400000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 100000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 400000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 100000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = 100000000 msat, missing = 24760 sat, reserve = 20000 sat, fees = 4760 sat).getMessage)
awaitCond(bob.stateName == CLOSING)
@@ -650,10 +650,10 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 1/2)") { f =>
import f._
val tx = bob.signCommitTx()
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 400000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 200000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 167600000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 3, 10000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 400000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 200000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 167600000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 3, 10000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = 10000000 msat, missing = 2790 sat, reserve = 20000 sat, fees = 5190 sat).getMessage)
awaitCond(bob.stateName == CLOSING)
@@ -668,9 +668,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 2/2)") { f =>
import f._
val tx = bob.signCommitTx()
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 500000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 500000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = 500000000 msat, missing = 324760 sat, reserve = 20000 sat, fees = 4760 sat).getMessage)
awaitCond(bob.stateName == CLOSING)
@@ -685,7 +685,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv UpdateAddHtlc (over max inflight htlc value)", Tag(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight)) { f =>
import f._
val tx = alice.signCommitTx()
- alice2bob.forward(alice, UpdateAddHtlc(ByteVector32.Zeroes, 0, 151_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+ alice2bob.forward(alice, UpdateAddHtlc(ByteVector32.Zeroes, 0, 151_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
val error = alice2bob.expectMsgType[Error]
assert(new String(error.data.toArray) == HtlcValueTooHighInFlight(channelId(alice), maximum = UInt64(150_000_000), actual = 151_000_000 msat).getMessage)
awaitCond(alice.stateName == CLOSING)
@@ -702,9 +702,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val tx = bob.signCommitTx()
// Bob accepts a maximum of 30 htlcs
for (i <- 0 until 30) {
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, i, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, i, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
}
- alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 30, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+ alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 30, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None))
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data.toArray) == TooManyAcceptedHtlcs(channelId(bob), maximum = 30).getMessage)
awaitCond(bob.stateName == CLOSING)
@@ -728,7 +728,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv CMD_SIGN (two identical htlcs in each direction)") { f =>
import f._
val sender = TestProbe()
- val add = CMD_ADD_HTLC(sender.ref, 10000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 10000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
@@ -771,19 +771,19 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val a2b_2 = Alice.nodeParams.channelConf.dustLimit + 20.sat // will be in alice and bob tx
val b2a_1 = Alice.nodeParams.channelConf.dustLimit + 10.sat // will be in alice and bob tx
val b2a_2 = Bob.nodeParams.channelConf.dustLimit + 10.sat // will be only be in bob tx
- alice ! CMD_ADD_HTLC(sender.ref, a2b_1.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, a2b_1.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
- alice ! CMD_ADD_HTLC(sender.ref, a2b_2.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, a2b_2.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
- bob ! CMD_ADD_HTLC(sender.ref, b2a_1.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ bob ! CMD_ADD_HTLC(sender.ref, b2a_1.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
bob2alice.expectMsgType[UpdateAddHtlc]
bob2alice.forward(alice)
- bob ! CMD_ADD_HTLC(sender.ref, b2a_2.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ bob ! CMD_ADD_HTLC(sender.ref, b2a_2.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
bob2alice.expectMsgType[UpdateAddHtlc]
bob2alice.forward(alice)
@@ -803,7 +803,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv CMD_SIGN (htlcs with same pubkeyScript but different amounts)") { f =>
import f._
val sender = TestProbe()
- val add = CMD_ADD_HTLC(sender.ref, 10_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 10_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
val epsilons = List(3, 1, 5, 7, 6) // unordered on purpose
val htlcCount = epsilons.size
for (i <- epsilons) {
@@ -1138,12 +1138,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val r = randomBytes32()
val h = Crypto.sha256(r)
- alice ! CMD_ADD_HTLC(sender.ref, 50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val htlc1 = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
- alice ! CMD_ADD_HTLC(sender.ref, 50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 50000000 msat, h, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val htlc2 = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
@@ -2182,7 +2182,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
- val add = UpdateAddHtlc(ByteVector32.Zeroes, 0, 2500000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add = UpdateAddHtlc(ByteVector32.Zeroes, 0, 2500000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
alice2bob.send(bob, add)
val fee = UpdateFee(initialState.channelId, FeeratePerByte(2 sat).perKw)
alice2bob.send(bob, fee)
@@ -2986,7 +2986,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// initially we have :
// alice = 800 000
// bob = 200 000
- val add = CMD_ADD_HTLC(sender.ref, 10000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 10000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
alice2bob.expectMsgType[UpdateAddHtlc]
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala
index 08f7d96..5ce9c41 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala
@@ -110,7 +110,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// |--- sig --X |
// | |
val sender = TestProbe()
- alice ! CMD_ADD_HTLC(sender.ref, 1000000 msat, ByteVector32.Zeroes, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 1000000 msat, ByteVector32.Zeroes, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
// bob receives the htlc
alice2bob.forward(bob)
@@ -160,7 +160,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// | X-- rev ---|
// | X-- sig ---|
val sender = TestProbe()
- alice ! CMD_ADD_HTLC(ActorRef.noSender, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(ActorRef.noSender, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
// bob receives the htlc and the signature
alice2bob.forward(bob, htlc)
@@ -204,7 +204,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// |<--- rev ---|
// | X-- sig ---|
val sender = TestProbe()
- alice ! CMD_ADD_HTLC(ActorRef.noSender, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(ActorRef.noSender, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
// bob receives the htlc and the signature
alice2bob.forward(bob, htlc)
@@ -537,7 +537,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
channelUpdateListener.expectNoMessage(300 millis)
// we attempt to send a payment
- alice ! CMD_ADD_HTLC(sender.ref, 4200 msat, randomBytes32(), CltvExpiry(123456), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ alice ! CMD_ADD_HTLC(sender.ref, 4200 msat, randomBytes32(), CltvExpiry(123456), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
sender.expectMsgType[RES_ADD_FAILED[ChannelUnavailable]]
// alice will broadcast a new disabled channel_update
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala
index 6628813..7a9680d 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala
@@ -63,7 +63,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
// alice sends an HTLC to bob
val h1 = Crypto.sha256(r1)
val recipient1 = SpontaneousRecipient(TestConstants.Bob.nodeParams.nodeId, 300_000_000 msat, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), r1)
- val Right(cmd1) = OutgoingPaymentPacket.buildOutgoingPayment(localOrigin(sender.ref), h1, makeSingleHopRoute(recipient1.totalAmount, recipient1.nodeId), recipient1, Reputation.Score.max).map(_.cmd.copy(commit = false))
+ val Right(cmd1) = OutgoingPaymentPacket.buildOutgoingPayment(localOrigin(sender.ref), h1, makeSingleHopRoute(recipient1.totalAmount, recipient1.nodeId), recipient1, Reputation.Score.max(accountable = false)).map(_.cmd.copy(commit = false))
alice ! cmd1
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val htlc1 = alice2bob.expectMsgType[UpdateAddHtlc]
@@ -72,7 +72,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
// alice sends another HTLC to bob
val h2 = Crypto.sha256(r2)
val recipient2 = SpontaneousRecipient(TestConstants.Bob.nodeParams.nodeId, 200_000_000 msat, CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), r2)
- val Right(cmd2) = OutgoingPaymentPacket.buildOutgoingPayment(localOrigin(sender.ref), h2, makeSingleHopRoute(recipient2.totalAmount, recipient2.nodeId), recipient2, Reputation.Score.max).map(_.cmd.copy(commit = false))
+ val Right(cmd2) = OutgoingPaymentPacket.buildOutgoingPayment(localOrigin(sender.ref), h2, makeSingleHopRoute(recipient2.totalAmount, recipient2.nodeId), recipient2, Reputation.Score.max(accountable = false)).map(_.cmd.copy(commit = false))
alice ! cmd2
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
val htlc2 = alice2bob.expectMsgType[UpdateAddHtlc]
@@ -144,7 +144,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
test("recv CMD_ADD_HTLC") { f =>
import f._
val sender = TestProbe()
- val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, r1, cltvExpiry = CltvExpiry(300000), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, r1, cltvExpiry = CltvExpiry(300000), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(RES_ADD_FAILED(add, error, None))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala
index cda4266..2fa3aba 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala
@@ -139,7 +139,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
aliceClose(f)
alice2bob.expectMsgType[ClosingSigned]
val sender = TestProbe()
- val add = CMD_ADD_HTLC(sender.ref, 5000000000L msat, randomBytes32(), CltvExpiry(300000), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 5000000000L msat, randomBytes32(), CltvExpiry(300000), TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(RES_ADD_FAILED(add, error, None))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala
index 3216b15..730303e 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala
@@ -277,7 +277,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// actual test starts here
val sender = TestProbe()
- val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = CltvExpiry(300000), onion = TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref))
+ val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = CltvExpiry(300000), onion = TestConstants.emptyOnionPacket, None, Reputation.Score.max(accountable = false), None, localOrigin(sender.ref))
alice ! add
val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(RES_ADD_FAILED(add, error, None))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/LiquidityDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/LiquidityDbSpec.scala
index dea3877..93ac2e7 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/db/LiquidityDbSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/LiquidityDbSpec.scala
@@ -71,10 +71,10 @@ class LiquidityDbSpec extends AnyFunSuite {
val paymentHash1 = randomBytes32()
val paymentHash2 = randomBytes32()
val upstream = Seq(
- Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 7, 25_000_000 msat, paymentHash1, CltvExpiry(750_000), randomOnion(), None, Reputation.maxEndorsement, None), TimestampMilli(0), randomKey().publicKey, 0.1),
- Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 0, 1 msat, paymentHash1, CltvExpiry(750_000), randomOnion(), Some(randomKey().publicKey), Reputation.maxEndorsement, None), TimestampMilli.now(), randomKey().publicKey, 0.1),
- Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 561, 100_000_000 msat, paymentHash2, CltvExpiry(799_999), randomOnion(), None, Reputation.maxEndorsement, None), TimestampMilli.now(), randomKey().publicKey, 0.1),
- Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 1105, 100_000_000 msat, paymentHash2, CltvExpiry(799_999), randomOnion(), None, Reputation.maxEndorsement, None), TimestampMilli.now(), randomKey().publicKey, 0.1),
+ Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 7, 25_000_000 msat, paymentHash1, CltvExpiry(750_000), randomOnion(), None, accountable = false, None), TimestampMilli(0), randomKey().publicKey, 0.1),
+ Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 0, 1 msat, paymentHash1, CltvExpiry(750_000), randomOnion(), Some(randomKey().publicKey), accountable = false, None), TimestampMilli.now(), randomKey().publicKey, 0.1),
+ Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 561, 100_000_000 msat, paymentHash2, CltvExpiry(799_999), randomOnion(), None, accountable = false, None), TimestampMilli.now(), randomKey().publicKey, 0.1),
+ Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), 1105, 100_000_000 msat, paymentHash2, CltvExpiry(799_999), randomOnion(), None, accountable = false, None), TimestampMilli.now(), randomKey().publicKey, 0.1),
)
val pendingAlice = Seq(
OnTheFlyFunding.Pending(
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 0582de6..eab686c 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
@@ -268,7 +268,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat
hmac = ByteVector32(hex"9442626f72c475963dbddf8a57ab2cef3013eb3d6a5e8afbea9e631dac4481f5")
),
pathKey_opt = None,
- endorsement = 6,
+ accountable = false,
fundingFee_opt = None,
)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala
index 14fe075..3d435b8 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala
@@ -98,7 +98,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
}
def createBlindedPacket(amount: MilliSatoshi, paymentHash: ByteVector32, expiry: CltvExpiry, finalExpiry: CltvExpiry, pathId: ByteVector): IncomingPaymentPacket.FinalPacket = {
- val add = UpdateAddHtlc(ByteVector32.One, 0, amount, paymentHash, expiry, TestConstants.emptyOnionPacket, Some(randomKey().publicKey), Reputation.maxEndorsement, None)
+ val add = UpdateAddHtlc(ByteVector32.One, 0, amount, paymentHash, expiry, TestConstants.emptyOnionPacket, Some(randomKey().publicKey), accountable = false, None)
val payload = FinalPayload.Blinded(TlvStream(AmountToForward(amount), TotalAmount(amount), OutgoingCltv(finalExpiry), EncryptedRecipientData(hex"deadbeef")), TlvStream(PathId(pathId), PaymentConstraints(CltvExpiry(500_000), 1 msat)))
IncomingPaymentPacket.FinalPacket(add, payload, TimestampMilli.now())
}
@@ -117,8 +117,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(!incoming.get.invoice.isExpired())
assert(Crypto.sha256(incoming.get.paymentPreimage) == invoice.paymentHash)
- val add = UpdateAddHtlc(ByteVector32.One, 1, amountMsat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 1, amountMsat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
assert(register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]].message.id == add.id)
val paymentReceived = eventListener.expectMsgType[PaymentReceived]
@@ -133,8 +133,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
sender.send(handlerWithoutMpp, ReceiveStandardPayment(sender.ref, Some(50_000 msat), Left("1 coffee with extra fees and expiry")))
val invoice = sender.expectMsgType[Bolt11Invoice]
- val add = UpdateAddHtlc(ByteVector32.One, 1, 75_000 msat, invoice.paymentHash, defaultExpiry + CltvExpiryDelta(12), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(70_000 msat, 70_000 msat, defaultExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 1, 75_000 msat, invoice.paymentHash, defaultExpiry + CltvExpiryDelta(12), TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(70_000 msat, 70_000 msat, defaultExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
assert(register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]].message.id == add.id)
val paymentReceived = eventListener.expectMsgType[PaymentReceived]
@@ -151,8 +151,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.features.hasFeature(BasicMultiPartPayment))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
- val add = UpdateAddHtlc(ByteVector32.One, 2, amountMsat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 2, amountMsat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
assert(register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]].message.id == add.id)
val paymentReceived = eventListener.expectMsgType[PaymentReceived]
@@ -198,8 +198,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = sender.expectMsgType[Bolt11Invoice]
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
- val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, invoice.paymentHash, CltvExpiryDelta(3).toCltvExpiry(nodeParams.currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, invoice.paymentHash, CltvExpiryDelta(3).toCltvExpiry(nodeParams.currentBlockHeight), TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(amountMsat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -331,8 +331,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(!invoice.features.hasFeature(BasicMultiPartPayment))
assert(invoice.isExpired())
- val add = UpdateAddHtlc(ByteVector32.One, 0, 1000 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 1000 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
val Some(incoming) = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
assert(incoming.invoice.isExpired() && incoming.status == IncomingPaymentStatus.Expired)
@@ -346,8 +346,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.features.hasFeature(BasicMultiPartPayment))
assert(invoice.isExpired())
- val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
val Some(incoming) = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
@@ -361,8 +361,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = sender.expectMsgType[Bolt11Invoice]
assert(!invoice.features.hasFeature(BasicMultiPartPayment))
- val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -376,8 +376,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.features.hasFeature(BasicMultiPartPayment))
val lowCltvExpiry = nodeParams.channelConf.fulfillSafetyBeforeTimeout.toCltvExpiry(nodeParams.currentBlockHeight)
- val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, lowCltvExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, lowCltvExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -390,8 +390,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = sender.expectMsgType[Bolt11Invoice]
assert(invoice.features.hasFeature(BasicMultiPartPayment))
- val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash.reverse, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash.reverse, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -404,8 +404,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = sender.expectMsgType[Bolt11Invoice]
assert(invoice.features.hasFeature(BasicMultiPartPayment))
- val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 999 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 999 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(999 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -418,8 +418,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = sender.expectMsgType[Bolt11Invoice]
assert(invoice.features.hasFeature(BasicMultiPartPayment))
- val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 2001 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 2001 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(2001 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -433,8 +433,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.features.hasFeature(BasicMultiPartPayment))
// Invalid payment secret.
- val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret.reverse, invoice.paymentMetadata), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret.reverse, invoice.paymentMetadata, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
@@ -465,8 +465,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
sender.send(handlerWithRouteBlinding, ReceiveOfferPayment(sender.ref, nodeKey, invoiceReq, createEmptyReceivingRoute(randomBytes32()), randomBytes32()))
val invoice = sender.expectMsgType[Bolt12Invoice]
- val add = UpdateAddHtlc(ByteVector32.One, 0, 5000 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, randomBytes32(), None), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 5000 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, randomBytes32(), None, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(5000 msat, nodeParams.currentBlockHeight)))
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).isEmpty)
@@ -547,15 +547,15 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
f.sender.send(handler, ReceiveStandardPayment(f.sender.ref, Some(1000 msat), Left("1 slow coffee")))
val pr1 = f.sender.expectMsgType[Bolt11Invoice]
val receivedAt1 = TimestampMilli.now()
- val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, pr1.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, pr1.paymentSecret, pr1.paymentMetadata), receivedAt1))
+ val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, pr1.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, pr1.paymentSecret, pr1.paymentMetadata, upgradeAccountability = false), receivedAt1))
// Partial payment exceeding the invoice amount, but incomplete because it promises to overpay.
f.sender.send(handler, ReceiveStandardPayment(f.sender.ref, Some(1500 msat), Left("1 slow latte")))
val pr2 = f.sender.expectMsgType[Bolt11Invoice]
val receivedAt2 = receivedAt1 + 1.millis
- val add2 = UpdateAddHtlc(ByteVector32.One, 1, 1600 msat, pr2.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 2000 msat, add2.cltvExpiry, pr2.paymentSecret, pr2.paymentMetadata), receivedAt2))
+ val add2 = UpdateAddHtlc(ByteVector32.One, 1, 1600 msat, pr2.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 2000 msat, add2.cltvExpiry, pr2.paymentSecret, pr2.paymentMetadata, upgradeAccountability = false), receivedAt2))
awaitCond {
f.sender.send(handler, GetPendingPayments)
@@ -574,7 +574,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
// Extraneous HTLCs should be failed.
val receivedAt3 = receivedAt1 + 2.millis
- f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(pr1.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 42, 200 msat, pr1.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None, Reputation.maxEndorsement, None), receivedAt3), Some(PaymentTimeout())))
+ f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(pr1.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 42, 200 msat, pr1.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None, accountable = false, None), receivedAt3), Some(PaymentTimeout())))
f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(42, FailureReason.LocalFailure(PaymentTimeout()), Some(FailureAttributionData(receivedAt3, None)), commit = true)))
// The payment should still be pending in DB.
@@ -591,15 +591,15 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val invoice = f.sender.expectMsgType[Bolt11Invoice]
val receivedAt1 = TimestampMilli.now()
- val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), receivedAt1))
+ val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt1))
// Invalid payment secret -> should be rejected.
val receivedAt2 = receivedAt1 + 1.millis
- val add2 = UpdateAddHtlc(ByteVector32.Zeroes, 42, 200 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1000 msat, add2.cltvExpiry, invoice.paymentSecret.reverse, invoice.paymentMetadata), receivedAt2))
+ val add2 = UpdateAddHtlc(ByteVector32.Zeroes, 42, 200 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1000 msat, add2.cltvExpiry, invoice.paymentSecret.reverse, invoice.paymentMetadata, upgradeAccountability = false), receivedAt2))
val receivedAt3 = receivedAt1 + 2.millis
val add3 = add2.copy(id = 43)
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), receivedAt3))
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt3))
f.register.expectMsgAllOf(
Register.Forward(null, add2.channelId, CMD_FAIL_HTLC(add2.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)), Some(FailureAttributionData(receivedAt2, None)), commit = true)),
@@ -619,7 +619,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
// Extraneous HTLCs should be fulfilled.
val receivedAt4 = receivedAt1 + 3.millis
- f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(invoice.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 44, 200 msat, invoice.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None, Reputation.maxEndorsement, None), receivedAt4), None))
+ f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(invoice.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 44, 200 msat, invoice.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None, accountable = false, None), receivedAt4), None))
f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FULFILL_HTLC(44, preimage, Some(FulfillAttributionData(receivedAt4, None, None)), commit = true)))
assert(f.eventListener.expectMsgType[PaymentReceived].amount == 200.msat)
val received2 = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
@@ -637,12 +637,12 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
f.sender.send(handler, ReceiveStandardPayment(f.sender.ref, Some(1000 msat), Left("1 coffee with tip please"), paymentPreimage_opt = Some(preimage)))
val invoice = f.sender.expectMsgType[Bolt11Invoice]
- val add1 = UpdateAddHtlc(randomBytes32(), 0, 1100 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add1 = UpdateAddHtlc(randomBytes32(), 0, 1100 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt1 = TimestampMilli.now()
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1500 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), receivedAt1))
- val add2 = UpdateAddHtlc(randomBytes32(), 1, 500 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1500 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt1))
+ val add2 = UpdateAddHtlc(randomBytes32(), 1, 500 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt2 = TimestampMilli.now() + 5.millis
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1500 msat, add2.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), receivedAt2))
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1500 msat, add2.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt2))
f.register.expectMsgAllOf(
Register.Forward(null, add1.channelId, CMD_FULFILL_HTLC(add1.id, preimage, Some(FulfillAttributionData(receivedAt1, None, None)), commit = true)),
@@ -666,21 +666,21 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(invoice.features.hasFeature(BasicMultiPartPayment))
assert(invoice.paymentHash == Crypto.sha256(preimage))
- val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt1 = TimestampMilli.now()
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), receivedAt1))
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt1))
f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(0, FailureReason.LocalFailure(PaymentTimeout()), Some(FailureAttributionData(receivedAt1, None)), commit = true)))
awaitCond({
f.sender.send(handler, GetPendingPayments)
f.sender.expectMsgType[PendingPayments].paymentHashes.isEmpty
})
- val add2 = UpdateAddHtlc(ByteVector32.One, 2, 300 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add2 = UpdateAddHtlc(ByteVector32.One, 2, 300 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt2 = TimestampMilli.now() + 10.millis
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1000 msat, add2.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), receivedAt2))
- val add3 = UpdateAddHtlc(ByteVector32.Zeroes, 5, 700 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add2, FinalPayload.Standard.createPayload(add2.amountMsat, 1000 msat, add2.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt2))
+ val add3 = UpdateAddHtlc(ByteVector32.Zeroes, 5, 700 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt3 = TimestampMilli.now() + 50.millis
- f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata), receivedAt3))
+ f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata, upgradeAccountability = false), receivedAt3))
// the fulfill are not necessarily in the same order as the commands
f.register.expectMsgAllOf(
@@ -711,7 +711,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
- val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
sender.send(handlerWithKeySend, IncomingPaymentPacket.FinalPacket(add, payload, TimestampMilli.now()))
register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]]
@@ -732,7 +732,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
- val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
sender.send(handlerWithKeySend, IncomingPaymentPacket.FinalPacket(add, payload, TimestampMilli.now()))
register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]]
@@ -754,7 +754,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
- val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val receivedAt = TimestampMilli.now()
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, payload, receivedAt))
@@ -769,8 +769,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val paymentSecret = randomBytes32()
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
- val add = UpdateAddHtlc(ByteVector32.One, 0, 1000 msat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, paymentSecret, None), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 1000 msat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, paymentSecret, None, upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.id == add.id)
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
@@ -783,8 +783,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val paymentSecret = randomBytes32()
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
- val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
- sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, paymentSecret, Some(hex"012345")), TimestampMilli.now()))
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
+ sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, paymentSecret, Some(hex"012345"), upgradeAccountability = false), TimestampMilli.now()))
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
assert(cmd.id == add.id)
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
@@ -797,7 +797,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val paymentHash = Crypto.sha256(paymentPreimage)
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
- val add = UpdateAddHtlc(ByteVector32.One, 0, 1000 msat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val add = UpdateAddHtlc(ByteVector32.One, 0, 1000 msat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, accountable = false, None)
val invoice = Bolt11Invoice(Block.Testnet3GenesisBlock.hash, None, paymentHash, randomKey(), Left("dummy"), CltvExpiryDelta(12))
val incomingPayment = IncomingStandardPayment(invoice, paymentPreimage, PaymentType.Standard, invoice.createdAt.toTimestampMilli, IncomingPaymentStatus.Pending)
val fulfill = DoFulfill(incomingPayment, MultiPartPaymentFSM.MultiPartPaymentSucceeded(paymentHash, Queue(HtlcPart(1000 msat, add, TimestampMilli.now()))))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala
index 8fa6def..66beba4 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala
@@ -233,7 +233,7 @@ object MultiPartPaymentFSMSpec {
def htlcIdToChannelId(htlcId: Long) = ByteVector32(ByteVector.fromLong(htlcId).padLeft(32))
def createMultiPartHtlc(totalAmount: MilliSatoshi, htlcAmount: MilliSatoshi, htlcId: Long): HtlcPart = {
- val htlc = UpdateAddHtlc(htlcIdToChannelId(htlcId), htlcId, htlcAmount, paymentHash, CltvExpiry(42), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)
+ val htlc = UpdateAddHtlc(htlcIdToChannelId(htlcId), htlcId, htlcAmount, paymentHash, CltvExpiry(42), TestConstants.emptyOnionPacket, None, accountable = false, None)
HtlcPart(totalAmount, htlc, TimestampMilli.now())
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
index 8646895..f19487b 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
@@ -66,7 +66,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
override def withFixture(test: OneArgTest): Outcome = {
val id = UUID.randomUUID()
- val cfg = SendPaymentConfig(id, id, Some("42"), paymentHash, randomKey().publicKey, Upstream.Local(id), None, None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true)
+ val cfg = SendPaymentConfig(id, id, Some("42"), paymentHash, randomKey().publicKey, Upstream.Local(id), None, None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true, accountable = false)
val nodeParams = TestConstants.Alice.nodeParams
val (childPayFsm, router, sender, eventListener, metricsListener) = (TestProbe(), TestProbe(), TestProbe(), TestProbe(), TestProbe())
val paymentHandler = TestFSMRef(new MultiPartPaymentLifecycle(nodeParams, cfg, publishPreimage = true, router.ref, FakePaymentFactory(childPayFsm)))
@@ -127,7 +127,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
import f._
assert(payFsm.stateName == WAIT_FOR_PAYMENT_REQUEST)
- val recipient = ClearRecipient(e, recipientFeatures, 1_200_000 msat, expiry, randomBytes32(), paymentMetadata_opt = Some(hex"012345"))
+ val recipient = ClearRecipient(e, recipientFeatures, 1_200_000 msat, expiry, randomBytes32(), paymentMetadata_opt = Some(hex"012345"), upgradeAccountability = false)
val payment = SendMultiPartPayment(sender.ref, recipient, 1, routeParams.copy(randomize = false))
sender.send(payFsm, payment)
@@ -330,7 +330,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
// The B -> E channel is private and provided in the invoice routing hints.
val extraEdge = ExtraEdge(b, e, hop_be.shortChannelId, hop_be.params.relayFees.feeBase, hop_be.params.relayFees.feeProportionalMillionths, hop_be.params.cltvExpiryDelta, hop_be.params.htlcMinimum, hop_be.params.htlcMaximum_opt)
- val recipient = ClearRecipient(e, Features.empty, finalAmount, expiry, randomBytes32(), Seq(extraEdge))
+ val recipient = ClearRecipient(e, Features.empty, finalAmount, expiry, randomBytes32(), upgradeAccountability = false, Seq(extraEdge))
val payment = SendMultiPartPayment(sender.ref, recipient, 3, routeParams)
sender.send(payFsm, payment)
assert(router.expectMsgType[RouteRequest].target.extraEdges == Seq(extraEdge))
@@ -353,7 +353,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
// The B -> E channel is private and provided in the invoice routing hints.
val extraEdge = ExtraEdge(b, e, hop_be.shortChannelId, hop_be.params.relayFees.feeBase, hop_be.params.relayFees.feeProportionalMillionths, hop_be.params.cltvExpiryDelta, hop_be.params.htlcMinimum, hop_be.params.htlcMaximum_opt)
- val recipient = ClearRecipient(e, Features.empty, finalAmount, expiry, randomBytes32(), Seq(extraEdge))
+ val recipient = ClearRecipient(e, Features.empty, finalAmount, expiry, randomBytes32(), upgradeAccountability = false, Seq(extraEdge))
val payment = SendMultiPartPayment(sender.ref, recipient, 3, routeParams)
sender.send(payFsm, payment)
assert(router.expectMsgType[RouteRequest].target.extraEdges == Seq(extraEdge))
@@ -394,7 +394,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
}
test("update routing hints") { () =>
- val recipient = ClearRecipient(e, Features.empty, finalAmount, expiry, randomBytes32(), Seq(
+ val recipient = ClearRecipient(e, Features.empty, finalAmount, expiry, randomBytes32(), upgradeAccountability = false, Seq(
ExtraEdge(a, b, ShortChannelId(1), 10 msat, 0, CltvExpiryDelta(12), 1 msat, None),
ExtraEdge(b, c, ShortChannelId(2), 0 msat, 100, CltvExpiryDelta(24), 1 msat, None),
ExtraEdge(a, c, ShortChannelId(3), 1 msat, 10, CltvExpiryDelta(144), 1 msat, None)
@@ -746,6 +746,6 @@ object MultiPartPaymentLifecycleSpec {
Features.PaymentSecret -> FeatureSupport.Mandatory,
Features.BasicMultiPartPayment -> FeatureSupport.Optional,
).invoiceFeatures()
- val clearRecipient = ClearRecipient(e, recipientFeatures, finalAmount, expiry, ByteVector32.One)
+ val clearRecipient = ClearRecipient(e, recipientFeatures, finalAmount, expiry, ByteVector32.One, upgradeAccountability = false)
}
\ No newline at end of file
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala
index d81b19a..93cddfa 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala
@@ -175,7 +175,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val request = SendPaymentToRoute(finalAmount, invoice, Nil, route, None, None)
sender.send(initiator, request)
val payment = sender.expectMsgType[SendPaymentToRouteResponse]
- payFsm.expectMsg(SendPaymentConfig(payment.paymentId, payment.parentId, None, paymentHash, c, Upstream.Local(payment.paymentId), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = false))
+ payFsm.expectMsg(SendPaymentConfig(payment.paymentId, payment.parentId, None, paymentHash, c, Upstream.Local(payment.paymentId), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = false, accountable = false))
payFsm.expectMsg(PaymentLifecycle.SendPaymentToRoute(initiator, Left(route), ClearRecipient(invoice, finalAmount, finalExpiryDelta.toCltvExpiry(nodeParams.currentBlockHeight + 1), Set.empty)))
sender.send(initiator, GetPayment(PaymentIdentifier.PaymentUUID(payment.paymentId)))
@@ -200,7 +200,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(req.finalExpiry(nodeParams) == (finalExpiryDelta + 1).toCltvExpiry(nodeParams.currentBlockHeight))
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
- payFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, c, Upstream.Local(id), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true))
+ payFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, c, Upstream.Local(id), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true, accountable = false))
payFsm.expectMsg(PaymentLifecycle.SendPaymentToNode(initiator, ClearRecipient(invoice, finalAmount, req.finalExpiry(nodeParams), Set.empty), 1, nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams))
sender.send(initiator, GetPayment(PaymentIdentifier.PaymentUUID(id)))
@@ -223,7 +223,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val req = SendPaymentToNode(sender.ref, finalAmount + 100.msat, invoice, Nil, 1, routeParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams)
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
- multiPartPayFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, c, Upstream.Local(id), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true))
+ multiPartPayFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, c, Upstream.Local(id), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true, accountable = false))
multiPartPayFsm.expectMsg(SendMultiPartPayment(initiator, ClearRecipient(invoice, finalAmount + 100.msat, req.finalExpiry(nodeParams), Set.empty), 1, nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams))
sender.send(initiator, GetPayment(PaymentIdentifier.PaymentUUID(id)))
@@ -247,7 +247,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val req = SendPaymentToNode(sender.ref, finalAmount, invoice, Nil, 1, routeParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams)
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
- multiPartPayFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, c, Upstream.Local(id), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true))
+ multiPartPayFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, c, Upstream.Local(id), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = true, accountable = false))
val payment = multiPartPayFsm.expectMsgType[SendMultiPartPayment]
val expiry = payment.recipient.asInstanceOf[ClearRecipient].expiry
assert(nodeParams.currentBlockHeight + invoiceFinalExpiryDelta.toInt + 50 <= expiry.blockHeight)
@@ -261,7 +261,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val req = SendPaymentToRoute(finalAmount, invoice, Nil, route, None, None)
sender.send(initiator, req)
val payment = sender.expectMsgType[SendPaymentToRouteResponse]
- payFsm.expectMsg(SendPaymentConfig(payment.paymentId, payment.parentId, None, paymentHash, c, Upstream.Local(payment.paymentId), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = false))
+ payFsm.expectMsg(SendPaymentConfig(payment.paymentId, payment.parentId, None, paymentHash, c, Upstream.Local(payment.paymentId), Some(invoice), None, storeInDb = true, publishEvent = true, recordPathFindingMetrics = false, accountable = false))
val msg = payFsm.expectMsgType[PaymentLifecycle.SendPaymentToRoute]
assert(msg.replyTo == initiator)
assert(msg.route == Left(route))
@@ -303,7 +303,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val req = SendPaymentToNode(sender.ref, finalAmount, invoice, resolvedPaths, 1, routeParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams, payerKey_opt = Some(payerKey))
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
- payFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, invoice.nodeId, Upstream.Local(id), Some(invoice), Some(payerKey), storeInDb = true, publishEvent = true, recordPathFindingMetrics = true))
+ payFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, invoice.nodeId, Upstream.Local(id), Some(invoice), Some(payerKey), storeInDb = true, publishEvent = true, recordPathFindingMetrics = true, accountable = false))
val payment = payFsm.expectMsgType[PaymentLifecycle.SendPaymentToNode]
assert(payment.amount == finalAmount)
assert(payment.recipient.nodeId == invoice.nodeId)
@@ -337,7 +337,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
val req = SendPaymentToNode(sender.ref, finalAmount, invoice, resolvedPaths, 1, routeParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams, payerKey_opt = Some(payerKey))
sender.send(initiator, req)
val id = sender.expectMsgType[UUID]
- multiPartPayFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, invoice.nodeId, Upstream.Local(id), Some(invoice), Some(payerKey), storeInDb = true, publishEvent = true, recordPathFindingMetrics = true))
+ multiPartPayFsm.expectMsg(SendPaymentConfig(id, id, None, paymentHash, invoice.nodeId, Upstream.Local(id), Some(invoice), Some(payerKey), storeInDb = true, publishEvent = true, recordPathFindingMetrics = true, accountable = false))
val payment = multiPartPayFsm.expectMsgType[SendMultiPartPayment]
assert(payment.recipient.nodeId == invoice.nodeId)
assert(payment.recipient.totalAmount == finalAmount)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala
index 9ea55f2..9afcae0 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala
@@ -78,7 +78,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
assert(request.ignore == ignore)
assert(request.paymentContext.contains(cfg.paymentContext))
}
-
+
case class PaymentFixture(cfg: SendPaymentConfig,
nodeParams: NodeParams,
paymentFSM: TestFSMRef[PaymentLifecycle.State, PaymentLifecycle.Data, PaymentLifecycle],
@@ -92,11 +92,10 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
def createPaymentLifecycle(invoice: Invoice, storeInDb: Boolean = true, publishEvent: Boolean = true, recordMetrics: Boolean = true): PaymentFixtuWhy this scored 37/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.