Skip to content

Commit 242fa8c

Browse files
committed
comments t-bast
1 parent d4261f5 commit 242fa8c

File tree

20 files changed

+178
-100
lines changed

20 files changed

+178
-100
lines changed

docs/release-notes/eclair-vnext.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,39 @@
99
With this release, eclair requires using Bitcoin Core 28.1.
1010
Newer versions of Bitcoin Core may be used, but have not been extensively tested.
1111

12+
### Offers
13+
14+
You can now create an offer with
15+
16+
```
17+
./eclair-cli createoffer --description=coffee --amountMsat=20000 --expireInSeconds=3600 --issuer=me@example.com --blindedPathsFirstNodeId=03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f
18+
```
19+
20+
All parameters are optional and omitting all of them will create a minimal offer with your public node id.
21+
You can also disable offers and list offers with
22+
23+
```
24+
./eclair-cli disableoffer --offer=lnoxxx
25+
./eclair-cli listoffers
26+
```
27+
28+
If you specify `--blindedPathsFirstNodeId`, your public node id will not be in the offer, you will instead be hidden behind a blinded path starting at the node that you have chosen.
29+
You can configure the number and length of blinded paths used in `eclair.conf`:
30+
31+
```
32+
offers {
33+
// Minimum length of an offer blinded path
34+
message-path-min-length = 2
35+
36+
// Number of payment paths to put in Bolt12 invoices
37+
payment-path-count = 2
38+
// Length of payment paths to put in Bolt12 invoices
39+
payment-path-length = 4
40+
// Expiry delta of payment paths to put in Bolt12 invoices
41+
payment-path-expiry-delta = 500
42+
}
43+
```
44+
1245
### Simplified mutual close
1346

1447
This release includes support for the latest [mutual close protocol](https://github.com/lightning/bolts/pull/1205).

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,11 +637,15 @@ eclair {
637637
cleanup-frequency = 1 day
638638
}
639639

640-
managed-offers {
640+
offers {
641+
// Minimum length of an offer blinded path when hiding our real node id
641642
message-path-min-length = 2
642643

644+
// Number of payment paths to put in Bolt12 invoices when hiding our real node id
643645
payment-path-count = 2
646+
// Length of payment paths to put in Bolt12 invoices when hiding our real node id
644647
payment-path-length = 4
648+
// Expiry delta of payment paths to put in Bolt12 invoices when hiding our real node id
645649
payment-path-expiry-delta = 500
646650
}
647651
}

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ trait Eclair {
128128

129129
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]
130130

131-
def createOffer(description_opt: Option[String], amount_opt: Option[MilliSatoshi], expiry_opt: Option[TimestampSecond], issuer_opt: Option[String], firstNodeId_opt: Option[PublicKey])(implicit timeout: Timeout): Future[Offer]
131+
def createOffer(description_opt: Option[String], amount_opt: Option[MilliSatoshi], expire_opt: Option[Long], issuer_opt: Option[String], blindedPathsFirstNodeId_opt: Option[PublicKey])(implicit timeout: Timeout): Future[Offer]
132132

133-
def disableOffer(offer: Offer)(implicit timeout: Timeout): Unit
133+
def disableOffer(offer: Offer)(implicit timeout: Timeout): Future[Unit]
134134

135135
def listOffers(onlyActive: Boolean = true)(implicit timeout: Timeout): Future[Seq[Offer]]
136136

@@ -396,22 +396,22 @@ class EclairImpl(val appKit: Kit) extends Eclair with Logging with SpendFromChan
396396
}
397397
}
398398

