Skip to content

Commit

Permalink
Add feature
Browse files Browse the repository at this point in the history
  • Loading branch information
thomash-acinq committed Aug 14, 2023
1 parent 2f84f91 commit 8ebf7a4
Show file tree
Hide file tree
Showing 7 changed files with 32 additions and 11 deletions.
1 change: 1 addition & 0 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ eclair {
option_route_blinding = disabled
option_shutdown_anysegwit = optional
option_dual_fund = disabled
option_attributable_error = optional
option_quiesce = disabled
option_onion_messages = optional
option_channel_type = optional
Expand Down
6 changes: 6 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ object Features {
val mandatory = 28
}

case object AttributableError extends Feature with InitFeature with NodeFeature with Bolt11Feature {
val rfcName = "option_attributable_error"
val mandatory = 30
}

// TODO: this should also extend NodeFeature once the spec is finalized
case object Quiescence extends Feature with InitFeature {
val rfcName = "option_quiesce"
Expand Down Expand Up @@ -322,6 +327,7 @@ object Features {
RouteBlinding,
ShutdownAnySegwit,
DualFunding,
AttributableError,
Quiescence,
OnionMessages,
ChannelType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case PostRevocationAction.RejectHtlc(add) =>
log.debug("rejecting incoming htlc {}", add)
// NB: we don't set commit = true, we will sign all updates at once afterwards.
self ! CMD_FAIL_HTLC(add.id, Right(TemporaryChannelFailure(d.channelUpdate)), useAttributableErrors = false, TimestampMilli.now(), commit = true)
// The HTLC is rejected without reading the onion, we default to using attributable errors if the feature is activated.
self ! CMD_FAIL_HTLC(add.id, Right(TemporaryChannelFailure(d.channelUpdate)), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.now(), commit = true)
case PostRevocationAction.RelayFailure(result) =>
log.debug("forwarding {} to relayer", result)
relayer ! result
Expand Down Expand Up @@ -1301,11 +1302,13 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case PostRevocationAction.RelayHtlc(add) =>
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
log.debug("closing in progress: failing {}", add)
self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), useAttributableErrors = false, TimestampMilli.now(), commit = true)
// The HTLC is rejected without reading the onion, we default to using attributable errors if the feature is activated.
self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.now(), commit = true)
case PostRevocationAction.RejectHtlc(add) =>
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
log.debug("closing in progress: rejecting {}", add)
self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), useAttributableErrors = false, TimestampMilli.now(), commit = true)
// The HTLC is rejected without reading the onion, we default to using attributable errors if the feature is activated.
self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.now(), commit = true)
case PostRevocationAction.RelayFailure(result) =>
log.debug("forwarding {} to relayer", result)
relayer ! result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ import fr.acinq.eclair.channel.{CMD_ADD_HTLC, CMD_FAIL_HTLC, CannotExtractShared
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.payment.send.Recipient
import fr.acinq.eclair.router.Router.{BlindedHop, ChannelHop, Route}
import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv.UseAttributableError
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, randomKey}
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, Features, MilliSatoshi, ShortChannelId, TimestampMilli, UInt64, randomKey}
import scodec.bits.ByteVector
import scodec.{Attempt, DecodeResult}

Expand Down Expand Up @@ -115,6 +116,10 @@ object IncomingPaymentPacket {
case None => privateKey
}
decryptOnion(add.paymentHash, outerOnionDecryptionKey, add.onionRoutingPacket).flatMap {
case DecodedOnionPacket(payload, _) if payload.get[UseAttributableError].isDefined && !features.hasFeature(Features.AttributableError) =>
Left(InvalidOnionPayload(UInt64(20), 0))
case DecodedOnionPacket(payload, _) if payload.get[UseAttributableError].isEmpty && features.hasFeature(Features.AttributableError, Some(FeatureSupport.Mandatory)) =>
Left(InvalidOnionPayload(UInt64(20), 0))
case DecodedOnionPacket(payload, Some(nextPacket)) =>
payload.get[OnionPaymentPayloadTlv.EncryptedRecipientData] match {
case Some(_) if !features.hasFeature(Features.RouteBlinding) => Left(InvalidOnionPayload(UInt64(10), 0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import fr.acinq.eclair.wire.protocol.{FailureMessage, InvalidOnionBlinding, Temp
import fr.acinq.eclair.{CustomCommitmentsPlugin, Feature, Features, Logs, MilliSatoshiLong, NodeParams, TimestampMilli}

import scala.concurrent.Promise
import scala.concurrent.duration.DurationInt
import scala.util.Try

/**
Expand Down Expand Up @@ -132,7 +131,8 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
val failure = InvalidOnionBlinding(ByteVector32.Zeroes)
CMD_FAIL_MALFORMED_HTLC(htlc.id, failure.onionHash, failure.code, commit = true)
case None =>
CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure()), useAttributableErrors = false, TimestampMilli.min, commit = true)
// By default we use attributable errors if the feature is activated.
CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure()), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.min, commit = true)
}
channel ! cmd
} else {
Expand Down Expand Up @@ -262,7 +262,8 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
val failure = InvalidOnionBlinding(ByteVector32.Zeroes)
CMD_FAIL_MALFORMED_HTLC(originHtlcId, failure.onionHash, failure.code, commit = true)
case None =>
ChannelRelay.translateRelayFailure(originHtlcId, fail, useAttributableErrors = false, TimestampMilli.min)
// By default we use attributable errors if the feature is activated.
ChannelRelay.translateRelayFailure(originHtlcId, fail, useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.min)
}
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, originChannelId, cmd)
case Origin.TrampolineRelayedCold(origins) =>
Expand All @@ -271,7 +272,8 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
Metrics.Resolved.withTag(Tags.Success, value = false).withTag(Metrics.Relayed, value = true).increment()
// We don't bother decrypting the downstream failure to forward a more meaningful error upstream, it's
// very likely that it won't be actionable anyway because of our node restart.
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure()), useAttributableErrors = false, TimestampMilli.min, commit = true))
// By default we use attributable errors if the feature is activated.
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure()), useAttributableErrors = nodeParams.features.hasFeature(Features.AttributableError), TimestampMilli.min, commit = true))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import fr.acinq.eclair.channel._
import fr.acinq.eclair.db.PendingCommandsDb
import fr.acinq.eclair.payment._
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CltvExpiryDelta, Logs, MilliSatoshi, NodeParams, TimestampMilli}
import fr.acinq.eclair.{CltvExpiryDelta, Features, Logs, MilliSatoshi, NodeParams, TimestampMilli}
import grizzled.slf4j.Logging

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ object PaymentOnion {
records.records.find {
case _: EncryptedRecipientData => false
case _: BlindingPoint => false
case _: UseAttributableError => false
case _ => true
} match {
case Some(_) => return Left(ForbiddenTlv(UInt64(0)))
Expand Down Expand Up @@ -435,6 +436,7 @@ object PaymentOnion {
case _: EncryptedRecipientData => false
case _: BlindingPoint => false
case _: TotalAmount => false
case _: UseAttributableError => false
case _ => true
} match {
case Some(_) => return Left(ForbiddenTlv(UInt64(0)))
Expand Down

0 comments on commit 8ebf7a4

Please sign in to comment.