Skip to content

Commit c8a54d0

Browse files
committed
recipient pays blinded fees
When using blinded payment paths, the fees of the blinded path should be paid by the recipient, not the sender. - It is more fair: the sender chooses the non blinded part of the path and pays the corresponding fees, the recipient chooses the blinded part of the path and pays the corresponding fees. - It is more private: the sender does not learn the actual fees for the path and can't use this information to unblind the path.
1 parent a8787ab commit c8a54d0

40 files changed

+684
-426
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
@@ -357,7 +357,7 @@ class Setup(val datadir: File,
357357
}
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))
360-
offerManager = system.spawn(Behaviors.supervise(OfferManager(nodeParams, router, paymentTimeout = 1 minute)).onFailure(typed.SupervisorStrategy.resume), name = "offer-manager")
360+
offerManager = system.spawn(Behaviors.supervise(OfferManager(nodeParams, paymentTimeout = 1 minute)).onFailure(typed.SupervisorStrategy.resume), name = "offer-manager")
361361
paymentHandler = system.actorOf(SimpleSupervisor.props(PaymentHandler.props(nodeParams, register, offerManager), "payment-handler", SupervisorStrategy.Resume))
362362
triggerer = system.spawn(Behaviors.supervise(AsyncPaymentTriggerer()).onFailure(typed.SupervisorStrategy.resume), name = "async-payment-triggerer")
363363
peerReadyManager = system.spawn(Behaviors.supervise(PeerReadyManager()).onFailure(typed.SupervisorStrategy.restart), name = "peer-ready-manager")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class DbEventHandler(nodeParams: NodeParams) extends Actor with DiagnosticActorL
6868
PaymentMetrics.PaymentFailed.withTag(PaymentTags.Direction, PaymentTags.Directions.Sent).increment()
6969

7070
case e: PaymentReceived =>
71-
PaymentMetrics.PaymentAmount.withTag(PaymentTags.Direction, PaymentTags.Directions.Received).record(e.amount.truncateToSatoshi.toLong)
71+
PaymentMetrics.PaymentAmount.withTag(PaymentTags.Direction, PaymentTags.Directions.Received).record(e.realAmount.truncateToSatoshi.toLong)
7272
PaymentMetrics.PaymentParts.withTag(PaymentTags.Direction, PaymentTags.Directions.Received).record(e.parts.length)
7373
auditDb.add(e)
7474
e.parts.foreach(p => channelsDb.updateChannelMeta(p.fromChannelId, ChannelEvent.EventType.PaymentReceived))

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
@@ -319,14 +319,14 @@ case class DualPaymentsDb(primary: PaymentsDb, secondary: PaymentsDb) extends Pa
319319
primary.addIncomingPayment(pr, preimage, paymentType)
320320
}
321321