399-
override def createOffer(description_opt: Option[String], amount_opt: Option[MilliSatoshi], expiry_opt: Option[TimestampSecond], issuer_opt: Option[String], firstNodeId_opt: Option[PublicKey])(implicit timeout: Timeout): Future[Offer] = {
399+
override def createOffer(description_opt: Option[String], amount_opt: Option[MilliSatoshi], expireInSeconds_opt: Option[Long], issuer_opt: Option[String], blindedPathsFirstNodeId_opt: Option[PublicKey])(implicit timeout: Timeout): Future[Offer] = {
400400
val offerCreator = appKit.system.spawnAnonymous(OfferCreator(appKit.nodeParams, appKit.router, appKit.offerManager, appKit.defaultOfferHandler))
401-
offerCreator.ask[Either[String, Offer]](replyTo => OfferCreator.Create(replyTo, description_opt, amount_opt, expiry_opt, issuer_opt, firstNodeId_opt))
401+
val expiry_opt = expireInSeconds_opt.map(TimestampSecond.now() + _)
402+
offerCreator.ask[OfferCreator.CreateOfferResult](replyTo => OfferCreator.Create(replyTo, description_opt, amount_opt, expiry_opt, issuer_opt, blindedPathsFirstNodeId_opt))
402403
.flatMap {
403-
case Left(errorMessage) => Future.failed(new Exception(errorMessage))
404-
case Right(offer) => Future.successful(offer)
404+
case OfferCreator.CreateOfferError(reason) => Future.failed(new Exception(reason))
405+
case OfferCreator.CreatedOffer(offer) => Future.successful(offer)
405406
}
406407
}
407408

408-
override def disableOffer(offer: Offer)(implicit timeout: Timeout): Unit = {
409+
override def disableOffer(offer: Offer)(implicit timeout: Timeout): Future[Unit] = Future {
409410
appKit.offerManager ! OfferManager.DisableOffer(offer)
410-
appKit.nodeParams.db.managedOffers.disableOffer(offer)
411411
}
412412

413413
override def listOffers(onlyActive: Boolean = true)(implicit timeout: Timeout): Future[Seq[Offer]] = Future {
414-
appKit.nodeParams.db.managedOffers.listOffers(onlyActive).map(_.offer)
414+
appKit.nodeParams.db.offers.listOffers(onlyActive).map(_.offer)
415415
}
416416

417417
override def newAddress(): Future[String] = {

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -709,10 +709,10 @@ object NodeParams extends Logging {
709709
cleanUpFrequency = FiniteDuration(config.getDuration("peer-storage.cleanup-frequency").getSeconds, TimeUnit.SECONDS),
710710
),
711711
offersConfig = OffersConfig(
712-
messagePathMinLength = config.getInt("managed-offers.message-path-min-length"),
713-
paymentPathCount = config.getInt("managed-offers.payment-path-count"),
714-
paymentPathLength = config.getInt("managed-offers.payment-path-length"),
715-
paymentPathCltvExpiryDelta = CltvExpiryDelta(config.getInt("managed-offers.payment-path-expiry-delta")),
712+
messagePathMinLength = config.getInt("offers.message-path-min-length"),
713+
paymentPathCount = config.getInt("offers.payment-path-count"),
714+
paymentPathLength = config.getInt("offers.payment-path-length"),
715+
paymentPathCltvExpiryDelta = CltvExpiryDelta(config.getInt("offers.payment-path-expiry-delta")),
716716
)
717717
)
718718
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import fr.acinq.eclair.db.FileBackupHandler.FileBackupParams
4040
import fr.acinq.eclair.db.{Databases, DbEventHandler, FileBackupHandler, PeerStorageCleaner}
4141
import fr.acinq.eclair.io._
4242
import fr.acinq.eclair.message.Postman
43-
import fr.acinq.eclair.payment.offer.{DefaultHandler, OfferManager}
43+
import fr.acinq.eclair.payment.offer.{DefaultOfferHandler, OfferManager}
4444
import fr.acinq.eclair.payment.receive.PaymentHandler
4545
import fr.acinq.eclair.payment.relay.{AsyncPaymentTriggerer, PostRestartHtlcCleaner, Relayer}
4646
import fr.acinq.eclair.payment.send.{Autoprobe, PaymentInitiator}
@@ -358,8 +358,8 @@ class Setup(val datadir: File,
358358
dbEventHandler = system.actorOf(SimpleSupervisor.props(DbEventHandler.props(nodeParams), "db-event-handler", SupervisorStrategy.Resume))
359359
register = system.actorOf(SimpleSupervisor.props(Register.props(), "register", SupervisorStrategy.Resume))
360360
offerManager = system.spawn(Behaviors.supervise(OfferManager(nodeParams, paymentTimeout = 1 minute)).onFailure(typed.SupervisorStrategy.resume), name = "offer-manager")
361-
defaultOfferHandler = system.spawn(Behaviors.supervise(DefaultHandler(nodeParams, router)).onFailure(typed.SupervisorStrategy.resume), name = "default-offer-handler")
362-
_ = for (offer <- nodeParams.db.managedOffers.listOffers(onlyActive = true)) offerManager ! OfferManager.RegisterOffer(offer.offer, None, offer.pathId_opt, defaultOfferHandler)
361+
defaultOfferHandler = system.spawn(Behaviors.supervise(DefaultOfferHandler(nodeParams, router)).onFailure(typed.SupervisorStrategy.resume), name = "default-offer-handler")
362+
_ = for (offer <- nodeParams.db.offers.listOffers(onlyActive = true)) offerManager ! OfferManager.RegisterOffer(offer.offer, None, offer.pathId_opt, defaultOfferHandler)
363363
paymentHandler = system.actorOf(SimpleSupervisor.props(PaymentHandler.props(nodeParams, register, offerManager), "payment-handler", SupervisorStrategy.Resume))
364364
triggerer = system.spawn(Behaviors.supervise(AsyncPaymentTriggerer()).onFailure(typed.SupervisorStrategy.resume), name = "async-payment-triggerer")
365365
peerReadyManager = system.spawn(Behaviors.supervise(PeerReadyManager()).onFailure(typed.SupervisorStrategy.restart), name = "peer-ready-manager")

eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ trait Databases {
4343
def channels: ChannelsDb
4444
def peers: PeersDb
4545
def payments: PaymentsDb
46-
def managedOffers: OffersDb
46+
def offers: OffersDb
4747
def pendingCommands: PendingCommandsDb
4848
def liquidity: LiquidityDb
4949
//@formatter:on
@@ -67,7 +67,7 @@ object Databases extends Logging {
6767
channels: SqliteChannelsDb,
6868
peers: SqlitePeersDb,
6969
payments: SqlitePaymentsDb,
70-
managedOffers: SqliteOffersDb,
70+
offers: SqliteOffersDb,
7171
pendingCommands: SqlitePendingCommandsDb,
7272
private val backupConnection: Connection) extends Databases with FileBackup {
7373
override def backup(backupFile: File): Unit = SqliteUtils.using(backupConnection.createStatement()) {
@@ -87,7 +87,7 @@ object Databases extends Logging {
8787
channels = new SqliteChannelsDb(eclairJdbc),
8888
peers = new SqlitePeersDb(eclairJdbc),
8989
payments = new SqlitePaymentsDb(eclairJdbc),
90-
managedOffers = new SqliteOffersDb(eclairJdbc),
90+
offers = new SqliteOffersDb(eclairJdbc),
9191
pendingCommands = new SqlitePendingCommandsDb(eclairJdbc),
9292
backupConnection = eclairJdbc
9393
)
@@ -100,7 +100,7 @@ object Databases extends Logging {
100100
channels: PgChannelsDb,
101101
peers: PgPeersDb,
102102
payments: PgPaymentsDb,
103-
managedOffers: PgOffersDb,
103+
offers: PgOffersDb,
104104
pendingCommands: PgPendingCommandsDb,
105105
dataSource: HikariDataSource,
106106
lock: PgLock) extends Databases with ExclusiveLock {
@@ -161,7 +161,7 @@ object Databases extends Logging {
161161
channels = new PgChannelsDb,
162162
peers = new PgPeersDb,
163163
payments = new PgPaymentsDb,
164-
managedOffers = new PgOffersDb,
164+
offers = new PgOffersDb,
165165
pendingCommands = new PgPendingCommandsDb,
166166
dataSource = ds,
167167
lock = lock)

eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ case class DualDatabases(primary: Databases, secondary: Databases) extends Datab
3636
override val channels: ChannelsDb = DualChannelsDb(primary.channels, secondary.channels)
3737
override val peers: PeersDb = DualPeersDb(primary.peers, secondary.peers)
3838
override val payments: PaymentsDb = DualPaymentsDb(primary.payments, secondary.payments)
39-
override val managedOffers: OffersDb = DualOffersDb(primary.managedOffers, secondary.managedOffers)
39+
override val offers: OffersDb = DualOffersDb(primary.offers, secondary.offers)
4040
override val pendingCommands: PendingCommandsDb = DualPendingCommandsDb(primary.pendingCommands, secondary.pendingCommands)
4141
override val liquidity: LiquidityDb = DualLiquidityDb(primary.liquidity, secondary.liquidity)
4242

@@ -420,11 +420,6 @@ case class DualOffersDb(primary: OffersDb, secondary: OffersDb) extends OffersDb
420420
primary.disableOffer(offer)
421421
}
422422

423-
override def enableOffer(offer: OfferTypes.Offer): Unit = {
424-
runAsync(secondary.enableOffer(offer))
425-
primary.enableOffer(offer)
426-
}
427-
428423
override def listOffers(onlyActive: Boolean): Seq[OfferData] = {
429424
runAsync(secondary.listOffers(onlyActive))
430425
primary.listOffers(onlyActive)

eclair-core/src/main/scala/fr/acinq/eclair/db/OffersDb.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ trait OffersDb {
3636
*/
3737
def disableOffer(offer: Offer): Unit
3838

39-
/**
40-
* Activate an offer that was previously disabled.
41-
*/
42-
def enableOffer(offer: Offer): Unit
43-
4439
/**
4540
* List offers managed by eclair.
4641
* @param onlyActive Whether to return only active offers or also disabled ones.

eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgOffersDb.scala

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ class PgOffersDb(implicit ds: DataSource, lock: PgLock) extends OffersDb with Lo
4545
using(pg.createStatement()) { statement =>
4646
getVersion(statement, DB_NAME) match {
4747
case None =>
48-
statement.executeUpdate("CREATE SCHEMA offers")
49-
statement.executeUpdate("CREATE TABLE offers.managed (offer_id TEXT NOT NULL PRIMARY KEY, offer TEXT NOT NULL, path_id TEXT, created_at TIMESTAMP WITH TIME ZONE NOT NULL, is_active BOOLEAN NOT NULL)")
50-
statement.executeUpdate("CREATE INDEX offer_is_active_idx ON offers.managed(is_active)")
48+
statement.executeUpdate("CREATE SCHEMA IF NOT EXISTS payments")
49+
statement.executeUpdate("CREATE TABLE payments.offers (offer_id TEXT NOT NULL PRIMARY KEY, offer TEXT NOT NULL, path_id TEXT, created_at TIMESTAMP WITH TIME ZONE NOT NULL, disabled_at TIMESTAMP WITH TIME ZONE)")
50+
statement.executeUpdate("CREATE INDEX offer_disabled_at_idx ON payments.offers(disabled_at)")
5151
case Some(CURRENT_VERSION) => () // table is up-to-date, nothing to do
5252
case Some(unknownVersion) => throw new RuntimeException(s"Unknown version of DB $DB_NAME found, version=$unknownVersion")
5353
}
@@ -57,7 +57,7 @@ class PgOffersDb(implicit ds: DataSource, lock: PgLock) extends OffersDb with Lo
5757

5858
override def addOffer(offer: OfferTypes.Offer, pathId_opt: Option[ByteVector32]): Unit = withMetrics("offers/add", DbBackends.Postgres){
5959
withLock { pg =>
60-
using(pg.prepareStatement("INSERT INTO offers.managed (offer_id, offer, path_id, created_at, is_active) VALUES (?, ?, ?, ?, TRUE)")) { statement =>
60+
using(pg.prepareStatement("INSERT INTO payments.offers (offer_id, offer, path_id, created_at, disabled_at) VALUES (?, ?, ?, ?, NULL)")) { statement =>
6161
statement.setString(1, offer.offerId.toHex)
6262
statement.setString(2, offer.toString)
6363
pathId_opt match {
@@ -72,17 +72,9 @@ class PgOffersDb(implicit ds: DataSource, lock: PgLock) extends OffersDb with Lo
7272

7373
override def disableOffer(offer: OfferTypes.Offer): Unit = withMetrics("offers/disable", DbBackends.Postgres){
7474
withLock { pg =>
75-
using(pg.prepareStatement("UPDATE offers.managed SET is_active = FALSE WHERE offer_id = ?")) { statement =>
76-
statement.setString(1, offer.offerId.toHex)
77-
statement.executeUpdate()
78-
}
79-
}
80-
}
81-
82-
override def enableOffer(offer: OfferTypes.Offer): Unit = withMetrics("offers/enable", DbBackends.Postgres){
83-
withLock { pg =>
84-
using(pg.prepareStatement("UPDATE offers.managed SET is_active = TRUE WHERE offer_id = ?")) { statement =>
85-
statement.setString(1, offer.offerId.toHex)
75+
using(pg.prepareStatement("UPDATE payments.offers SET disabled_at = ? WHERE offer_id = ?")) { statement =>
76+
statement.setTimestamp(1, TimestampMilli.now().toSqlTimestamp)
77+
statement.setString(2, offer.offerId.toHex)
8678
statement.executeUpdate()
8779
}
8880
}
@@ -93,18 +85,18 @@ class PgOffersDb(implicit ds: DataSource, lock: PgLock) extends OffersDb with Lo
9385
Offer.decode(rs.getString("offer")).get,
9486
rs.getStringNullable("path_id").map(ByteVector32.fromValidHex),
9587
TimestampMilli.fromSqlTimestamp(rs.getTimestamp("created_at")),
96-
rs.getBoolean("is_active")
88+
{ rs.getTimestamp("disabled_at"); rs.wasNull() }
9789
)
9890
}
9991

10092
override def listOffers(onlyActive: Boolean): Seq[OfferData] = withMetrics("offers/list", DbBackends.Postgres){
10193
withLock { pg =>
10294
if (onlyActive) {
103-
using(pg.prepareStatement("SELECT * FROM offers.managed WHERE is_active = TRUE")) { statement =>
95+
using(pg.prepareStatement("SELECT * FROM payments.offers WHERE disabled_at IS NULL")) { statement =>
10496
statement.executeQuery().map(parseOfferData).toSeq
10597
}
10698
} else {
107-
using(pg.prepareStatement("SELECT * FROM offers.managed")) { statement =>
99+
using(pg.prepareStatement("SELECT * FROM payments.offers")) { statement =>
108100
statement.executeQuery().map(parseOfferData).toSeq
109101
}
110102
}

eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgPaymentsDb.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit
5050
using(pg.createStatement()) { statement =>
5151

5252
def migration45(statement: Statement): Unit = {
53-
statement.executeUpdate("CREATE SCHEMA payments")
53+
statement.executeUpdate("CREATE SCHEMA IF NOT EXISTS payments")
5454
statement.executeUpdate("ALTER TABLE received_payments RENAME TO received")
5555
statement.executeUpdate("ALTER TABLE received SET SCHEMA payments")
5656
statement.executeUpdate("ALTER TABLE sent_payments RENAME TO sent")
@@ -79,7 +79,7 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit
7979

8080
getVersion(statement, DB_NAME) match {
8181
case None =>
82-
statement.executeUpdate("CREATE SCHEMA payments")
82+
statement.executeUpdate("CREATE SCHEMA IF NOT EXISTS payments")
8383

8484
statement.executeUpdate("CREATE TABLE payments.received (payment_hash TEXT NOT NULL PRIMARY KEY, payment_type TEXT NOT NULL, payment_preimage TEXT NOT NULL, path_ids BYTEA, payment_request TEXT NOT NULL, received_msat BIGINT, created_at TIMESTAMP WITH TIME ZONE NOT NULL, expire_at TIMESTAMP WITH TIME ZONE NOT NULL, received_at TIMESTAMP WITH TIME ZONE)")
8585
statement.executeUpdate("CREATE TABLE payments.sent (id TEXT NOT NULL PRIMARY KEY, parent_id TEXT NOT NULL, external_id TEXT, payment_hash TEXT NOT NULL, payment_preimage TEXT, payment_type TEXT NOT NULL, amount_msat BIGINT NOT NULL, fees_msat BIGINT, recipient_amount_msat BIGINT NOT NULL, recipient_node_id TEXT NOT NULL, payment_request TEXT, offer_id TEXT, payer_key TEXT, payment_route BYTEA, failures BYTEA, created_at TIMESTAMP WITH TIME ZONE NOT NULL, completed_at TIMESTAMP WITH TIME ZONE)")

0 commit comments

Comments
 (0)