Skip to content

Commit e15ae71

Browse files
committed
Add feature
1 parent 85317eb commit e15ae71

File tree

7 files changed

+32
-11
lines changed

7 files changed

+32
-11
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ eclair {
7171
option_route_blinding = disabled
7272
option_shutdown_anysegwit = optional
7373
option_dual_fund = disabled
74+
option_attributable_error = optional
7475
option_quiesce = disabled
7576
option_onion_messages = optional
7677
option_channel_type = optional

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ object Features {
264264
val mandatory = 28
265265
}
266266

267+
case object AttributableError extends Feature with InitFeature with NodeFeature with Bolt11Feature {
268+
val rfcName = "option_attributable_error"
269+
val mandatory = 30
270+
}
271+
267272
// TODO: this should also extend NodeFeature once the spec is finalized
268273
case object Quiescence extends Feature with InitFeature {
269274
val rfcName = "option_quiesce"
@@ -339,6 +344,7 @@ object Features {
339344
RouteBlinding,
340345
ShutdownAnySegwit,
341346
DualFunding,
347+
AttributableError,
342348
Quiescence,
343349
OnionMessages,
344350
ChannelType,

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
640640
case PostRevocationAction.RejectHtlc(add) =>
641641
log.debug("rejecting incoming htlc {}", add)
642642
// NB: we don't set commit = true, we will sign all updates at once afterwards.
643-
self ! CMD_FAIL_HTLC(add.id, Right(TemporaryChannelFailure(d.channelUpdate)), useAttributableErrors = false, TimestampMilli.now(), commit = true)
643+
// The HTLC is rejected without reading the onion, we default to using attributable errors if the feature is activated.
644+
self ! CMD_FAIL_HTLC(add.id, Right(TemporaryChannelFailure(d.channelUpdate)), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.now(), commit = true)
644645
case PostRevocationAction.RelayFailure(result) =>
645646
log.debug("forwarding {} to relayer", result)
646647
relayer ! result
@@ -1341,11 +1342,13 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
13411342
case PostRevocationAction.RelayHtlc(add) =>
13421343
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
13431344
log.debug("closing in progress: failing {}", add)
1344-
self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), useAttributableErrors = false, TimestampMilli.now(), commit = true)
1345+
// The HTLC is rejected without reading the onion, we default to using attributable errors if the feature is activated.
1346+
self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.now(), commit = true)
13451347
case PostRevocationAction.RejectHtlc(add) =>
13461348
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
13471349
log.debug("closing in progress: rejecting {}", add)
1348-
self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), useAttributableErrors = false, TimestampMilli.now(), commit = true)
1350+
// The HTLC is rejected without reading the onion, we default to using attributable errors if the feature is activated.
1351+
self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.now(), commit = true)
13491352
case PostRevocationAction.RelayFailure(result) =>
13501353
log.debug("forwarding {} to relayer", result)
13511354
relayer ! result

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import fr.acinq.eclair.channel.{CMD_ADD_HTLC, CMD_FAIL_HTLC, CannotExtractShared
2323
import fr.acinq.eclair.crypto.Sphinx
2424
import fr.acinq.eclair.payment.send.Recipient
2525
import fr.acinq.eclair.router.Router.{BlindedHop, ChannelHop, Route}
26+
import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv.UseAttributableError
2627
import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload, PerHopPayload}
2728
import fr.acinq.eclair.wire.protocol._
28-
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, Features, MilliSatoshi, ShortChannelId, TimestampMilli, UInt64, randomKey}
29+
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, Features, MilliSatoshi, ShortChannelId, TimestampMilli, UInt64, randomKey}
2930
import scodec.bits.ByteVector
3031
import scodec.{Attempt, DecodeResult}
3132

