Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/release-notes/eclair-vnext.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@

## Major changes

### Trampoline payments

Trampoline payments allow nodes running on constrained devices to sync only a small portion of the network and leverage trampoline nodes to calculate the missing parts of the payment route, while providing the same privacy as fully source-routed payments.

Eclair started supporting [trampoline payments](https://github.com/lightning/bolts/pull/829) in v0.3.3.
The specification has evolved since then and has recently been added to the [BOLTs](https://github.com/lightning/bolts/pull/836).

With this release, eclair nodes are able to relay and receive trampoline payments (activated by default).
This feature can be disabled if you don't want to relay or receive trampoline payments:

```conf
eclair.features.trampoline_routing = disabled
```

### Update minimal version of Bitcoin Core

With this release, eclair requires using Bitcoin Core 30.x.
Expand Down
8 changes: 4 additions & 4 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ eclair {
node-alias = "eclair"
node-color = "49daaa"

trampoline-payments-enable = false // TODO: @t-bast: once spec-ed this should use a global feature flag
// see https://github.com/lightningnetwork/lightning-rfc/blob/master/09-features.md
features {
// option_upfront_shutdown_script is not activated by default.
Expand Down Expand Up @@ -91,6 +90,7 @@ eclair {
option_simple_close = optional
option_splice = optional
option_simple_taproot = optional
trampoline_routing = optional
trampoline_payment_prototype = disabled
async_payment_prototype = disabled
on_the_fly_funding = disabled
Expand Down Expand Up @@ -733,11 +733,11 @@ eclair {

offers {
// Minimum length of an offer blinded path when hiding our real node id
message-path-min-length = 2
message-path-min-length = 1
// Number of payment paths to put in Bolt12 invoices when hiding our real node id
payment-path-count = 2
payment-path-count = 1
// Length of payment paths to put in Bolt12 invoices when hiding our real node id
payment-path-length = 2
payment-path-length = 1
// Expiry delta of payment paths to put in Bolt12 invoices when hiding our real node id
payment-path-expiry-delta = 500
}
Expand Down
7 changes: 7 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ trait Eclair {

def sendBlocking(externalId_opt: Option[String], amount: MilliSatoshi, invoice: Bolt11Invoice, maxAttempts_opt: Option[Int] = None, maxFeeFlat_opt: Option[Satoshi] = None, maxFeePct_opt: Option[Double] = None, pathFindingExperimentName_opt: Option[String] = None)(implicit timeout: Timeout): Future[PaymentEvent]

def sendTrampoline(trampolineNodeId: PublicKey, amount: MilliSatoshi, invoice: Bolt11Invoice)(implicit timeout: Timeout): Future[PaymentEvent]

def sendWithPreimage(externalId_opt: Option[String], recipientNodeId: PublicKey, amount: MilliSatoshi, paymentPreimage: ByteVector32 = randomBytes32(), maxAttempts_opt: Option[Int] = None, maxFeeFlat_opt: Option[Satoshi] = None, maxFeePct_opt: Option[Double] = None, pathFindingExperimentName_opt: Option[String] = None)(implicit timeout: Timeout): Future[UUID]

def sentInfo(id: PaymentIdentifier)(implicit timeout: Timeout): Future[Seq[OutgoingPayment]]
Expand Down Expand Up @@ -544,6 +546,11 @@ class EclairImpl(val appKit: Kit) extends Eclair with Logging with SpendFromChan
}
}

override def sendTrampoline(trampolineNodeId: PublicKey, amount: MilliSatoshi, invoice: Bolt11Invoice)(implicit timeout: Timeout): Future[PaymentEvent] = {
val routeParams = appKit.nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams
appKit.paymentInitiator.toTyped.ask[PaymentEvent](ref => SendTrampolinePayment(ref.toClassic, invoice, trampolineNodeId, routeParams, blockUntilComplete = true))
}

override def sendWithPreimage(externalId_opt: Option[String], recipientNodeId: PublicKey, amount: MilliSatoshi, paymentPreimage: ByteVector32, maxAttempts_opt: Option[Int], maxFeeFlat_opt: Option[Satoshi], maxFeePct_opt: Option[Double], pathFindingExperimentName_opt: Option[String])(implicit timeout: Timeout): Future[UUID] = {
val maxAttempts = maxAttempts_opt.getOrElse(appKit.nodeParams.maxPaymentAttempts)
getRouteParams(pathFindingExperimentName_opt) match {
Expand Down
16 changes: 8 additions & 8 deletions eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,11 @@ object Features {
val mandatory = 54
}

case object TrampolinePayment extends Feature with InitFeature with NodeFeature with Bolt11Feature with Bolt12Feature {
val rfcName = "trampoline_routing"
val mandatory = 56
}

case object SimpleClose extends Feature with InitFeature with NodeFeature {
val rfcName = "option_simple_close"
val mandatory = 60
Expand All @@ -420,12 +425,7 @@ object Features {
val mandatory = 132
}

// TODO: @t-bast: update feature bits once spec-ed (currently reserved here: https://github.com/lightningnetwork/lightning-rfc/issues/605)
// We're not advertising these bits yet in our announcements, clients have to assume support.
// This is why we haven't added them yet to `areSupported`.
// The version of trampoline enabled by this feature bit does not match the latest spec PR: once the spec is accepted,
// we will introduce a new version of trampoline that will work in parallel to this legacy one, until we can safely
// deprecate it.
// TODO: we can remove this feature once we've migrated existing Phoenix users
case object TrampolinePaymentPrototype extends Feature with InitFeature with NodeFeature with Bolt11Feature {
val rfcName = "trampoline_payment_prototype"
val mandatory = 148
Expand Down Expand Up @@ -496,6 +496,7 @@ object Features {
SimpleTaprootChannels,
SimpleTaprootChannelsPhoenix,
WakeUpNotificationClient,
TrampolinePayment,
TrampolinePaymentPrototype,
AsyncPaymentPrototype,
SplicePrototype,
Expand All @@ -512,12 +513,11 @@ object Features {
AnchorOutputs -> (StaticRemoteKey :: Nil),
AnchorOutputsZeroFeeHtlcTx -> (StaticRemoteKey :: Nil),
RouteBlinding -> (VariableLengthOnion :: Nil),
TrampolinePaymentPrototype -> (PaymentSecret :: Nil),
KeySend -> (VariableLengthOnion :: Nil),
SimpleClose -> (ShutdownAnySegwit :: Nil),
SimpleTaprootChannels -> (ChannelType :: SimpleClose :: Nil),
SimpleTaprootChannelsPhoenix -> (ChannelType :: SimpleClose :: Nil),
AsyncPaymentPrototype -> (TrampolinePaymentPrototype :: Nil),
AsyncPaymentPrototype -> (TrampolinePayment :: Nil),
FundingFeeCredit -> (OnTheFlyFunding :: Nil)
)

Expand Down
2 changes: 0 additions & 2 deletions eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
socksProxy_opt: Option[Socks5ProxyParams],
maxPaymentAttempts: Int,
paymentFinalExpiry: PaymentFinalExpiryConf,
enableTrampolinePayment: Boolean,
balanceCheckInterval: FiniteDuration,
blockchainWatchdogThreshold: Int,
blockchainWatchdogSources: Seq[String],
Expand Down Expand Up @@ -691,7 +690,6 @@ object NodeParams extends Logging {
socksProxy_opt = socksProxy_opt,
maxPaymentAttempts = config.getInt("max-payment-attempts"),
paymentFinalExpiry = PaymentFinalExpiryConf(CltvExpiryDelta(config.getInt("send.recipient-final-expiry.min-delta")), CltvExpiryDelta(config.getInt("send.recipient-final-expiry.max-delta"))),
enableTrampolinePayment = config.getBoolean("trampoline-payments-enable"),
balanceCheckInterval = FiniteDuration(config.getDuration("balance-check-interval").getSeconds, TimeUnit.SECONDS),
blockchainWatchdogThreshold = config.getInt("blockchain-watchdog.missing-blocks-threshold"),
blockchainWatchdogSources = config.getStringList("blockchain-watchdog.sources").asScala.toSeq,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,12 @@ object Bolt12Invoice {
features: Features[Bolt12Feature],
paths: Seq[PaymentBlindedRoute],
additionalTlvs: Set[InvoiceTlv] = Set.empty,
customTlvs: Set[GenericTlv] = Set.empty): Bolt12Invoice = {
customTlvs: Set[GenericTlv] = Set.empty,
accountable: Boolean = true): Bolt12Invoice = {
val amount = request.amount
val tlvs: Set[InvoiceTlv] = removeSignature(request.records).records ++ Set(
Some(InvoicePaths(paths.map(_.route))),
Some(InvoiceAccountable()),
if (accountable) Some(InvoiceAccountable()) else None,
Some(InvoiceBlindedPay(paths.map(_.paymentInfo))),
Some(InvoiceCreatedAt(TimestampSecond.now())),
Some(InvoiceRelativeExpiry(invoiceExpiry.toSeconds)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ object Monitoring {
def apply(cmdFail: CMD_FAIL_HTLC): String = cmdFail.reason match {
case _: FailureReason.EncryptedDownstreamFailure => Remote
case FailureReason.LocalFailure(f) => f.getClass.getSimpleName
case FailureReason.LocalTrampolineFailure(f) => f.getClass.getSimpleName
}

def apply(pf: PaymentFailure): String = pf match {
Expand Down
Loading
Loading