Skip to content

Commit 7491779

Browse files
committed
more comments
1 parent 242fa8c commit 7491779

File tree

9 files changed

+51
-47
lines changed

9 files changed

+51
-47
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ class Setup(val datadir: File,
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")
361361
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)
362+
_ = for (offer <- nodeParams.db.offers.listOffers(onlyActive = true)) offerManager ! OfferManager.RegisterOffer(offer.offer, if (offer.pathId_opt.isEmpty) Some(nodeParams.privateKey) else 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/DualDatabases.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,14 @@ case class DualOffersDb(primary: OffersDb, secondary: OffersDb) extends OffersDb
410410

411411
private implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("db-offers").build()))
412412

413-
override def addOffer(offer: OfferTypes.Offer, pathId_opt: Option[ByteVector32]): Unit = {
414-
runAsync(secondary.addOffer(offer, pathId_opt))
415-
primary.addOffer(offer, pathId_opt)
413+
override def addOffer(offer: OfferTypes.Offer, pathId_opt: Option[ByteVector32], createdAt: TimestampMilli = TimestampMilli.now()): Unit = {
414+
runAsync(secondary.addOffer(offer, pathId_opt, createdAt))
415+
primary.addOffer(offer, pathId_opt, createdAt)
416416
}
417417

418-
override def disableOffer(offer: OfferTypes.Offer): Unit = {
419-
runAsync(secondary.disableOffer(offer))
420-
primary.disableOffer(offer)
418+
override def disableOffer(offer: OfferTypes.Offer, disabledAt: TimestampMilli = TimestampMilli.now()): Unit = {
419+
runAsync(secondary.disableOffer(offer, disabledAt))
420+
primary.disableOffer(offer, disabledAt)
421421
}
422422

423423
override def listOffers(onlyActive: Boolean): Seq[OfferData] = {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ trait OffersDb {
2828
* Add an offer managed by eclair.
2929
* @param pathId_opt If the offer uses a blinded path, this is the corresponding pathId.
3030
*/
31-
def addOffer(offer: Offer, pathId_opt: Option[ByteVector32]): Unit
31+
def addOffer(offer: Offer, pathId_opt: Option[ByteVector32], createdAt: TimestampMilli = TimestampMilli.now()): Unit
3232

3333
/**
3434
* Disable an offer. The offer is still stored but new invoice requests and new payment attempts for already emitted
3535
* invoices will be rejected.
3636
*/
37-
def disableOffer(offer: Offer): Unit
37+
def disableOffer(offer: Offer, disabledAt: TimestampMilli = TimestampMilli.now()): Unit
3838

3939
/**
4040
* List offers managed by eclair.
@@ -43,4 +43,4 @@ trait OffersDb {
4343
def listOffers(onlyActive: Boolean): Seq[OfferData]
4444
}
4545

46-
case class OfferData(offer: Offer, pathId_opt: Option[ByteVector32], createdAt: TimestampMilli, isActive: Boolean)
46+
case class OfferData(offer: Offer, pathId_opt: Option[ByteVector32], createdAt: TimestampMilli, disabledAt: Option[TimestampMilli])

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,57 +46,57 @@ class PgOffersDb(implicit ds: DataSource, lock: PgLock) extends OffersDb with Lo
4646
getVersion(statement, DB_NAME) match {
4747
case None =>
4848
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)")
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, is_active BOOLEAN NOT NULL, disabled_at TIMESTAMP WITH TIME ZONE)")
50+
statement.executeUpdate("CREATE INDEX offer_created_at_idx ON payments.offers(created_at)")
51+
statement.executeUpdate("CREATE INDEX offer_is_active_idx ON payments.offers(is_active)")
5152
case Some(CURRENT_VERSION) => () // table is up-to-date, nothing to do
5253
case Some(unknownVersion) => throw new RuntimeException(s"Unknown version of DB $DB_NAME found, version=$unknownVersion")
5354
}
5455
setVersion(statement, DB_NAME, CURRENT_VERSION)
5556
}
5657
}
5758

58-
override def addOffer(offer: OfferTypes.Offer, pathId_opt: Option[ByteVector32]): Unit = withMetrics("offers/add", DbBackends.Postgres){
59+
override def addOffer(offer: OfferTypes.Offer, pathId_opt: Option[ByteVector32], createdAt: TimestampMilli = TimestampMilli.now()): Unit = withMetrics("offers/add", DbBackends.Postgres){
5960
withLock { pg =>
60-
using(pg.prepareStatement("INSERT INTO payments.offers (offer_id, offer, path_id, created_at, disabled_at) VALUES (?, ?, ?, ?, NULL)")) { statement =>
61+
using(pg.prepareStatement("INSERT INTO payments.offers (offer_id, offer, path_id, created_at, is_active, disabled_at) VALUES (?, ?, ?, ?, TRUE, NULL)")) { statement =>
6162
statement.setString(1, offer.offerId.toHex)
6263
statement.setString(2, offer.toString)
63-
pathId_opt match {
64-
case Some(pathId) => statement.setString(3, pathId.toHex)
65-
case None => statement.setNull(3, java.sql.Types.VARCHAR)
66-
}
67-
statement.setTimestamp(4, TimestampMilli.now().toSqlTimestamp)
64+
statement.setString(3, pathId_opt.map(_.toHex).orNull)
65+
statement.setTimestamp(4, createdAt.toSqlTimestamp)
6866
statement.executeUpdate()
6967
}
7068
}
7169
}
7270

73-
override def disableOffer(offer: OfferTypes.Offer): Unit = withMetrics("offers/disable", DbBackends.Postgres){
71+
override def disableOffer(offer: OfferTypes.Offer, disabledAt: TimestampMilli = TimestampMilli.now()): Unit = withMetrics("offers/disable", DbBackends.Postgres){
7472
withLock { pg =>
75-
using(pg.prepareStatement("UPDATE payments.offers SET disabled_at = ? WHERE offer_id = ?")) { statement =>
76-
statement.setTimestamp(1, TimestampMilli.now().toSqlTimestamp)
73+
using(pg.prepareStatement("UPDATE payments.offers SET disabled_at = ?, is_active = FALSE WHERE offer_id = ?")) { statement =>
74+
statement.setTimestamp(1, disabledAt.toSqlTimestamp)
7775
statement.setString(2, offer.offerId.toHex)
7876
statement.executeUpdate()
7977
}
8078
}
8179
}
8280

8381
private def parseOfferData(rs: ResultSet): OfferData = {
82+
val disabledAt = rs.getTimestamp("disabled_at")
83+
val disabledAt_opt = if (rs.wasNull()) None else Some(TimestampMilli.fromSqlTimestamp(disabledAt))
8484
OfferData(
8585
Offer.decode(rs.getString("offer")).get,
8686
rs.getStringNullable("path_id").map(ByteVector32.fromValidHex),
8787
TimestampMilli.fromSqlTimestamp(rs.getTimestamp("created_at")),
88-
{ rs.getTimestamp("disabled_at"); rs.wasNull() }
88+
disabledAt_opt
8989
)
9090
}
9191

9292
override def listOffers(onlyActive: Boolean): Seq[OfferData] = withMetrics("offers/list", DbBackends.Postgres){
9393
withLock { pg =>
9494
if (onlyActive) {
95-
using(pg.prepareStatement("SELECT * FROM payments.offers WHERE disabled_at IS NULL")) { statement =>
95+
using(pg.prepareStatement("SELECT * FROM payments.offers WHERE is_active = TRUE ORDER BY created_at DESC")) { statement =>
9696
statement.executeQuery().map(parseOfferData).toSeq
9797
}
9898
} else {
99-
using(pg.prepareStatement("SELECT * FROM payments.offers")) { statement =>
99+
using(pg.prepareStatement("SELECT * FROM payments.offers ORDER BY created_at DESC")) { statement =>
100100
statement.executeQuery().map(parseOfferData).toSeq
101101
}
102102
}

eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteOffersDb.scala

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,52 +40,52 @@ class SqliteOffersDb(val sqlite: Connection) extends OffersDb with Logging {
4040
using(sqlite.createStatement(), inTransaction = true) { statement =>
4141
getVersion(statement, DB_NAME) match {
4242
case None =>
43-
statement.executeUpdate("CREATE TABLE offers (offer_id BLOB NOT NULL PRIMARY KEY, offer TEXT NOT NULL, path_id BLOB, created_at INTEGER NOT NULL, disabled_at INTEGER)")
44-
statement.executeUpdate("CREATE INDEX offer_disabled_at_idx ON offers(disabled_at)")
43+
statement.executeUpdate("CREATE TABLE offers (offer_id BLOB NOT NULL PRIMARY KEY, offer TEXT NOT NULL, path_id BLOB, created_at INTEGER NOT NULL, is_active INTEGER NOT NULL, disabled_at INTEGER)")
44+
statement.executeUpdate("CREATE INDEX offer_created_at_idx ON offers(created_at)")
45+
statement.executeUpdate("CREATE INDEX offer_is_active_idx ON offers(is_active)")
4546
case Some(CURRENT_VERSION) => () // table is up-to-date, nothing to do
4647
case Some(unknownVersion) => throw new RuntimeException(s"Unknown version of DB $DB_NAME found, version=$unknownVersion")
4748
}
4849
setVersion(statement, DB_NAME, CURRENT_VERSION)
4950

5051
}
5152

52-
override def addOffer(offer: OfferTypes.Offer, pathId_opt: Option[ByteVector32]): Unit = withMetrics("offers/add", DbBackends.Sqlite) {
53-
using(sqlite.prepareStatement("INSERT INTO offers (offer_id, offer, path_id, created_at, disabled_at) VALUES (?, ?, ?, ?, NULL)")) { statement =>
53+
override def addOffer(offer: OfferTypes.Offer, pathId_opt: Option[ByteVector32], createdAt: TimestampMilli = TimestampMilli.now()): Unit = withMetrics("offers/add", DbBackends.Sqlite) {
54+
using(sqlite.prepareStatement("INSERT INTO offers (offer_id, offer, path_id, created_at, is_active, disabled_at) VALUES (?, ?, ?, ?, TRUE, NULL)")) { statement =>
5455
statement.setBytes(1, offer.offerId.toArray)
5556
statement.setString(2, offer.toString)
56-
pathId_opt match {
57-
case Some(pathId) => statement.setBytes(3, pathId.toArray)
58-
case None => statement.setNull(3, java.sql.Types.VARBINARY)
59-
}
60-
statement.setLong(4, TimestampMilli.now().toLong)
57+
statement.setBytes(3, pathId_opt.map(_.toArray).orNull)
58+
statement.setLong(4, createdAt.toLong)
6159
statement.executeUpdate()
6260
}
6361
}
6462

65-
override def disableOffer(offer: OfferTypes.Offer): Unit = withMetrics("offers/disable", DbBackends.Sqlite) {
66-
using(sqlite.prepareStatement("UPDATE offers SET disabled_at = ? WHERE offer_id = ?")) { statement =>
67-
statement.setLong(1, TimestampMilli.now().toLong)
63+
override def disableOffer(offer: OfferTypes.Offer, disabledAt: TimestampMilli = TimestampMilli.now()): Unit = withMetrics("offers/disable", DbBackends.Sqlite) {
64+
using(sqlite.prepareStatement("UPDATE offers SET disabled_at = ?, is_active = FALSE WHERE offer_id = ?")) { statement =>
65+
statement.setLong(1, disabledAt.toLong)
6866
statement.setBytes(2, offer.offerId.toArray)
6967
statement.executeUpdate()
7068
}
7169
}
7270

7371
private def parseOfferData(rs: ResultSet): OfferData = {
72+
val disabledAt = rs.getLong("disabled_at")
73+
val disabledAt_opt = if (rs.wasNull()) None else Some(TimestampMilli(disabledAt))
7474
OfferData(
7575
Offer.decode(rs.getString("offer")).get,
7676
rs.getByteVector32Nullable("path_id"),
7777
TimestampMilli(rs.getLong("created_at")),
78-
{ rs.getLong("disabled_at"); rs.wasNull() }
78+
disabledAt_opt
7979
)
8080
}
8181

8282
override def listOffers(onlyActive: Boolean): Seq[OfferData] = withMetrics("offers/list", DbBackends.Sqlite) {
8383
if (onlyActive) {
84-
using(sqlite.prepareStatement("SELECT * FROM offers WHERE disabled_at IS NULL")) { statement =>
84+
using(sqlite.prepareStatement("SELECT * FROM offers WHERE is_active = TRUE ORDER BY created_at DESC")) { statement =>
8585
statement.executeQuery().map(parseOfferData).toSeq
8686
}
8787
} else {
88-
using(sqlite.prepareStatement("SELECT * FROM offers")) { statement =>
88+
using(sqlite.prepareStatement("SELECT * FROM offers ORDER BY created_at DESC")) { statement =>
8989
statement.executeQuery().map(parseOfferData).toSeq
9090
}
9191
}

eclair-core/src/main/scala/fr/acinq/eclair/payment/offer/DefaultOfferHandler.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ object DefaultOfferHandler {
4444
case OfferTypes.BlindedPath(BlindedRoute(firstNodeId: EncodedNodeId.WithPublicKey, _, _)) =>
4545
val baseParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams
4646
val routeParams = baseParams.copy(boundaries = baseParams.boundaries.copy(maxRouteLength = nodeParams.offersConfig.paymentPathLength, maxCltv = nodeParams.offersConfig.paymentPathCltvExpiryDelta))
47-
router ! BlindedRouteRequest(context.spawnAnonymous(waitForRoute(nodeParams, replyTo, invoiceRequest.offer, amount)), firstNodeId.publicKey, nodeParams.nodeId, amount, routeParams, pathsToFind = 2)
47+
router ! BlindedRouteRequest(context.spawnAnonymous(waitForRoute(nodeParams, replyTo, invoiceRequest.offer, amount)), firstNodeId.publicKey, nodeParams.nodeId, amount, routeParams, nodeParams.offersConfig.paymentPathCount)
4848
case OfferTypes.BlindedPath(BlindedRoute(_: EncodedNodeId.ShortChannelIdDir, _, _)) =>
4949
context.log.error("unexpected managed offer with compact first node id")
5050
replyTo ! InvoiceRequestActor.RejectRequest("internal error")
@@ -63,13 +63,14 @@ object DefaultOfferHandler {
6363
replyTo ! InvoiceRequestActor.ApproveRequest(amount, makeRoutes(nodeParams, routes.map(_.hops)))
6464
Behaviors.stopped
6565
case (context, Router.PaymentRouteNotFound(error)) =>
66-
context.log.error("Couldn't find blinded route for creating invoice offer={} amount={} : {}", offer, amount, error.getMessage)
66+
context.log.error("couldn't find blinded route for creating invoice offer={} amount={} : {}", offer, amount, error.getMessage)
6767
replyTo ! InvoiceRequestActor.RejectRequest("internal error")
6868
Behaviors.stopped
6969
}
7070
}
7171

72-
def makeRoutes(nodeParams: NodeParams, routes: Seq[Seq[Router.ChannelHop]]): Seq[InvoiceRequestActor.Route] = {
72+
// Ensure that we don't leak routing information by always returning the same number of routes, with the same number of hops and the same fees and CLTV delta.
73+
private def makeRoutes(nodeParams: NodeParams, routes: Seq[Seq[Router.ChannelHop]]): Seq[InvoiceRequestActor.Route] = {
7374
(0 until nodeParams.offersConfig.paymentPathCount).map(i => {
7475
val hops = routes(i % routes.length)
7576
val dummyHops = Seq.fill(nodeParams.offersConfig.paymentPathLength - hops.length)(ChannelHop.dummy(nodeParams.nodeId, 0 msat, 0, CltvExpiryDelta(0)))

eclair-core/src/main/scala/fr/acinq/eclair/payment/offer/OfferManager.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,10 @@ object OfferManager {
168168
customTlvs: Set[GenericTlv] = Set.empty) extends Command
169169

170170
/**
171-
* @param recipientPaysFees If true, fees for the blinded route will be hidden to the payer and paid by the recipient.
171+
* @param feeOverride fees that will be published for this route, the difference between these and the
172+
* actual fees of the route will be paid by the recipient.
173+
* @param cltvOverride Cltv expiry delta to publish for the route.
174+
* @param shortChannelIdDir_opt short channel id and direction to use for the first node instead of its node id.
172175
*/
173176
case class Route(hops: Seq[Router.ChannelHop], maxFinalExpiryDelta: CltvExpiryDelta, feeOverride: Option[RelayFees] = None, cltvOverride: Option[CltvExpiryDelta] = None, shortChannelIdDir_opt: Option[ShortChannelIdDir] = None) {
174177
def finalize(nodePriv: PrivateKey, preimage: ByteVector32, amount: MilliSatoshi, invoiceRequest: InvoiceRequest, minFinalExpiryDelta: CltvExpiryDelta, pluginData_opt: Option[ByteVector]): ReceivingRoute = {

eclair-core/src/test/scala/fr/acinq/eclair/db/OffersDbSpec.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class OffersDbSpec extends AnyFunSuite {
5050
assert(listed1.length == 1)
5151
assert(listed1.head.offer == offer1)
5252
assert(listed1.head.pathId_opt == None)
53-
assert(listed1.head.isActive)
53+
assert(listed1.head.disabledAt == None)
5454
val offer2 = Offer(None, Some("test 2"), randomKey().publicKey, Features(), Block.LivenetGenesisBlock.hash)
5555
val pathId = randomBytes32()
5656
db.addOffer(offer2, Some(pathId))
@@ -61,7 +61,7 @@ class OffersDbSpec extends AnyFunSuite {
6161
assert(listed2.length == 1)
6262
assert(listed2.head.offer == offer2)
6363
assert(listed2.head.pathId_opt == Some(pathId))
64-
assert(listed2.head.isActive)
64+
assert(listed2.head.disabledAt == None)
6565
db.disableOffer(offer2)
6666
assert(db.listOffers(onlyActive = true).isEmpty)
6767
}

eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Offer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ trait Offer {
4141

4242
val listoffers: Route = postRequest("listoffers") { implicit t =>
4343
formFields("activeOnly".as[Boolean].?) { onlyActive =>
44-
complete(eclairApi.listOffers(onlyActive.getOrElse(false)))
44+
complete(eclairApi.listOffers(onlyActive.getOrElse(true)))
4545
}
4646
}
4747

0 commit comments

Comments
 (0)