Skip to content

Commit 5f25c33

Browse files
committed
WIP
1 parent a87963b commit 5f25c33

File tree

64 files changed

+780
-876
lines changed

Some content is hidden

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

64 files changed

+780
-876
lines changed

eclair-core/src/main/resources/reference.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ eclair {
263263
// Payments that stay pending for longer than this get penalized.
264264
max-relay-duration = 5 minutes
265265
}
266+
267+
reserved-for-accountable = 0.5 // Fraction of a channel slots and usable liquidity that is reserved for accountable HTLCs
266268
}
267269

268270
on-chain-fees {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ class EclairImpl(val appKit: Kit) extends Eclair with Logging with SpendFromChan
473473
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)(implicit timeout: Timeout): Future[RouteResponse] = {
474474
getRouteParams(pathFindingExperimentName_opt) match {
475475
case Right(routeParams) =>
476-
val target = ClearRecipient(targetNodeId, Features.empty, amount, CltvExpiry(appKit.nodeParams.currentBlockHeight), ByteVector32.Zeroes, extraEdges)
476+
val target = ClearRecipient(targetNodeId, Features.empty, amount, CltvExpiry(appKit.nodeParams.currentBlockHeight), ByteVector32.Zeroes, extraEdges, upgradeAccountability = true)
477477
val routeParams1 = routeParams.copy(
478478
includeLocalChannelCost = includeLocalChannelCost,
479479
boundaries = routeParams.boundaries.copy(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ object NodeParams extends Logging {
645645
halfLife = FiniteDuration(config.getDuration("relay.peer-reputation.half-life").getSeconds, TimeUnit.SECONDS),
646646
maxRelayDuration = FiniteDuration(config.getDuration("relay.peer-reputation.max-relay-duration").getSeconds, TimeUnit.SECONDS),
647647
),
648+
reservedBucket = config.getDouble("relay.reserved-for-accountable"),
648649
),
649650
db = database,
650651
autoReconnect = config.getBoolean("auto-reconnect"),

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,12 @@ object Upstream {
160160
override val amountIn: MilliSatoshi = add.amountMsat
161161
val expiryIn: CltvExpiry = add.cltvExpiry
162162

163-
override def toString: String = s"Channel(amountIn=$amountIn, receivedAt=${receivedAt.toLong}, receivedFrom=${receivedFrom.toHex}, endorsement=${add.endorsement}, incomingChannelOccupancy=$incomingChannelOccupancy)"
163+
override def toString: String = s"Channel(amountIn=$amountIn, receivedAt=${receivedAt.toLong}, receivedFrom=${receivedFrom.toHex}, accountable=${add.accountable}, incomingChannelOccupancy=$incomingChannelOccupancy)"
164164
}
165165
/** Our node is forwarding a payment based on a set of HTLCs from potentially multiple upstream channels. */
166166
case class Trampoline(received: List[Channel]) extends Hot {
167167
override val amountIn: MilliSatoshi = received.map(_.add.amountMsat).sum
168+
val accountable: Boolean = received.map(_.add.accountable).reduce(_ || _)
168169
// We must use the lowest expiry of the incoming HTLC set.
169170
val expiryIn: CltvExpiry = received.map(_.add.cltvExpiry).min
170171
val receivedAt: TimestampMilli = received.map(_.receivedAt).max

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ case class LocalCommitConfirmed(channel: ActorRef, remoteNodeId: PublicKey, chan
105105

106106
case class ChannelClosed(channel: ActorRef, channelId: ByteVector32, closingType: ClosingType, commitments: Commitments) extends ChannelEvent
107107

108-
case class OutgoingHtlcAdded(add: UpdateAddHtlc, remoteNodeId: PublicKey, upstream: Upstream.Hot, fee: MilliSatoshi)
108+
case class OutgoingHtlcAdded(add: UpdateAddHtlc, remoteNodeId: PublicKey, fee: MilliSatoshi)
109109

110110
sealed trait OutgoingHtlcSettled
111111

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ case class ForbiddenDuringSplice (override val channelId: Byte
152152
case class ForbiddenDuringQuiescence (override val channelId: ByteVector32, command: String) extends ChannelException(channelId, s"cannot process $command while quiescent")
153153
case class ConcurrentRemoteSplice (override val channelId: ByteVector32) extends ChannelException(channelId, "splice attempt canceled, remote initiated splice before us")
154154
case class TooManySmallHtlcs (override val channelId: ByteVector32, number: Long, below: MilliSatoshi) extends ChannelJammingException(channelId, s"too many small htlcs: $number HTLCs below $below")
155-
case class IncomingConfidenceTooLow (override val channelId: ByteVector32, confidence: Double, occupancy: Double) extends ChannelJammingException(channelId, s"incoming confidence too low: confidence=$confidence occupancy=$occupancy")
156155
case class OutgoingConfidenceTooLow (override val channelId: ByteVector32, confidence: Double, occupancy: Double) extends ChannelJammingException(channelId, s"outgoing confidence too low: confidence=$confidence occupancy=$occupancy")
157156
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")
158157
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")

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -523,9 +523,7 @@ case class Commitment(fundingTxIndex: Long,
523523
return Left(RemoteDustHtlcExposureTooHigh(params.channelId, maxDustExposure, remoteDustExposureAfterAdd))
524524
}
525525

526-
// Jamming protection
527-
// Must be the last checks so that they can be ignored for shadow deployment.
528-
reputationScore.checkOutgoingChannelOccupancy(params.channelId, this, outgoingHtlcs.toSeq)
526+
Right(())
529527
}
530528

531529
def canReceiveAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges): Either[ChannelException, Unit] = {
@@ -898,7 +896,7 @@ case class Commitments(channelParams: ChannelParams,
898896
return Left(HtlcValueTooSmall(channelId, minimum = htlcMinimum, actual = cmd.amount))
899897
}
900898

901-
val add = UpdateAddHtlc(channelId, changes.localNextHtlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextPathKey_opt, cmd.reputationScore.endorsement, cmd.fundingFee_opt)
899+
val add = UpdateAddHtlc(channelId, changes.localNextHtlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextPathKey_opt, cmd.reputationScore.accountable, cmd.fundingFee_opt)
902900
// we increment the local htlc index and add an entry to the origins map
903901
val changes1 = changes.addLocalProposal(add).copy(localNextHtlcId = changes.localNextHtlcId + 1)
904902
val originChannels1 = originChannels + (add.id -> cmd.origin)
@@ -915,7 +913,6 @@ case class Commitments(channelParams: ChannelParams,
915913
Metrics.dropHtlc(failure, Tags.Directions.Outgoing)
916914
failure match {
917915
case f: TooManySmallHtlcs => log.info("TooManySmallHtlcs: {} outgoing HTLCs are below {}", f.number, f.below)
918-
case f: IncomingConfidenceTooLow => log.info("IncomingConfidenceTooLow: confidence is {}% while channel is {}% full", (100 * f.confidence).toInt, (100 * f.occupancy).toInt)
919916
case f: OutgoingConfidenceTooLow => log.info("OutgoingConfidenceTooLow: confidence is {}% while channel is {}% full", (100 * f.confidence).toInt, (100 * f.occupancy).toInt)
920917
case _ => ()
921918
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,8 +535,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
535535
if (c.commit) self ! CMD_SIGN()
536536
context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.aliases, commitments1, d.lastAnnouncement_opt))
537537
val relayFee = nodeFee(d.channelUpdate.relayFees, add.amountMsat)
538-
context.system.eventStream.publish(OutgoingHtlcAdded(add, remoteNodeId, c.origin.upstream, relayFee))
539-
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))
538+
context.system.eventStream.publish(OutgoingHtlcAdded(add, remoteNodeId, relayFee))
539+
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))
540540
handleCommandSuccess(c, d.copy(commitments = commitments1)) sending add
541541
case Left(cause) => handleAddHtlcCommandError(c, cause, Some(d.channelUpdate))
542542
}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ case class Bolt11Invoice(prefix: String, amount_opt: Option[MilliSatoshi], creat
7272

7373
override lazy val features: Features[InvoiceFeature] = tags.collectFirst { case f: InvoiceFeatures => f.features.invoiceFeatures() }.getOrElse(Features.empty)
7474

75+
override lazy val accountable: Boolean = tags.contains(Accountable)
76+
7577
/**
7678
* @return the hash of this payment invoice
7779
*/
@@ -196,7 +198,7 @@ object Bolt11Invoice {
196198
case class UnknownTag28(data: BitVector) extends UnknownTaggedField
197199
case class UnknownTag29(data: BitVector) extends UnknownTaggedField
198200
case class UnknownTag30(data: BitVector) extends UnknownTaggedField
199-
case class UnknownTag31(data: BitVector) extends UnknownTaggedField
201+
case class InvalidTag31(data: BitVector) extends InvalidTaggedField
200202
// @formatter:on
201203

202204
/**
@@ -283,6 +285,12 @@ object Bolt11Invoice {
283285
}
284286
}
285287

288+
/**
289+
* Present if the recipient is willing to be held accountable for the timely resolution of HTLCs.
290+
*/
291+
case object Accountable extends TaggedField
292+
293+
286294
/**
287295
* This returns a bitvector with the minimum size necessary to encode the long, left padded to have a length (in bits)
288296
* that is a multiple of 5.
@@ -439,7 +447,10 @@ object Bolt11Invoice {
439447
.typecase(28, dataCodec(bits).as[UnknownTag28])
440448
.typecase(29, dataCodec(bits).as[UnknownTag29])
441449
.typecase(30, dataCodec(bits).as[UnknownTag30])
442-
.typecase(31, dataCodec(bits).as[UnknownTag31])
450+
.\(31) {
451+
case Accountable => Accountable
452+
case a: InvalidTag31 => a: TaggedField
453+
}(choice(dataCodec(provide(Accountable), expectedLength = Some(0)).upcast[TaggedField], dataCodec(bits).as[InvalidTag31].upcast[TaggedField]))
443454

444455
private def fixedSizeTrailingCodec[A](codec: Codec[A], size: Int): Codec[A] = Codec[A](
445456
(data: A) => codec.encode(data),

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ case class Bolt12Invoice(records: TlvStream[InvoiceTlv]) extends Invoice {
5151
val blindedPaths: Seq[PaymentBlindedRoute] = records.get[InvoicePaths].get.paths.zip(records.get[InvoiceBlindedPay].get.paymentInfo).map { case (route, info) => PaymentBlindedRoute(route, info) }
5252
val fallbacks: Option[Seq[FallbackAddress]] = records.get[InvoiceFallbacks].map(_.addresses)
5353
val signature: ByteVector64 = records.get[Signature].get.signature
54+
override val accountable: Boolean = records.records.contains(InvoiceAccountable)
5455

5556
// It is assumed that the request is valid for this offer.
5657
def validateFor(request: InvoiceRequest, pathNodeId: PublicKey): Either[String, Unit] = {
@@ -109,6 +110,7 @@ object Bolt12Invoice {
109110
val amount = request.amount
110111
val tlvs: Set[InvoiceTlv] = removeSignature(request.records).records ++ Set(
111112
Some(InvoicePaths(paths.map(_.route))),
113+
Some(InvoiceAccountable),
112114
Some(InvoiceBlindedPay(paths.map(_.paymentInfo))),
113115
Some(InvoiceCreatedAt(TimestampSecond.now())),
114116
Some(InvoiceRelativeExpiry(invoiceExpiry.toSeconds)),
@@ -168,6 +170,7 @@ case class MinimalBolt12Invoice(records: TlvStream[InvoiceTlv]) extends Invoice
168170
override val createdAt: TimestampSecond = records.get[InvoiceCreatedAt].get.timestamp
169171
override val relativeExpiry: FiniteDuration = FiniteDuration(records.get[InvoiceRelativeExpiry].map(_.seconds).getOrElse(Bolt12Invoice.DEFAULT_EXPIRY_SECONDS), TimeUnit.SECONDS)
170172
override val features: Features[InvoiceFeature] = records.get[InvoiceFeatures].map(f => Features(f.features).invoiceFeatures()).getOrElse(Features[InvoiceFeature](Features.BasicMultiPartPayment -> FeatureSupport.Optional))
173+
override def accountable: Boolean = true
171174

172175
override def toString: String = {
173176
val data = OfferCodecs.invoiceTlvCodec.encode(records).require.bytes

0 commit comments

Comments
 (0)