322-
override def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli): Boolean = {
323-
runAsync(secondary.receiveIncomingPayment(paymentHash, amount, receivedAt))
324-
primary.receiveIncomingPayment(paymentHash, amount, receivedAt)
322+
override def receiveIncomingPayment(paymentHash: ByteVector32, virtualAmount: MilliSatoshi, realAmount: MilliSatoshi, receivedAt: TimestampMilli): Boolean = {
323+
runAsync(secondary.receiveIncomingPayment(paymentHash, virtualAmount, realAmount, receivedAt))
324+
primary.receiveIncomingPayment(paymentHash, virtualAmount, realAmount, receivedAt)
325325
}
326326

327-
override def receiveIncomingOfferPayment(pr: MinimalBolt12Invoice, preimage: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli, paymentType: String): Unit = {
328-
runAsync(secondary.receiveIncomingOfferPayment(pr, preimage, amount, receivedAt, paymentType))
329-
primary.receiveIncomingOfferPayment(pr, preimage, amount, receivedAt, paymentType)
327+
override def receiveIncomingOfferPayment(pr: MinimalBolt12Invoice, preimage: ByteVector32, virtualAmount: MilliSatoshi, realAmount: MilliSatoshi, receivedAt: TimestampMilli, paymentType: String): Unit = {
328+
runAsync(secondary.receiveIncomingOfferPayment(pr, preimage, virtualAmount, realAmount, receivedAt, paymentType))
329+
primary.receiveIncomingOfferPayment(pr, preimage, virtualAmount, realAmount, receivedAt, paymentType)
330330
}
331331

332332
override def getIncomingPayment(paymentHash: ByteVector32): Option[IncomingPayment] = {

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ trait IncomingPaymentsDb {
3636
* Mark an incoming payment as received (paid). The received amount may exceed the invoice amount.
3737
* If there was no matching invoice in the DB, this will return false.
3838
*/
39-
def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli = TimestampMilli.now()): Boolean
39+
def receiveIncomingPayment(paymentHash: ByteVector32, virtualAmount: MilliSatoshi, realAmount: MilliSatoshi, receivedAt: TimestampMilli = TimestampMilli.now()): Boolean
4040

4141
/**
4242
* Add a new incoming offer payment as received.
4343
* If the invoice is already paid, adds `amount` to the amount paid.
4444
*/
45-
def receiveIncomingOfferPayment(pr: MinimalBolt12Invoice, preimage: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli = TimestampMilli.now(), paymentType: String = PaymentType.Blinded): Unit
45+
def receiveIncomingOfferPayment(pr: MinimalBolt12Invoice, preimage: ByteVector32, virtualAmount: MilliSatoshi, realAmount: MilliSatoshi, receivedAt: TimestampMilli = TimestampMilli.now(), paymentType: String = PaymentType.Blinded): Unit
4646

4747
/** Get information about the incoming payment (paid or not) for the given payment hash, if any. */
4848
def getIncomingPayment(paymentHash: ByteVector32): Option[IncomingPayment]
@@ -150,10 +150,11 @@ object IncomingPaymentStatus {
150150
/**
151151
* Payment has been successfully received.
152152
*
153-
* @param amount amount of the payment received, in milli-satoshis (may exceed the invoice amount).
154-
* @param receivedAt absolute time in milli-seconds since UNIX epoch when the payment was received.
153+
* @param virtualAmount amount of the payment received, in milli-satoshis (may exceed the invoice amount).
154+
* @param realAmount amount of the payment received, in milli-satoshis (may be less or more than the invoice amount).
155+
* @param receivedAt absolute time in milli-seconds since UNIX epoch when the payment was received.
155156
*/
156-
case class Received(amount: MilliSatoshi, receivedAt: TimestampMilli) extends IncomingPaymentStatus
157+
case class Received(virtualAmount: MilliSatoshi, realAmount: MilliSatoshi, receivedAt: TimestampMilli) extends IncomingPaymentStatus
157158

158159
}
159160

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import javax.sql.DataSource
3636

3737
object PgAuditDb {
3838
val DB_NAME = "audit"
39-
val CURRENT_VERSION = 12
39+
val CURRENT_VERSION = 13
4040
}
4141

4242
class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
@@ -114,12 +114,20 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
114114
statement.executeUpdate("CREATE INDEX transactions_published_channel_id_idx ON audit.transactions_published(channel_id)")
115115
}
116116

117+
def migration1213(statement: Statement): Unit = {
118+
statement.executeUpdate("ALTER TABLE audit.received RENAME TO received_old")
119+
statement.executeUpdate("CREATE TABLE audit.received (virtual_amount_msat BIGINT NOT NULL, real_amount_msat BIGINT NOT NULL, payment_hash TEXT NOT NULL, from_channel_id TEXT NOT NULL, timestamp TIMESTAMP WITH TIME ZONE NOT NULL)")
120+
statement.executeUpdate("INSERT INTO audit.received SELECT amount_msat, amount_msat, payment_hash, from_channel_id, timestamp FROM audit.received_old")
121+
statement.executeUpdate("DROP TABLE audit.received_old")
122+
statement.executeUpdate("CREATE INDEX received_timestamp_idx ON audit.received(timestamp)")
123+
}
124+
117125
getVersion(statement, DB_NAME) match {
118126
case None =>
119127
statement.executeUpdate("CREATE SCHEMA audit")
120128

121129
statement.executeUpdate("CREATE TABLE audit.sent (amount_msat BIGINT NOT NULL, fees_msat BIGINT NOT NULL, recipient_amount_msat BIGINT NOT NULL, payment_id TEXT NOT NULL, parent_payment_id TEXT NOT NULL, payment_hash TEXT NOT NULL, payment_preimage TEXT NOT NULL, recipient_node_id TEXT NOT NULL, to_channel_id TEXT NOT NULL, timestamp TIMESTAMP WITH TIME ZONE NOT NULL)")
122-
statement.executeUpdate("CREATE TABLE audit.received (amount_msat BIGINT NOT NULL, payment_hash TEXT NOT NULL, from_channel_id TEXT NOT NULL, timestamp TIMESTAMP WITH TIME ZONE NOT NULL)")
130+
statement.executeUpdate("CREATE TABLE audit.received (virtual_amount_msat BIGINT NOT NULL, real_amount_msat BIGINT NOT NULL, payment_hash TEXT NOT NULL, from_channel_id TEXT NOT NULL, timestamp TIMESTAMP WITH TIME ZONE NOT NULL)")
123131
statement.executeUpdate("CREATE TABLE audit.relayed (payment_hash TEXT NOT NULL, amount_msat BIGINT NOT NULL, channel_id TEXT NOT NULL, direction TEXT NOT NULL, relay_type TEXT NOT NULL, timestamp TIMESTAMP WITH TIME ZONE NOT NULL)")
124132
statement.executeUpdate("CREATE TABLE audit.relayed_trampoline (payment_hash TEXT NOT NULL, amount_msat BIGINT NOT NULL, next_node_id TEXT NOT NULL, timestamp TIMESTAMP WITH TIME ZONE NOT NULL)")
125133
statement.executeUpdate("CREATE TABLE audit.channel_events (channel_id TEXT NOT NULL, node_id TEXT NOT NULL, capacity_sat BIGINT NOT NULL, is_funder BOOLEAN NOT NULL, is_private BOOLEAN NOT NULL, event TEXT NOT NULL, timestamp TIMESTAMP WITH TIME ZONE NOT NULL)")
@@ -149,7 +157,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
149157
statement.executeUpdate("CREATE INDEX transactions_published_channel_id_idx ON audit.transactions_published(channel_id)")
150158
statement.executeUpdate("CREATE INDEX transactions_published_timestamp_idx ON audit.transactions_published(timestamp)")
151159
statement.executeUpdate("CREATE INDEX transactions_confirmed_timestamp_idx ON audit.transactions_confirmed(timestamp)")
152-
case Some(v@(4 | 5 | 6 | 7 | 8 | 9 | 10 | 11)) =>
160+
case Some(v@(4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12)) =>
153161
logger.warn(s"migrating db $DB_NAME, found version=$v current=$CURRENT_VERSION")
154162
if (v < 5) {
155163
migration45(statement)
@@ -175,6 +183,9 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
175183
if (v < 12) {
176184
migration1112(statement)
177185
}
186+
if (v < 13) {
187+
migration1213(statement)
188+
}
178189
case Some(CURRENT_VERSION) => () // table is up-to-date, nothing to do
179190
case Some(unknownVersion) => throw new RuntimeException(s"Unknown version of DB $DB_NAME found, version=$unknownVersion")
180191
}
@@ -220,12 +231,13 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
220231

221232
override def add(e: PaymentReceived): Unit = withMetrics("audit/add-payment-received", DbBackends.Postgres) {
222233
inTransaction { pg =>
223-
using(pg.prepareStatement("INSERT INTO audit.received VALUES (?, ?, ?, ?)")) { statement =>
234+
using(pg.prepareStatement("INSERT INTO audit.received VALUES (?, ?, ?, ?, ?)")) { statement =>
224235
e.parts.foreach(p => {
225-
statement.setLong(1, p.amount.toLong)
226-
statement.setString(2, e.paymentHash.toHex)
227-
statement.setString(3, p.fromChannelId.toHex)
228-
statement.setTimestamp(4, p.timestamp.toSqlTimestamp)
236+
statement.setLong(1, p.virtualAmount.toLong)
237+
statement.setLong(2, p.realAmount.toLong)
238+
statement.setString(3, e.paymentHash.toHex)
239+
statement.setString(4, p.fromChannelId.toHex)
240+
statement.setTimestamp(5, p.timestamp.toSqlTimestamp)
229241
statement.addBatch()
230242
})
231243
statement.executeBatch()
@@ -404,7 +416,8 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging {
404416
.foldLeft(Map.empty[ByteVector32, PaymentReceived]) { (receivedByHash, rs) =>
405417
val paymentHash = rs.getByteVector32FromHex("payment_hash")
406418
val part = PaymentReceived.PartialPayment(
407-
MilliSatoshi(rs.getLong("amount_msat")),
419+
MilliSatoshi(rs.getLong("virtual_amount_msat")),
420+
MilliSatoshi(rs.getLong("real_amount_msat")),
408421
rs.getByteVector32FromHex("from_channel_id"),
409422
TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp")))
410423
val received = receivedByHash.get(paymentHash) match {

0 commit comments

Comments
 (0)