mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-19 01:43:22 +01:00
Allow including routing hints when creating Bolt 11 invoice (#2909)
When nodes only have private channels, they must include routing hints in their Bolt 11 invoices to be able to receive payments. We add a parameter to the `createinvoice` RPC for this. Note that this may leak the channel outpoint if `scid_alias` isn't used. Fixes #2802
This commit is contained in:
parent
7b25c5adca
commit
885b45bd75
@ -27,6 +27,7 @@ Eclair will not allow remote peers to open new obsolete channels that do not sup
|
||||
### API changes
|
||||
|
||||
- `channelstats` now takes optional parameters `--count` and `--skip` to control pagination. By default, it will return first 10 entries. (#2890)
|
||||
- `createinvoice` now takes an optional `--privateChannelIds` parameter that can be used to add routing hints through private channels. (#2909)
|
||||
|
||||
### Miscellaneous improvements and bug fixes
|
||||
|
||||
|
@ -114,7 +114,7 @@ trait Eclair {
|
||||
|
||||
def nodes(nodeIds_opt: Option[Set[PublicKey]] = None)(implicit timeout: Timeout): Future[Iterable[NodeAnnouncement]]
|
||||
|
||||
def receive(description: Either[String, ByteVector32], amount_opt: Option[MilliSatoshi], expire_opt: Option[Long], fallbackAddress_opt: Option[String], paymentPreimage_opt: Option[ByteVector32])(implicit timeout: Timeout): Future[Bolt11Invoice]
|
||||
def receive(description: Either[String, ByteVector32], amount_opt: Option[MilliSatoshi], expire_opt: Option[Long], fallbackAddress_opt: Option[String], paymentPreimage_opt: Option[ByteVector32], privateChannelIds_opt: Option[List[ByteVector32]])(implicit timeout: Timeout): Future[Bolt11Invoice]
|
||||
|
||||
def newAddress(): Future[String]
|
||||
|
||||
@ -330,14 +330,28 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
|
||||
}
|
||||
}
|
||||
|
||||
override def receive(description: Either[String, ByteVector32], amount_opt: Option[MilliSatoshi], expire_opt: Option[Long], fallbackAddress_opt: Option[String], paymentPreimage_opt: Option[ByteVector32])(implicit timeout: Timeout): Future[Bolt11Invoice] = {
|
||||
override def receive(description: Either[String, ByteVector32], amount_opt: Option[MilliSatoshi], expire_opt: Option[Long], fallbackAddress_opt: Option[String], paymentPreimage_opt: Option[ByteVector32], privateChannelIds_opt: Option[List[ByteVector32]])(implicit timeout: Timeout): Future[Bolt11Invoice] = {
|
||||
fallbackAddress_opt.foreach { fa =>
|
||||
// If it's not a valid bitcoin address we throw an exception.
|
||||
addressToPublicKeyScript(appKit.nodeParams.chainHash, fa) match {
|
||||
case Left(failure) => throw new IllegalArgumentException(failure.toString)
|
||||
case Right(_) => ()
|
||||
}
|
||||
} // if it's not a bitcoin address throws an exception
|
||||
appKit.paymentHandler.toTyped.ask(ref => ReceiveStandardPayment(ref, amount_opt, description, expire_opt, fallbackAddress_opt = fallbackAddress_opt, paymentPreimage_opt = paymentPreimage_opt))
|
||||
}
|
||||
for {
|
||||
routingHints <- getInvoiceRoutingHints(privateChannelIds_opt)
|
||||
invoice <- appKit.paymentHandler.toTyped.ask[Bolt11Invoice](ref => ReceiveStandardPayment(ref, amount_opt, description, expire_opt, routingHints, fallbackAddress_opt, paymentPreimage_opt))
|
||||
} yield invoice
|
||||
}
|
||||
|
||||
private def getInvoiceRoutingHints(privateChannelIds_opt: Option[List[ByteVector32]])(implicit timeout: Timeout): Future[List[List[Bolt11Invoice.ExtraHop]]] = {
|
||||
privateChannelIds_opt match {
|
||||
case Some(channelIds) =>
|
||||
(appKit.router ? GetRouterData).mapTo[Router.Data].map {
|
||||
d => channelIds.flatMap(cid => d.privateChannels.get(cid)).flatMap(_.toIncomingExtraHop).map(hop => hop :: Nil)
|
||||
}
|
||||
case None => Future.successful(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
override def newAddress(): Future[String] = {
|
||||
|
@ -313,7 +313,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
|
||||
|
||||
val fallBackAddressRaw = "muhtvdmsnbQEPFuEmxcChX58fGvXaaUoVt"
|
||||
val eclair = new EclairImpl(kit)
|
||||
eclair.receive(Left("some desc"), Some(123 msat), Some(456), Some(fallBackAddressRaw), None)
|
||||
eclair.receive(Left("some desc"), Some(123 msat), Some(456), Some(fallBackAddressRaw), None, None)
|
||||
val receive = paymentHandler.expectMsgType[ReceiveStandardPayment]
|
||||
|
||||
assert(receive.amount_opt.contains(123 msat))
|
||||
@ -321,7 +321,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
|
||||
assert(receive.fallbackAddress_opt.contains(fallBackAddressRaw))
|
||||
|
||||
// try with wrong address format
|
||||
assertThrows[IllegalArgumentException](eclair.receive(Left("some desc"), Some(123 msat), Some(456), Some("wassa wassa"), None))
|
||||
assertThrows[IllegalArgumentException](eclair.receive(Left("some desc"), Some(123 msat), Some(456), Some("wassa wassa"), None, None))
|
||||
}
|
||||
|
||||
test("passing a payment_preimage to /createinvoice should result in an invoice with payment_hash=H(payment_preimage)") { f =>
|
||||
@ -331,7 +331,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
|
||||
val eclair = new EclairImpl(kitWithPaymentHandler)
|
||||
val paymentPreimage = randomBytes32()
|
||||
|
||||
eclair.receive(Left("some desc"), None, None, None, Some(paymentPreimage)).pipeTo(sender.ref)
|
||||
eclair.receive(Left("some desc"), None, None, None, Some(paymentPreimage), None).pipeTo(sender.ref)
|
||||
assert(sender.expectMsgType[Invoice].paymentHash == Crypto.sha256(paymentPreimage))
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ package fr.acinq.eclair.api.handlers
|
||||
|
||||
import akka.http.scaladsl.server.Route
|
||||
import fr.acinq.bitcoin.scalacompat.ByteVector32
|
||||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
||||
import fr.acinq.eclair.api.Service
|
||||
import fr.acinq.eclair.api.directives.EclairDirectives
|
||||
import fr.acinq.eclair.api.serde.FormParamExtractors._
|
||||
@ -28,9 +29,9 @@ trait Invoice {
|
||||
import fr.acinq.eclair.api.serde.JsonSupport.{formats, marshaller, serialization}
|
||||
|
||||
val createInvoice: Route = postRequest("createinvoice") { implicit t =>
|
||||
formFields("description".as[String].?, "descriptionHash".as[ByteVector32].?, amountMsatFormParam.?, "expireIn".as[Long].?, "fallbackAddress".as[String].?, "paymentPreimage".as[ByteVector32](bytes32Unmarshaller).?) {
|
||||
case (Some(desc), None, amountMsat, expire, fallBackAddress, paymentPreimage_opt) => complete(eclairApi.receive(Left(desc), amountMsat, expire, fallBackAddress, paymentPreimage_opt))
|
||||
case (None, Some(desc), amountMsat, expire, fallBackAddress, paymentPreimage_opt) => complete(eclairApi.receive(Right(desc), amountMsat, expire, fallBackAddress, paymentPreimage_opt))
|
||||
formFields("description".as[String].?, "descriptionHash".as[ByteVector32].?, amountMsatFormParam.?, "expireIn".as[Long].?, "fallbackAddress".as[String].?, "paymentPreimage".as[ByteVector32](bytes32Unmarshaller).?, "privateChannelIds".as[List[ByteVector32]](bytes32ListUnmarshaller).?) {
|
||||
case (Some(desc), None, amountMsat, expire, fallBackAddress, paymentPreimage_opt, privateChannelIds_opt) => complete(eclairApi.receive(Left(desc), amountMsat, expire, fallBackAddress, paymentPreimage_opt, privateChannelIds_opt))
|
||||
case (None, Some(desc), amountMsat, expire, fallBackAddress, paymentPreimage_opt, privateChannelIds_opt) => complete(eclairApi.receive(Right(desc), amountMsat, expire, fallBackAddress, paymentPreimage_opt, privateChannelIds_opt))
|
||||
case _ => failWith(new RuntimeException("Either 'description' (string) or 'descriptionHash' (sha256 hash of description string) must be supplied"))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user