@@ -115,6 +116,10 @@ object IncomingPaymentPacket {
115116
case None => privateKey
116117
}
117118
decryptOnion(add.paymentHash, outerOnionDecryptionKey, add.onionRoutingPacket).flatMap {
119+
case DecodedOnionPacket(payload, _) if payload.get[UseAttributableError].isDefined && !features.hasFeature(Features.AttributableError) =>
120+
Left(InvalidOnionPayload(UInt64(20), 0))
121+
case DecodedOnionPacket(payload, _) if payload.get[UseAttributableError].isEmpty && features.hasFeature(Features.AttributableError, Some(FeatureSupport.Mandatory)) =>
122+
Left(InvalidOnionPayload(UInt64(20), 0))
118123
case DecodedOnionPacket(payload, Some(nextPacket)) =>
119124
payload.get[OnionPaymentPayloadTlv.EncryptedRecipientData] match {
120125
case Some(_) if !features.hasFeature(Features.RouteBlinding) => Left(InvalidOnionPayload(UInt64(10), 0))

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import fr.acinq.eclair.wire.protocol.{FailureMessage, InvalidOnionBlinding, Temp
3232
import fr.acinq.eclair.{CustomCommitmentsPlugin, Feature, Features, Logs, MilliSatoshiLong, NodeParams, TimestampMilli}
3333

3434
import scala.concurrent.Promise
35-
import scala.concurrent.duration.DurationInt
3635
import scala.util.Try
3736

3837
/**
@@ -132,7 +131,8 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
132131
val failure = InvalidOnionBlinding(ByteVector32.Zeroes)
133132
CMD_FAIL_MALFORMED_HTLC(htlc.id, failure.onionHash, failure.code, commit = true)
134133
case None =>
135-
CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure()), useAttributableErrors = false, TimestampMilli.min, commit = true)
134+
// By default we use attributable errors if the feature is activated.
135+
CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure()), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.min, commit = true)
136136
}
137137
channel ! cmd
138138
} else {
@@ -262,7 +262,8 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
262262
val failure = InvalidOnionBlinding(ByteVector32.Zeroes)
263263
CMD_FAIL_MALFORMED_HTLC(originHtlcId, failure.onionHash, failure.code, commit = true)
264264
case None =>
265-
ChannelRelay.translateRelayFailure(originHtlcId, fail, useAttributableErrors = false, TimestampMilli.min)
265+
// By default we use attributable errors if the feature is activated.
266+
ChannelRelay.translateRelayFailure(originHtlcId, fail, useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.min)
266267
}
267268
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, originChannelId, cmd)
268269
case Origin.TrampolineRelayedCold(origins) =>
@@ -271,7 +272,8 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
271272
Metrics.Resolved.withTag(Tags.Success, value = false).withTag(Metrics.Relayed, value = true).increment()
272273
// We don't bother decrypting the downstream failure to forward a more meaningful error upstream, it's
273274
// very likely that it won't be actionable anyway because of our node restart.
274-
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure()), useAttributableErrors = false, TimestampMilli.min, commit = true))
275+
// By default we use attributable errors if the feature is activated.
276+
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure()), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.min, commit = true))
275277
}
276278
}
277279
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import fr.acinq.eclair.channel._
2929
import fr.acinq.eclair.db.PendingCommandsDb
3030
import fr.acinq.eclair.payment._
3131
import fr.acinq.eclair.wire.protocol._
32-
import fr.acinq.eclair.{CltvExpiryDelta, Logs, MilliSatoshi, NodeParams, TimestampMilli}
32+
import fr.acinq.eclair.{CltvExpiryDelta, Features, Logs, MilliSatoshi, NodeParams, TimestampMilli}
3333
import grizzled.slf4j.Logging
3434

3535
import scala.concurrent.Promise
@@ -84,15 +84,17 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym
8484
// We are the introduction point of a blinded path: we add a non-negligible delay to make it look like it
8585
// could come from a downstream node.
8686
val delay = Some(500.millis + Random.nextLong(1500).millis)
87-
CMD_FAIL_HTLC(add.id, Right(InvalidOnionBlinding(badOnion.onionHash)), useAttributableErrors = false, TimestampMilli.now(), delay, commit = true)
87+
// By default we use attributable errors if the feature is activated.
88+
CMD_FAIL_HTLC(add.id, Right(InvalidOnionBlinding(badOnion.onionHash)), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.now(), delay, commit = true)
8889
case _ =>
8990
CMD_FAIL_MALFORMED_HTLC(add.id, badOnion.onionHash, badOnion.code, commit = true)
9091
}
9192
log.warning(s"rejecting htlc #${add.id} from channelId=${add.channelId} reason=malformed onionHash=${badOnion.onionHash} failureCode=${badOnion.code}")
9293
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, cmdFail)
9394
case Left(failure) =>
9495
log.warning(s"rejecting htlc #${add.id} from channelId=${add.channelId} reason=$failure")
95-
val cmdFail = CMD_FAIL_HTLC(add.id, Right(failure), useAttributableErrors = false, TimestampMilli.now(), commit = true)
96+
// By default we use attributable errors if the feature is activated.
97+
val cmdFail = CMD_FAIL_HTLC(add.id, Right(failure), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.now(), commit = true)
9698
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, cmdFail)
9799
}
98100

eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PaymentOnion.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ object PaymentOnion {
283283
records.records.find {
284284
case _: EncryptedRecipientData => false
285285
case _: BlindingPoint => false
286+
case _: UseAttributableError => false
286287
case _ => true
287288
} match {
288289
case Some(_) => return Left(ForbiddenTlv(UInt64(0)))
@@ -435,6 +436,7 @@ object PaymentOnion {
435436
case _: EncryptedRecipientData => false
436437
case _: BlindingPoint => false
437438
case _: TotalAmount => false
439+
case _: UseAttributableError => false
438440
case _ => true
439441
} match {
440442
case Some(_) => return Left(ForbiddenTlv(UInt64(0)))

0 commit comments

Comments
 (0)