From a51f705ff98a55e55450c5f5ee1f072869199aac Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 25 Jun 2019 12:30:38 +0200 Subject: [PATCH 01/11] Add optional invoice and description to SendPayment and OutgoingPayment --- .../main/scala/fr/acinq/eclair/Eclair.scala | 8 ++--- .../scala/fr/acinq/eclair/api/Service.scala | 6 ++-- .../scala/fr/acinq/eclair/db/PaymentsDb.scala | 34 ++++++++++++++----- .../eclair/db/sqlite/SqlitePaymentsDb.scala | 15 ++++++-- .../fr/acinq/eclair/payment/Autoprobe.scala | 3 +- .../eclair/payment/PaymentLifecycle.scala | 10 +++--- .../eclair/db/SqlitePaymentsDbSpec.scala | 10 ++++-- 7 files changed, 58 insertions(+), 28 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala index 7c425328c4..6450b1953e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -77,7 +77,7 @@ trait Eclair { def receivedInfo(paymentHash: ByteVector32)(implicit timeout: Timeout): Future[Option[IncomingPayment]] - def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long] = None, maxAttempts_opt: Option[Int] = None, feeThresholdSat_opt: Option[Long] = None, maxFeePct_opt: Option[Double] = None)(implicit timeout: Timeout): Future[UUID] + def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long] = None, maxAttempts_opt: Option[Int] = None, feeThresholdSat_opt: Option[Long] = None, maxFeePct_opt: Option[Double] = None, paymentRequest_opt: Option[PaymentRequest] = None, customDescription_opt: Option[String] = None)(implicit timeout: Timeout): Future[UUID] def sentInfo(id: Either[UUID, ByteVector32])(implicit timeout: Timeout): Future[Seq[OutgoingPayment]] @@ -188,7 +188,7 @@ class EclairImpl(appKit: Kit) extends Eclair { (appKit.paymentInitiator ? SendPaymentToRoute(amountMsat, paymentHash, route, finalCltvExpiry)).mapTo[UUID] } - override def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long], maxAttempts_opt: Option[Int], feeThresholdSat_opt: Option[Long], maxFeePct_opt: Option[Double])(implicit timeout: Timeout): Future[UUID] = { + override def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]], minFinalCltvExpiry_opt: Option[Long], maxAttempts_opt: Option[Int], feeThresholdSat_opt: Option[Long], maxFeePct_opt: Option[Double], paymentRequest_opt: Option[PaymentRequest], customDescription_opt: Option[String])(implicit timeout: Timeout): Future[UUID] = { val maxAttempts = maxAttempts_opt.getOrElse(appKit.nodeParams.maxPaymentAttempts) val defaultRouteParams = Router.getDefaultRouteParams(appKit.nodeParams.routerConf) @@ -198,8 +198,8 @@ class EclairImpl(appKit: Kit) extends Eclair { ) val sendPayment = minFinalCltvExpiry_opt match { - case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts, routeParams = Some(routeParams)) - case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts, routeParams = Some(routeParams)) + case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt, customDescription_opt) + case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt = paymentRequest_opt, description_opt = customDescription_opt) } (appKit.paymentInitiator ? sendPayment).mapTo[UUID] } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala index f816aa8be3..0394816cdb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala @@ -219,15 +219,15 @@ trait Service extends ExtraDirectives with Logging { path("payinvoice") { formFields(invoiceFormParam, amountMsatFormParam.?, "maxAttempts".as[Int].?, "feeThresholdSat".as[Long].?, "maxFeePct".as[Double].?) { case (invoice@PaymentRequest(_, Some(amount), _, nodeId, _, _), None, maxAttempts, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(nodeId, amount.toLong, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt)) + complete(eclairApi.send(nodeId, amount.toLong, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, Some(invoice), None)) case (invoice, Some(overrideAmount), maxAttempts, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt)) + complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, Some(invoice), None)) case _ => reject(MalformedFormFieldRejection("invoice", "The invoice must have an amount or you need to specify one using the field 'amountMsat'")) } } ~ path("sendtonode") { formFields(amountMsatFormParam, paymentHashFormParam, nodeIdFormParam, "maxAttempts".as[Int].?, "feeThresholdSat".as[Long].?, "maxFeePct".as[Double].?) { (amountMsat, paymentHash, nodeId, maxAttempts_opt, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(nodeId, amountMsat, paymentHash, maxAttempts_opt = maxAttempts_opt, feeThresholdSat_opt = feeThresholdSat_opt, maxFeePct_opt = maxFeePct_opt)) + complete(eclairApi.send(nodeId, amountMsat, paymentHash, maxAttempts_opt = maxAttempts_opt, feeThresholdSat_opt = feeThresholdSat_opt, maxFeePct_opt = maxFeePct_opt, paymentRequest_opt = None, customDescription_opt = None)) } } ~ path("sendtoroute") { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index 38b1f60e8d..bc2ebbc392 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -17,7 +17,9 @@ package fr.acinq.eclair.db import java.util.UUID + import fr.acinq.bitcoin.ByteVector32 +import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.eclair.payment.PaymentRequest trait PaymentsDb { @@ -65,18 +67,32 @@ trait PaymentsDb { case class IncomingPayment(paymentHash: ByteVector32, amountMsat: Long, receivedAt: Long) /** - * Sent payment is every payment that is sent by this node, they may not be finalized and + * OutgoingPayment is every payment that is sent by this node, they may not be finalized and * when is final it can be failed or successful. * - * @param id internal payment identifier - * @param paymentHash payment_hash - * @param preimage the preimage of the payment_hash, known if the outgoing payment was successful - * @param amountMsat amount of the payment, in milli-satoshis - * @param createdAt absolute time in seconds since UNIX epoch when the payment was created. - * @param completedAt absolute time in seconds since UNIX epoch when the payment succeeded. - * @param status current status of the payment. + * @param id internal payment identifier + * @param paymentHash payment_hash + * @param preimage the preimage of the payment_hash, known if the outgoing payment was successful + * @param amountMsat amount of the payment, in milli-satoshis + * @param createdAt absolute time in seconds since UNIX epoch when the payment was created. + * @param completedAt absolute time in seconds since UNIX epoch when the payment succeeded. + * @param status current status of the payment. + * @param paymentRequest_opt the payment request that was associated with this payment + * @param description_opt a custom description + * @param targetNodeId the recipient of this payment */ -case class OutgoingPayment(id: UUID, paymentHash: ByteVector32, preimage:Option[ByteVector32], amountMsat: Long, createdAt: Long, completedAt: Option[Long], status: OutgoingPaymentStatus.Value) +case class OutgoingPayment( + id: UUID, + paymentHash: ByteVector32, + preimage:Option[ByteVector32], + amountMsat: Long, + createdAt: Long, + completedAt: Option[Long], + status: OutgoingPaymentStatus.Value, + paymentRequest_opt: Option[PaymentRequest] = None, + description_opt: Option[String] = None, + targetNodeId: PublicKey + ) object OutgoingPaymentStatus extends Enumeration { val PENDING = Value(1, "PENDING") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala index 8fd6ded566..4b34c2e9f3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala @@ -79,7 +79,10 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getLong("amount_msat"), rs.getLong("created_at"), getNullableLong(rs, "completed_at"), - OutgoingPaymentStatus.withName(rs.getString("status")) + OutgoingPaymentStatus.withName(rs.getString("status")), + None, + None, + ??? )) } else { None @@ -100,7 +103,10 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getLong("amount_msat"), rs.getLong("created_at"), getNullableLong(rs, "completed_at"), - OutgoingPaymentStatus.withName(rs.getString("status")) + OutgoingPaymentStatus.withName(rs.getString("status")), + None, + None, + ??? ) } q @@ -119,7 +125,10 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getLong("amount_msat"), rs.getLong("created_at"), getNullableLong(rs, "completed_at"), - OutgoingPaymentStatus.withName(rs.getString("status")) + OutgoingPaymentStatus.withName(rs.getString("status")), + None, + None, + ??? ) } q diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala index 918d02f065..56474509d5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala @@ -23,7 +23,6 @@ import fr.acinq.eclair.payment.PaymentLifecycle.{PaymentFailed, PaymentResult, R import fr.acinq.eclair.router.{Announcements, Data} import fr.acinq.eclair.wire.{IncorrectOrUnknownPaymentDetails} import fr.acinq.eclair.{NodeParams, randomBytes32, secureRandom} - import scala.concurrent.duration._ /** @@ -54,7 +53,7 @@ class Autoprobe(nodeParams: NodeParams, router: ActorRef, paymentInitiator: Acto case Some(targetNodeId) => val paymentHash = randomBytes32 // we don't even know the preimage (this needs to be a secure random!) log.info(s"sending payment probe to node=$targetNodeId payment_hash=$paymentHash") - paymentInitiator ! SendPayment(PAYMENT_AMOUNT_MSAT, paymentHash, targetNodeId, maxAttempts = 1) + paymentInitiator ! SendPayment(PAYMENT_AMOUNT_MSAT, paymentHash, targetNodeId, maxAttempts = 1, paymentRequest_opt = None, description_opt = None) case None => log.info(s"could not find a destination, re-scheduling") scheduleProbe() diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala index cf878ed627..4fb4ace785 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala @@ -48,14 +48,14 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis when(WAITING_FOR_REQUEST) { case Event(c: SendPaymentToRoute, WaitingForRequest) => - val send = SendPayment(c.amountMsat, c.paymentHash, c.hops.last, finalCltvExpiry = c.finalCltvExpiry, maxAttempts = 1) - paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING)) + val send = SendPayment(c.amountMsat, c.paymentHash, c.hops.last, finalCltvExpiry = c.finalCltvExpiry, maxAttempts = 1, assistedRoutes = Seq.empty, routeParams = None, paymentRequest_opt = None, description_opt = None) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, None, None, c.hops.last)) router ! FinalizeRoute(c.hops) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, send, failures = Nil) case Event(c: SendPayment, WaitingForRequest) => router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.amountMsat, c.assistedRoutes, routeParams = c.routeParams) - paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING)) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt, c.description_opt, c.targetNodeId)) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, c, failures = Nil) } @@ -208,7 +208,9 @@ object PaymentLifecycle { assistedRoutes: Seq[Seq[ExtraHop]] = Nil, finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY, maxAttempts: Int, - routeParams: Option[RouteParams] = None) extends GenericSendPayment { + routeParams: Option[RouteParams] = None, + paymentRequest_opt: Option[PaymentRequest] = None, + description_opt: Option[String] = None) extends GenericSendPayment { require(amountMsat > 0, s"amountMsat must be > 0") } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala index b8eec4b784..764b192f4b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala @@ -17,6 +17,7 @@ package fr.acinq.eclair.db import java.util.UUID + import fr.acinq.eclair.db.sqlite.SqliteUtils._ import fr.acinq.bitcoin.{Block, ByteVector32, MilliSatoshi} import fr.acinq.eclair.TestConstants.Bob @@ -26,8 +27,11 @@ import fr.acinq.eclair.payment.PaymentRequest import org.scalatest.FunSuite import scodec.bits._ import fr.acinq.eclair.randomBytes32 + import scala.compat.Platform import OutgoingPaymentStatus._ +import fr.acinq.bitcoin.Crypto.PublicKey + import concurrent.duration._ class SqlitePaymentsDbSpec extends FunSuite { @@ -71,7 +75,7 @@ class SqlitePaymentsDbSpec extends FunSuite { assert(preMigrationDb.getIncomingPayment(oldReceivedPayment.paymentHash).isEmpty) // add a few rows - val ps1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING) + val ps1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) val i1 = PaymentRequest.read("lnbc10u1pw2t4phpp5ezwm2gdccydhnphfyepklc0wjkxhz0r4tctg9paunh2lxgeqhcmsdqlxycrqvpqwdshgueqvfjhggr0dcsry7qcqzpgfa4ecv7447p9t5hkujy9qgrxvkkf396p9zar9p87rv2htmeuunkhydl40r64n5s2k0u7uelzc8twxmp37nkcch6m0wg5tvvx69yjz8qpk94qf3") val pr1 = IncomingPayment(i1.paymentHash, 12345678, 1513871928275L) @@ -121,8 +125,8 @@ class SqlitePaymentsDbSpec extends FunSuite { val db = new SqlitePaymentsDb(TestConstants.sqliteInMemory()) - val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING) - val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING) + val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) assert(db.listOutgoingPayments().isEmpty) db.addOutgoingPayment(s1) From b72722ba1a536190b1685f52778e41325c9d079b Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 25 Jun 2019 16:06:53 +0200 Subject: [PATCH 02/11] Add new fields to db and handle migration for version 2->3 --- .../eclair/db/sqlite/SqlitePaymentsDb.scala | 67 +++++++++++----- .../acinq/eclair/db/sqlite/SqliteUtils.scala | 7 +- .../eclair/db/SqlitePaymentsDbSpec.scala | 79 +++++++++++++++++-- 3 files changed, 126 insertions(+), 27 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala index 4b34c2e9f3..107c536b69 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala @@ -16,7 +16,7 @@ package fr.acinq.eclair.db.sqlite -import java.sql.Connection +import java.sql.{Connection, Statement} import java.util.UUID import fr.acinq.bitcoin.ByteVector32 import fr.acinq.eclair.db.sqlite.SqliteUtils._ @@ -25,6 +25,8 @@ import fr.acinq.eclair.payment.PaymentRequest import grizzled.slf4j.Logging import scala.collection.immutable.Queue import OutgoingPaymentStatus._ +import fr.acinq.bitcoin.Crypto.PublicKey +import scodec.bits.ByteVector import concurrent.duration._ import scala.compat.Platform @@ -33,25 +35,50 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { import SqliteUtils.ExtendedResultSet._ val DB_NAME = "payments" - val CURRENT_VERSION = 2 + val CURRENT_VERSION = 3 - using(sqlite.createStatement()) { statement => - require(getVersion(statement, DB_NAME, CURRENT_VERSION) <= CURRENT_VERSION, s"incompatible version of $DB_NAME DB found") // version 2 is "backward compatible" in the sense that it uses separate tables from version 1. There is no migration though + def migration12(statement: Statement) = { statement.executeUpdate("CREATE TABLE IF NOT EXISTS received_payments (payment_hash BLOB NOT NULL PRIMARY KEY, preimage BLOB NOT NULL, payment_request TEXT NOT NULL, received_msat INTEGER, created_at INTEGER NOT NULL, expire_at INTEGER, received_at INTEGER)") statement.executeUpdate("CREATE TABLE IF NOT EXISTS sent_payments (id TEXT NOT NULL PRIMARY KEY, payment_hash BLOB NOT NULL, preimage BLOB, amount_msat INTEGER NOT NULL, created_at INTEGER NOT NULL, completed_at INTEGER, status VARCHAR NOT NULL)") statement.executeUpdate("CREATE INDEX IF NOT EXISTS payment_hash_idx ON sent_payments(payment_hash)") - setVersion(statement, DB_NAME, CURRENT_VERSION) + } + + def migration23(statement: Statement) = { + statement.executeUpdate("ALTER TABLE sent_payments ADD payment_request TEXT") + statement.executeUpdate("ALTER TABLE sent_payments ADD custom_description TEXT") + statement.executeUpdate(s"ALTER TABLE sent_payments ADD target_node_id BLOB DEFAULT '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f' NOT NULL") + } + + using(sqlite.createStatement()) { statement => + getVersion(statement, DB_NAME, CURRENT_VERSION) match { + case 1 => + logger.warn(s"migrating $DB_NAME from version 1 to $CURRENT_VERSION") + migration12(statement) + migration23(statement) + setVersion(statement, DB_NAME, CURRENT_VERSION) + case 2 => + logger.warn(s"migrating $DB_NAME from version 2 to $CURRENT_VERSION") + migration23(statement) + setVersion(statement, DB_NAME, CURRENT_VERSION) + case CURRENT_VERSION => + statement.executeUpdate("CREATE TABLE IF NOT EXISTS received_payments (payment_hash BLOB NOT NULL PRIMARY KEY, preimage BLOB NOT NULL, payment_request TEXT NOT NULL, received_msat INTEGER, created_at INTEGER NOT NULL, expire_at INTEGER, received_at INTEGER)") + statement.executeUpdate("CREATE TABLE IF NOT EXISTS sent_payments (id TEXT NOT NULL PRIMARY KEY, payment_hash BLOB NOT NULL, preimage BLOB, amount_msat INTEGER NOT NULL, created_at INTEGER NOT NULL, completed_at INTEGER, status VARCHAR NOT NULL, payment_request TEXT, custom_description TEXT, target_node_id BLOB NOT NULL)") + statement.executeUpdate("CREATE INDEX IF NOT EXISTS payment_hash_idx ON sent_payments(payment_hash)") + setVersion(statement, DB_NAME, CURRENT_VERSION) + } } override def addOutgoingPayment(sent: OutgoingPayment): Unit = { - using(sqlite.prepareStatement("INSERT INTO sent_payments (id, payment_hash, amount_msat, created_at, status) VALUES (?, ?, ?, ?, ?)")) { statement => + using(sqlite.prepareStatement("INSERT INTO sent_payments (id, payment_hash, amount_msat, created_at, status, payment_request, custom_description, target_node_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) { statement => statement.setString(1, sent.id.toString) statement.setBytes(2, sent.paymentHash.toArray) statement.setLong(3, sent.amountMsat) statement.setLong(4, sent.createdAt) statement.setString(5, sent.status.toString) - val res = statement.executeUpdate() - logger.debug(s"inserted $res payment=${sent.paymentHash} into payment DB") + statement.setString(6, sent.paymentRequest_opt.map(PaymentRequest.write).orNull) + statement.setString(7, sent.description_opt.orNull) + statement.setBytes(8, sent.targetNodeId.value.toHex.getBytes) + statement.executeUpdate() } } @@ -68,7 +95,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { } override def getOutgoingPayment(id: UUID): Option[OutgoingPayment] = { - using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status FROM sent_payments WHERE id = ?")) { statement => + using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status, payment_request, custom_description, target_node_id FROM sent_payments WHERE id = ?")) { statement => statement.setString(1, id.toString) val rs = statement.executeQuery() if (rs.next()) { @@ -80,9 +107,9 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getLong("created_at"), getNullableLong(rs, "completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), - None, - None, - ??? + rs.getStringNullable("payment_request").map(PaymentRequest.read), + rs.getStringNullable("custom_description"), + PublicKey(ByteVector.fromValidHex(rs.getString("target_node_id"))) )) } else { None @@ -91,7 +118,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { } override def getOutgoingPayments(paymentHash: ByteVector32): Seq[OutgoingPayment] = { - using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status FROM sent_payments WHERE payment_hash = ?")) { statement => + using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status, payment_request, custom_description, target_node_id FROM sent_payments WHERE payment_hash = ?")) { statement => statement.setBytes(1, paymentHash.toArray) val rs = statement.executeQuery() var q: Queue[OutgoingPayment] = Queue() @@ -104,9 +131,9 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getLong("created_at"), getNullableLong(rs, "completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), - None, - None, - ??? + rs.getStringNullable("payment_request").map(PaymentRequest.read), + rs.getStringNullable("custom_description"), + PublicKey(ByteVector.fromValidHex(rs.getString("target_node_id"))) ) } q @@ -115,7 +142,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { override def listOutgoingPayments(): Seq[OutgoingPayment] = { using(sqlite.createStatement()) { statement => - val rs = statement.executeQuery("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status FROM sent_payments") + val rs = statement.executeQuery("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status, payment_request, custom_description, target_node_id FROM sent_payments") var q: Queue[OutgoingPayment] = Queue() while (rs.next()) { q = q :+ OutgoingPayment( @@ -126,9 +153,9 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getLong("created_at"), getNullableLong(rs, "completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), - None, - None, - ??? + rs.getStringNullable("payment_request").map(PaymentRequest.read), + rs.getStringNullable("custom_description"), + PublicKey(ByteVector.fromValidHex(rs.getString("target_node_id"))) ) } q diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala index dda0510dee..74343486b6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala @@ -17,11 +17,9 @@ package fr.acinq.eclair.db.sqlite import java.sql.{Connection, ResultSet, Statement} - import fr.acinq.bitcoin.ByteVector32 import scodec.Codec import scodec.bits.{BitVector, ByteVector} - import scala.collection.immutable.Queue object SqliteUtils { @@ -131,6 +129,11 @@ object SqliteUtils { val bytes = rs.getBytes(columnLabel) if(rs.wasNull()) None else Some(ByteVector32(ByteVector(bytes))) } + + def getStringNullable(columnLabel: String): Option[String] = { + val str = rs.getString(columnLabel) + if(rs.wasNull()) None else Some(str) + } } object ExtendedResultSet { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala index 764b192f4b..2deae382ed 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala @@ -31,21 +31,25 @@ import fr.acinq.eclair.randomBytes32 import scala.compat.Platform import OutgoingPaymentStatus._ import fr.acinq.bitcoin.Crypto.PublicKey +import fr.acinq.eclair.wire.ChannelCodecs._ import concurrent.duration._ class SqlitePaymentsDbSpec extends FunSuite { + lazy val dummyPaymentRequest = PaymentRequest.read("lnbc5450n1pw2t4qdpp5vcrf6ylgpettyng4ac3vujsk0zpc25cj0q3zp7l7w44zvxmpzh8qdzz2pshjmt9de6zqen0wgsr2dp4ypcxj7r9d3ejqct5ypekzar0wd5xjuewwpkxzcm99cxqzjccqp2rzjqtspxelp67qc5l56p6999wkatsexzhs826xmupyhk6j8lxl038t27z9tsqqqgpgqqqqqqqlgqqqqqzsqpcz8z8hmy8g3ecunle4n3edn3zg2rly8g4klsk5md736vaqqy3ktxs30ht34rkfkqaffzxmjphvd0637dk2lp6skah2hq09z6lrjna3xqp3d4vyd") + test("init sqlite 2 times in a row") { val sqlite = TestConstants.sqliteInMemory() val db1 = new SqlitePaymentsDb(sqlite) val db2 = new SqlitePaymentsDb(sqlite) } - test("handle version migration 1->2") { + test("handle version migration 1->3") { val connection = TestConstants.sqliteInMemory() + // payment DB version 1 using(connection.createStatement()) { statement => getVersion(statement, "payments", 1) statement.executeUpdate("CREATE TABLE IF NOT EXISTS payments (payment_hash BLOB NOT NULL PRIMARY KEY, amount_msat INTEGER NOT NULL, timestamp INTEGER NOT NULL)") @@ -68,7 +72,7 @@ class SqlitePaymentsDbSpec extends FunSuite { val preMigrationDb = new SqlitePaymentsDb(connection) using(connection.createStatement()) { statement => - assert(getVersion(statement, "payments", 1) == 2) // version has changed from 1 to 2! + assert(getVersion(statement, "payments", 1) == 3) // version has changed from 1 to 3! } // the existing received payment can NOT be queried anymore @@ -90,7 +94,7 @@ class SqlitePaymentsDbSpec extends FunSuite { val postMigrationDb = new SqlitePaymentsDb(connection) using(connection.createStatement()) { statement => - assert(getVersion(statement, "payments", 2) == 2) // version still to 2 + assert(getVersion(statement, "payments", 3) == 3) // version still to 2 } assert(postMigrationDb.listIncomingPayments() == Seq(pr1)) @@ -98,6 +102,69 @@ class SqlitePaymentsDbSpec extends FunSuite { assert(preMigrationDb.listPaymentRequests(0, (Platform.currentTime.milliseconds + 1.minute).toSeconds) == Seq(i1)) } + + test("handle version migration 2->3") { + + val connection = TestConstants.sqliteInMemory() + + // payment DB version 2 + using(connection.createStatement()) { statement => + getVersion(statement, "payments", 2) + statement.executeUpdate("CREATE TABLE IF NOT EXISTS received_payments (payment_hash BLOB NOT NULL PRIMARY KEY, preimage BLOB NOT NULL, payment_request TEXT NOT NULL, received_msat INTEGER, created_at INTEGER NOT NULL, expire_at INTEGER, received_at INTEGER)") + statement.executeUpdate("CREATE TABLE IF NOT EXISTS sent_payments (id TEXT NOT NULL PRIMARY KEY, payment_hash BLOB NOT NULL, preimage BLOB, amount_msat INTEGER NOT NULL, created_at INTEGER NOT NULL, completed_at INTEGER, status VARCHAR NOT NULL)") + statement.executeUpdate("CREATE INDEX IF NOT EXISTS payment_hash_idx ON sent_payments(payment_hash)") + } + + using(connection.createStatement()) { statement => + assert(getVersion(statement, "payments", 2) == 2) // version 2 is deployed now + } + + // insert old type record + using(connection.prepareStatement("INSERT INTO sent_payments VALUES (?, ?, ?, ?, ?, ?, ?)")) { statement => + statement.setString(1, UNKNOWN_UUID.toString) + statement.setBytes(2, ByteVector32.One.toArray) + statement.setBytes(3, ByteVector32.Zeroes.toArray) + statement.setLong(4, 123) + statement.setLong(5, 456) + statement.setLong(6, 789) + statement.setString(7, PENDING.toString) + statement.executeUpdate() + } + + // migration + val preMigrationDb = new SqlitePaymentsDb(connection) + + using(connection.createStatement()) { statement => + assert(getVersion(statement, "payments", 2) == 3) // version has changed from 2 to 3! + } + + // check the old record has been migrated + assert(preMigrationDb.getOutgoingPayment(UNKNOWN_UUID).get.targetNodeId == PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) + + // add a few rows + val ps1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val i1 = PaymentRequest.read("lnbc10u1pw2t4phpp5ezwm2gdccydhnphfyepklc0wjkxhz0r4tctg9paunh2lxgeqhcmsdqlxycrqvpqwdshgueqvfjhggr0dcsry7qcqzpgfa4ecv7447p9t5hkujy9qgrxvkkf396p9zar9p87rv2htmeuunkhydl40r64n5s2k0u7uelzc8twxmp37nkcch6m0wg5tvvx69yjz8qpk94qf3") + val pr1 = IncomingPayment(i1.paymentHash, 12345678, 1513871928275L) + + preMigrationDb.addPaymentRequest(i1, ByteVector32.Zeroes) + preMigrationDb.addIncomingPayment(pr1) + preMigrationDb.addOutgoingPayment(ps1) + + assert(preMigrationDb.listIncomingPayments() == Seq(pr1)) + assert(preMigrationDb.listOutgoingPayments().contains(ps1)) + assert(preMigrationDb.listPaymentRequests(0, (Platform.currentTime.milliseconds + 1.minute).toSeconds) == Seq(i1)) + + val postMigrationDb = new SqlitePaymentsDb(connection) + + using(connection.createStatement()) { statement => + assert(getVersion(statement, "payments", 3) == 3) // version still to 3 + } + + assert(postMigrationDb.listIncomingPayments() == Seq(pr1)) + assert(postMigrationDb.listOutgoingPayments().contains(ps1)) + assert(preMigrationDb.listPaymentRequests(0, (Platform.currentTime.milliseconds + 1.minute).toSeconds) == Seq(i1)) + } + test("add/list received payments/find 1 payment that exists/find 1 payment that does not exist") { val sqlite = TestConstants.sqliteInMemory() val db = new SqlitePaymentsDb(sqlite) @@ -125,8 +192,8 @@ class SqlitePaymentsDbSpec extends FunSuite { val db = new SqlitePaymentsDb(TestConstants.sqliteInMemory()) - val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) - val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, paymentRequest_opt = Some(dummyPaymentRequest), targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, description_opt = Some("custom description"), targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) assert(db.listOutgoingPayments().isEmpty) db.addOutgoingPayment(s1) @@ -135,6 +202,8 @@ class SqlitePaymentsDbSpec extends FunSuite { assert(db.listOutgoingPayments().toList == Seq(s1, s2)) assert(db.getOutgoingPayment(s1.id) === Some(s1)) assert(db.getOutgoingPayment(s1.id).get.completedAt.isEmpty) + assert(db.getOutgoingPayment(s1.id).get.paymentRequest_opt === Some(dummyPaymentRequest)) + assert(db.getOutgoingPayment(s2.id).get.description_opt === Some("custom description")) assert(db.getOutgoingPayment(UUID.randomUUID()) === None) assert(db.getOutgoingPayments(s2.paymentHash) === Seq(s2)) assert(db.getOutgoingPayments(ByteVector32.Zeroes) === Seq.empty) From 09bb355d39792f7ee58b49388d3e8cfa4db1a7b5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 25 Jun 2019 16:27:59 +0200 Subject: [PATCH 03/11] Add test for EclairApi, limit receiving invoice to 65k for outgoing payments --- .../src/main/scala/fr/acinq/eclair/Eclair.scala | 1 + .../test/scala/fr/acinq/eclair/EclairImplSpec.scala | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala index 6450b1953e..1d90fe3ddd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -189,6 +189,7 @@ class EclairImpl(appKit: Kit) extends Eclair { } override def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]], minFinalCltvExpiry_opt: Option[Long], maxAttempts_opt: Option[Int], feeThresholdSat_opt: Option[Long], maxFeePct_opt: Option[Double], paymentRequest_opt: Option[PaymentRequest], customDescription_opt: Option[String])(implicit timeout: Timeout): Future[UUID] = { + require(paymentRequest_opt.map(PaymentRequest.write(_).getBytes).forall(_.length <= 65536)) val maxAttempts = maxAttempts_opt.getOrElse(appKit.nodeParams.maxPaymentAttempts) val defaultRouteParams = Router.getDefaultRouteParams(appKit.nodeParams.routerConf) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala index 2ab1b0b329..1e7ebba7d4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala @@ -132,6 +132,18 @@ class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSu assert(send3.paymentHash == ByteVector32.Zeroes) assert(send3.routeParams.get.maxFeeBaseMsat == 123 * 1000) // conversion sat -> msat assert(send3.routeParams.get.maxFeePct == 4.20) + + // with paymentRequest and custom description + val pr = PaymentRequest.read("lnbc10u1pw2t4phpp5ezwm2gdccydhnphfyepklc0wjkxhz0r4tctg9paunh2lxgeqhcmsdqlxycrqvpqwdshgueqvfjhggr0dcsry7qcqzpgfa4ecv7447p9t5hkujy9qgrxvkkf396p9zar9p87rv2htmeuunkhydl40r64n5s2k0u7uelzc8twxmp37nkcch6m0wg5tvvx69yjz8qpk94qf3") + eclair.send(recipientNodeId = nodeId, amountMsat = 123, paymentHash = ByteVector32.Zeroes, assistedRoutes = Seq.empty, minFinalCltvExpiry_opt = None, feeThresholdSat_opt = Some(123), maxFeePct_opt = Some(4.20), paymentRequest_opt = Some(pr), customDescription_opt = Some("hey hey heyy")) + val send4 = paymentInitiator.expectMsgType[SendPayment] + assert(send4.targetNodeId == nodeId) + assert(send4.amountMsat == 123) + assert(send4.paymentHash == ByteVector32.Zeroes) + assert(send4.routeParams.get.maxFeeBaseMsat == 123 * 1000) // conversion sat -> msat + assert(send4.routeParams.get.maxFeePct == 4.20) + assert(send4.paymentRequest_opt == Some(pr)) + assert(send4.description_opt == Some("hey hey heyy")) } test("allupdates can filter by nodeId") { f => From 198d3074a58de70bf2d30eee8d9a88a7cde939b2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 25 Jun 2019 16:32:09 +0200 Subject: [PATCH 04/11] Do not store paid invoices from API calls, update ApiServiceSpec --- .../src/main/scala/fr/acinq/eclair/api/Service.scala | 4 ++-- .../src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala index 0394816cdb..60cc8bc5eb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala @@ -219,9 +219,9 @@ trait Service extends ExtraDirectives with Logging { path("payinvoice") { formFields(invoiceFormParam, amountMsatFormParam.?, "maxAttempts".as[Int].?, "feeThresholdSat".as[Long].?, "maxFeePct".as[Double].?) { case (invoice@PaymentRequest(_, Some(amount), _, nodeId, _, _), None, maxAttempts, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(nodeId, amount.toLong, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, Some(invoice), None)) + complete(eclairApi.send(nodeId, amount.toLong, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, None, None)) case (invoice, Some(overrideAmount), maxAttempts, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, Some(invoice), None)) + complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, None, None)) case _ => reject(MalformedFormFieldRejection("invoice", "The invoice must have an amount or you need to specify one using the field 'amountMsat'")) } } ~ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index c558649466..b0cc59456f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -241,7 +241,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock val invoice = "lnbc12580n1pw2ywztpp554ganw404sh4yjkwnysgn3wjcxfcq7gtx53gxczkjr9nlpc3hzvqdq2wpskwctddyxqr4rqrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glc7z9rtvqqwngqqqqqqqlgqqqqqeqqjqrrt8smgjvfj7sg38dwtr9kc9gg3era9k3t2hvq3cup0jvsrtrxuplevqgfhd3rzvhulgcxj97yjuj8gdx8mllwj4wzjd8gdjhpz3lpqqvk2plh" val eclair = mock[Eclair] - eclair.send(any, any, any, any, any, any, any, any)(any[Timeout]) returns Future.successful(UUID.randomUUID()) + eclair.send(any, any, any, any, any, any, any, any, any, any)(any[Timeout]) returns Future.successful(UUID.randomUUID()) val mockService = new MockService(eclair) Post("/payinvoice", FormData("invoice" -> invoice).toEntity) ~> @@ -250,7 +250,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == OK) - eclair.send(any, 1258000, any, any, any, any, any, any)(any[Timeout]).wasCalled(once) + eclair.send(any, 1258000, any, any, any, any, any, any, None, None)(any[Timeout]).wasCalled(once) } @@ -260,7 +260,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == OK) - eclair.send(any, 123, any, any, any, any, Some(112233), Some(2.34))(any[Timeout]).wasCalled(once) + eclair.send(any, 123, any, any, any, any, Some(112233), Some(2.34), None, None)(any[Timeout]).wasCalled(once) } } From fa25fd740cb63bb903b8cf65b1798d56581832f2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 25 Jun 2019 17:06:20 +0200 Subject: [PATCH 05/11] Renaming --- .../src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala | 4 ++-- .../scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala | 2 +- .../test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index bc2ebbc392..84cb7381a0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -78,7 +78,7 @@ case class IncomingPayment(paymentHash: ByteVector32, amountMsat: Long, received * @param completedAt absolute time in seconds since UNIX epoch when the payment succeeded. * @param status current status of the payment. * @param paymentRequest_opt the payment request that was associated with this payment - * @param description_opt a custom description + * @param customDescription_opt a custom description * @param targetNodeId the recipient of this payment */ case class OutgoingPayment( @@ -90,7 +90,7 @@ case class OutgoingPayment( completedAt: Option[Long], status: OutgoingPaymentStatus.Value, paymentRequest_opt: Option[PaymentRequest] = None, - description_opt: Option[String] = None, + customDescription_opt: Option[String] = None, targetNodeId: PublicKey ) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala index 107c536b69..8b2601c5c8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala @@ -76,7 +76,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { statement.setLong(4, sent.createdAt) statement.setString(5, sent.status.toString) statement.setString(6, sent.paymentRequest_opt.map(PaymentRequest.write).orNull) - statement.setString(7, sent.description_opt.orNull) + statement.setString(7, sent.customDescription_opt.orNull) statement.setBytes(8, sent.targetNodeId.value.toHex.getBytes) statement.executeUpdate() } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala index 2deae382ed..3637914faf 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala @@ -193,7 +193,7 @@ class SqlitePaymentsDbSpec extends FunSuite { val db = new SqlitePaymentsDb(TestConstants.sqliteInMemory()) val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, paymentRequest_opt = Some(dummyPaymentRequest), targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) - val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, description_opt = Some("custom description"), targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, customDescription_opt = Some("custom description"), targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) assert(db.listOutgoingPayments().isEmpty) db.addOutgoingPayment(s1) @@ -203,7 +203,7 @@ class SqlitePaymentsDbSpec extends FunSuite { assert(db.getOutgoingPayment(s1.id) === Some(s1)) assert(db.getOutgoingPayment(s1.id).get.completedAt.isEmpty) assert(db.getOutgoingPayment(s1.id).get.paymentRequest_opt === Some(dummyPaymentRequest)) - assert(db.getOutgoingPayment(s2.id).get.description_opt === Some("custom description")) + assert(db.getOutgoingPayment(s2.id).get.customDescription_opt === Some("custom description")) assert(db.getOutgoingPayment(UUID.randomUUID()) === None) assert(db.getOutgoingPayments(s2.paymentHash) === Seq(s2)) assert(db.getOutgoingPayments(ByteVector32.Zeroes) === Seq.empty) From d97462603e69bab0b9975a808e84604ce7b9c46f Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 26 Jun 2019 16:41:16 +0200 Subject: [PATCH 06/11] Enrich test in paymentLifecycle, formatting --- .../scala/fr/acinq/eclair/db/PaymentsDb.scala | 18 +++++++++--------- .../eclair/payment/PaymentLifecycleSpec.scala | 11 +++++++++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index 84cb7381a0..024ba40877 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -70,16 +70,16 @@ case class IncomingPayment(paymentHash: ByteVector32, amountMsat: Long, received * OutgoingPayment is every payment that is sent by this node, they may not be finalized and * when is final it can be failed or successful. * - * @param id internal payment identifier - * @param paymentHash payment_hash - * @param preimage the preimage of the payment_hash, known if the outgoing payment was successful - * @param amountMsat amount of the payment, in milli-satoshis - * @param createdAt absolute time in seconds since UNIX epoch when the payment was created. - * @param completedAt absolute time in seconds since UNIX epoch when the payment succeeded. - * @param status current status of the payment. - * @param paymentRequest_opt the payment request that was associated with this payment + * @param id internal payment identifier + * @param paymentHash payment_hash + * @param preimage the preimage of the payment_hash, known if the outgoing payment was successful + * @param amountMsat amount of the payment, in milli-satoshis + * @param createdAt absolute time in seconds since UNIX epoch when the payment was created. + * @param completedAt absolute time in seconds since UNIX epoch when the payment succeeded. + * @param status current status of the payment. + * @param paymentRequest_opt the payment request that was associated with this payment * @param customDescription_opt a custom description - * @param targetNodeId the recipient of this payment + * @param targetNodeId the recipient of this payment */ case class OutgoingPayment( id: UUID, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index deb2a51441..f6b521f7ed 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -67,6 +67,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]]) val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]]) awaitCond(paymentDb.getOutgoingPayment(id).exists(_.status == OutgoingPaymentStatus.PENDING)) + awaitCond(paymentDb.getOutgoingPayment(id).exists(_.targetNodeId == d)) sender.send(paymentFSM, UpdateFulfillHtlc(ByteVector32.Zeroes, 0, defaultPaymentHash)) sender.expectMsgType[PaymentSucceeded] @@ -369,6 +370,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { test("payment succeeded") { fixture => import fixture._ + val paymentRequest = PaymentRequest.read("lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp") val defaultPaymentHash = randomBytes32 val nodeParams = TestConstants.Alice.nodeParams.copy(keyManager = testKeyManager) val paymentDb = nodeParams.db.payments @@ -382,11 +384,16 @@ class PaymentLifecycleSpec extends BaseRouterSpec { paymentFSM ! SubscribeTransitionCallBack(monitor.ref) val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]]) - val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d, maxAttempts = 5) + val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d, paymentRequest_opt = Some(paymentRequest), description_opt = Some("yolo"), maxAttempts = 5) sender.send(paymentFSM, request) val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]]) val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]]) - awaitCond(paymentDb.getOutgoingPayment(id).exists(_.status == OutgoingPaymentStatus.PENDING)) + awaitCond(paymentDb.getOutgoingPayment(id).exists { payment => + payment.status == OutgoingPaymentStatus.PENDING && + payment.targetNodeId == d && + payment.paymentRequest_opt == Some(paymentRequest) && + payment.customDescription_opt == Some("yolo") + }) sender.send(paymentFSM, UpdateFulfillHtlc(ByteVector32.Zeroes, 0, defaultPaymentHash)) val paymentOK = sender.expectMsgType[PaymentSucceeded] From 8472d933d74a432f7e7295fd902ed3085a0abf82 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 26 Jun 2019 16:42:16 +0200 Subject: [PATCH 07/11] Renaming --- eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala | 2 +- .../src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala | 2 +- .../scala/fr/acinq/eclair/payment/PaymentLifecycle.scala | 6 +++--- .../src/test/scala/fr/acinq/eclair/EclairImplSpec.scala | 2 +- .../fr/acinq/eclair/payment/PaymentLifecycleSpec.scala | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala index 1d90fe3ddd..08fcd1f259 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -200,7 +200,7 @@ class EclairImpl(appKit: Kit) extends Eclair { val sendPayment = minFinalCltvExpiry_opt match { case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt, customDescription_opt) - case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt = paymentRequest_opt, description_opt = customDescription_opt) + case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt = paymentRequest_opt, customDescription_opt = customDescription_opt) } (appKit.paymentInitiator ? sendPayment).mapTo[UUID] } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala index 56474509d5..220468b434 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala @@ -53,7 +53,7 @@ class Autoprobe(nodeParams: NodeParams, router: ActorRef, paymentInitiator: Acto case Some(targetNodeId) => val paymentHash = randomBytes32 // we don't even know the preimage (this needs to be a secure random!) log.info(s"sending payment probe to node=$targetNodeId payment_hash=$paymentHash") - paymentInitiator ! SendPayment(PAYMENT_AMOUNT_MSAT, paymentHash, targetNodeId, maxAttempts = 1, paymentRequest_opt = None, description_opt = None) + paymentInitiator ! SendPayment(PAYMENT_AMOUNT_MSAT, paymentHash, targetNodeId, maxAttempts = 1, paymentRequest_opt = None, customDescription_opt = None) case None => log.info(s"could not find a destination, re-scheduling") scheduleProbe() diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala index 4fb4ace785..472ec7e5b0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala @@ -48,14 +48,14 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis when(WAITING_FOR_REQUEST) { case Event(c: SendPaymentToRoute, WaitingForRequest) => - val send = SendPayment(c.amountMsat, c.paymentHash, c.hops.last, finalCltvExpiry = c.finalCltvExpiry, maxAttempts = 1, assistedRoutes = Seq.empty, routeParams = None, paymentRequest_opt = None, description_opt = None) + val send = SendPayment(c.amountMsat, c.paymentHash, c.hops.last, finalCltvExpiry = c.finalCltvExpiry, maxAttempts = 1, assistedRoutes = Seq.empty, routeParams = None, paymentRequest_opt = None, customDescription_opt = None) paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, None, None, c.hops.last)) router ! FinalizeRoute(c.hops) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, send, failures = Nil) case Event(c: SendPayment, WaitingForRequest) => router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.amountMsat, c.assistedRoutes, routeParams = c.routeParams) - paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt, c.description_opt, c.targetNodeId)) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt, c.customDescription_opt, c.targetNodeId)) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, c, failures = Nil) } @@ -210,7 +210,7 @@ object PaymentLifecycle { maxAttempts: Int, routeParams: Option[RouteParams] = None, paymentRequest_opt: Option[PaymentRequest] = None, - description_opt: Option[String] = None) extends GenericSendPayment { + customDescription_opt: Option[String] = None) extends GenericSendPayment { require(amountMsat > 0, s"amountMsat must be > 0") } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala index 1e7ebba7d4..6c5caac418 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala @@ -143,7 +143,7 @@ class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSu assert(send4.routeParams.get.maxFeeBaseMsat == 123 * 1000) // conversion sat -> msat assert(send4.routeParams.get.maxFeePct == 4.20) assert(send4.paymentRequest_opt == Some(pr)) - assert(send4.description_opt == Some("hey hey heyy")) + assert(send4.customDescription_opt == Some("hey hey heyy")) } test("allupdates can filter by nodeId") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index f6b521f7ed..2e7fb43e0f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -384,7 +384,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { paymentFSM ! SubscribeTransitionCallBack(monitor.ref) val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]]) - val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d, paymentRequest_opt = Some(paymentRequest), description_opt = Some("yolo"), maxAttempts = 5) + val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d, paymentRequest_opt = Some(paymentRequest), customDescription_opt = Some("yolo"), maxAttempts = 5) sender.send(paymentFSM, request) val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]]) val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]]) From 9c7567c9944d22298b45f75326d63e2bbc92f206 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 27 Jun 2019 15:50:36 +0200 Subject: [PATCH 08/11] Rename customDescription -> description_opt, Use description from the invoice in paymentDB.sent_payments, use datatype BLOB to store descriptions --- .../main/scala/fr/acinq/eclair/Eclair.scala | 8 ++++---- .../scala/fr/acinq/eclair/api/Service.scala | 6 +++--- .../scala/fr/acinq/eclair/db/PaymentsDb.scala | 4 ++-- .../eclair/db/sqlite/SqlitePaymentsDb.scala | 20 +++++++++---------- .../acinq/eclair/db/sqlite/SqliteUtils.scala | 6 ++++-- .../fr/acinq/eclair/payment/Autoprobe.scala | 2 +- .../eclair/payment/PaymentLifecycle.scala | 8 ++++---- .../fr/acinq/eclair/EclairImplSpec.scala | 5 ++--- .../fr/acinq/eclair/api/ApiServiceSpec.scala | 6 +++--- .../eclair/db/SqlitePaymentsDbSpec.scala | 6 +++--- .../eclair/payment/PaymentLifecycleSpec.scala | 4 ++-- 11 files changed, 38 insertions(+), 37 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala index 08fcd1f259..1c0c0f2228 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -77,7 +77,7 @@ trait Eclair { def receivedInfo(paymentHash: ByteVector32)(implicit timeout: Timeout): Future[Option[IncomingPayment]] - def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long] = None, maxAttempts_opt: Option[Int] = None, feeThresholdSat_opt: Option[Long] = None, maxFeePct_opt: Option[Double] = None, paymentRequest_opt: Option[PaymentRequest] = None, customDescription_opt: Option[String] = None)(implicit timeout: Timeout): Future[UUID] + def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long] = None, maxAttempts_opt: Option[Int] = None, feeThresholdSat_opt: Option[Long] = None, maxFeePct_opt: Option[Double] = None, paymentRequest_opt: Option[PaymentRequest] = None)(implicit timeout: Timeout): Future[UUID] def sentInfo(id: Either[UUID, ByteVector32])(implicit timeout: Timeout): Future[Seq[OutgoingPayment]] @@ -188,7 +188,7 @@ class EclairImpl(appKit: Kit) extends Eclair { (appKit.paymentInitiator ? SendPaymentToRoute(amountMsat, paymentHash, route, finalCltvExpiry)).mapTo[UUID] } - override def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]], minFinalCltvExpiry_opt: Option[Long], maxAttempts_opt: Option[Int], feeThresholdSat_opt: Option[Long], maxFeePct_opt: Option[Double], paymentRequest_opt: Option[PaymentRequest], customDescription_opt: Option[String])(implicit timeout: Timeout): Future[UUID] = { + override def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]], minFinalCltvExpiry_opt: Option[Long], maxAttempts_opt: Option[Int], feeThresholdSat_opt: Option[Long], maxFeePct_opt: Option[Double], paymentRequest_opt: Option[PaymentRequest])(implicit timeout: Timeout): Future[UUID] = { require(paymentRequest_opt.map(PaymentRequest.write(_).getBytes).forall(_.length <= 65536)) val maxAttempts = maxAttempts_opt.getOrElse(appKit.nodeParams.maxPaymentAttempts) @@ -199,8 +199,8 @@ class EclairImpl(appKit: Kit) extends Eclair { ) val sendPayment = minFinalCltvExpiry_opt match { - case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt, customDescription_opt) - case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt = paymentRequest_opt, customDescription_opt = customDescription_opt) + case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt) + case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt = paymentRequest_opt) } (appKit.paymentInitiator ? sendPayment).mapTo[UUID] } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala index 60cc8bc5eb..d9c6a531d6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala @@ -219,15 +219,15 @@ trait Service extends ExtraDirectives with Logging { path("payinvoice") { formFields(invoiceFormParam, amountMsatFormParam.?, "maxAttempts".as[Int].?, "feeThresholdSat".as[Long].?, "maxFeePct".as[Double].?) { case (invoice@PaymentRequest(_, Some(amount), _, nodeId, _, _), None, maxAttempts, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(nodeId, amount.toLong, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, None, None)) + complete(eclairApi.send(nodeId, amount.toLong, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, None)) case (invoice, Some(overrideAmount), maxAttempts, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, None, None)) + complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt, None)) case _ => reject(MalformedFormFieldRejection("invoice", "The invoice must have an amount or you need to specify one using the field 'amountMsat'")) } } ~ path("sendtonode") { formFields(amountMsatFormParam, paymentHashFormParam, nodeIdFormParam, "maxAttempts".as[Int].?, "feeThresholdSat".as[Long].?, "maxFeePct".as[Double].?) { (amountMsat, paymentHash, nodeId, maxAttempts_opt, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(nodeId, amountMsat, paymentHash, maxAttempts_opt = maxAttempts_opt, feeThresholdSat_opt = feeThresholdSat_opt, maxFeePct_opt = maxFeePct_opt, paymentRequest_opt = None, customDescription_opt = None)) + complete(eclairApi.send(nodeId, amountMsat, paymentHash, maxAttempts_opt = maxAttempts_opt, feeThresholdSat_opt = feeThresholdSat_opt, maxFeePct_opt = maxFeePct_opt, paymentRequest_opt = None)) } } ~ path("sendtoroute") { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index 024ba40877..37d73fa257 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -78,7 +78,7 @@ case class IncomingPayment(paymentHash: ByteVector32, amountMsat: Long, received * @param completedAt absolute time in seconds since UNIX epoch when the payment succeeded. * @param status current status of the payment. * @param paymentRequest_opt the payment request that was associated with this payment - * @param customDescription_opt a custom description + * @param description_opt a custom description * @param targetNodeId the recipient of this payment */ case class OutgoingPayment( @@ -90,7 +90,7 @@ case class OutgoingPayment( completedAt: Option[Long], status: OutgoingPaymentStatus.Value, paymentRequest_opt: Option[PaymentRequest] = None, - customDescription_opt: Option[String] = None, + description_opt: Option[String] = None, targetNodeId: PublicKey ) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala index 8b2601c5c8..1f7c5887f0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala @@ -45,7 +45,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { def migration23(statement: Statement) = { statement.executeUpdate("ALTER TABLE sent_payments ADD payment_request TEXT") - statement.executeUpdate("ALTER TABLE sent_payments ADD custom_description TEXT") + statement.executeUpdate("ALTER TABLE sent_payments ADD description BLOB") statement.executeUpdate(s"ALTER TABLE sent_payments ADD target_node_id BLOB DEFAULT '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f' NOT NULL") } @@ -62,21 +62,21 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { setVersion(statement, DB_NAME, CURRENT_VERSION) case CURRENT_VERSION => statement.executeUpdate("CREATE TABLE IF NOT EXISTS received_payments (payment_hash BLOB NOT NULL PRIMARY KEY, preimage BLOB NOT NULL, payment_request TEXT NOT NULL, received_msat INTEGER, created_at INTEGER NOT NULL, expire_at INTEGER, received_at INTEGER)") - statement.executeUpdate("CREATE TABLE IF NOT EXISTS sent_payments (id TEXT NOT NULL PRIMARY KEY, payment_hash BLOB NOT NULL, preimage BLOB, amount_msat INTEGER NOT NULL, created_at INTEGER NOT NULL, completed_at INTEGER, status VARCHAR NOT NULL, payment_request TEXT, custom_description TEXT, target_node_id BLOB NOT NULL)") + statement.executeUpdate("CREATE TABLE IF NOT EXISTS sent_payments (id TEXT NOT NULL PRIMARY KEY, payment_hash BLOB NOT NULL, preimage BLOB, amount_msat INTEGER NOT NULL, created_at INTEGER NOT NULL, completed_at INTEGER, status VARCHAR NOT NULL, payment_request TEXT, description BLOB, target_node_id BLOB NOT NULL)") statement.executeUpdate("CREATE INDEX IF NOT EXISTS payment_hash_idx ON sent_payments(payment_hash)") setVersion(statement, DB_NAME, CURRENT_VERSION) } } override def addOutgoingPayment(sent: OutgoingPayment): Unit = { - using(sqlite.prepareStatement("INSERT INTO sent_payments (id, payment_hash, amount_msat, created_at, status, payment_request, custom_description, target_node_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) { statement => + using(sqlite.prepareStatement("INSERT INTO sent_payments (id, payment_hash, amount_msat, created_at, status, payment_request, description, target_node_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) { statement => statement.setString(1, sent.id.toString) statement.setBytes(2, sent.paymentHash.toArray) statement.setLong(3, sent.amountMsat) statement.setLong(4, sent.createdAt) statement.setString(5, sent.status.toString) statement.setString(6, sent.paymentRequest_opt.map(PaymentRequest.write).orNull) - statement.setString(7, sent.customDescription_opt.orNull) + statement.setBytes(7, sent.description_opt.map(_.getBytes).orNull) statement.setBytes(8, sent.targetNodeId.value.toHex.getBytes) statement.executeUpdate() } @@ -95,7 +95,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { } override def getOutgoingPayment(id: UUID): Option[OutgoingPayment] = { - using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status, payment_request, custom_description, target_node_id FROM sent_payments WHERE id = ?")) { statement => + using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status, payment_request, description, target_node_id FROM sent_payments WHERE id = ?")) { statement => statement.setString(1, id.toString) val rs = statement.executeQuery() if (rs.next()) { @@ -108,7 +108,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { getNullableLong(rs, "completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), rs.getStringNullable("payment_request").map(PaymentRequest.read), - rs.getStringNullable("custom_description"), + rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)), PublicKey(ByteVector.fromValidHex(rs.getString("target_node_id"))) )) } else { @@ -118,7 +118,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { } override def getOutgoingPayments(paymentHash: ByteVector32): Seq[OutgoingPayment] = { - using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status, payment_request, custom_description, target_node_id FROM sent_payments WHERE payment_hash = ?")) { statement => + using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status, payment_request, description, target_node_id FROM sent_payments WHERE payment_hash = ?")) { statement => statement.setBytes(1, paymentHash.toArray) val rs = statement.executeQuery() var q: Queue[OutgoingPayment] = Queue() @@ -132,7 +132,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { getNullableLong(rs, "completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), rs.getStringNullable("payment_request").map(PaymentRequest.read), - rs.getStringNullable("custom_description"), + rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)), PublicKey(ByteVector.fromValidHex(rs.getString("target_node_id"))) ) } @@ -142,7 +142,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { override def listOutgoingPayments(): Seq[OutgoingPayment] = { using(sqlite.createStatement()) { statement => - val rs = statement.executeQuery("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status, payment_request, custom_description, target_node_id FROM sent_payments") + val rs = statement.executeQuery("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status, payment_request, description, target_node_id FROM sent_payments") var q: Queue[OutgoingPayment] = Queue() while (rs.next()) { q = q :+ OutgoingPayment( @@ -154,7 +154,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { getNullableLong(rs, "completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), rs.getStringNullable("payment_request").map(PaymentRequest.read), - rs.getStringNullable("custom_description"), + rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)), PublicKey(ByteVector.fromValidHex(rs.getString("target_node_id"))) ) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala index 74343486b6..b77f237a72 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala @@ -125,11 +125,13 @@ object SqliteUtils { def getByteVector32(columnLabel: String): ByteVector32 = ByteVector32(ByteVector(rs.getBytes(columnLabel))) - def getByteVector32Nullable(columnLabel: String): Option[ByteVector32] = { + def getByteVectorNullable(columnLabel: String): Option[ByteVector] = { val bytes = rs.getBytes(columnLabel) - if(rs.wasNull()) None else Some(ByteVector32(ByteVector(bytes))) + if(rs.wasNull()) None else Some(ByteVector(bytes)) } + def getByteVector32Nullable(columnLabel: String): Option[ByteVector32] = getByteVectorNullable(columnLabel).map(ByteVector32(_)) + def getStringNullable(columnLabel: String): Option[String] = { val str = rs.getString(columnLabel) if(rs.wasNull()) None else Some(str) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala index 220468b434..7b32206649 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Autoprobe.scala @@ -53,7 +53,7 @@ class Autoprobe(nodeParams: NodeParams, router: ActorRef, paymentInitiator: Acto case Some(targetNodeId) => val paymentHash = randomBytes32 // we don't even know the preimage (this needs to be a secure random!) log.info(s"sending payment probe to node=$targetNodeId payment_hash=$paymentHash") - paymentInitiator ! SendPayment(PAYMENT_AMOUNT_MSAT, paymentHash, targetNodeId, maxAttempts = 1, paymentRequest_opt = None, customDescription_opt = None) + paymentInitiator ! SendPayment(PAYMENT_AMOUNT_MSAT, paymentHash, targetNodeId, maxAttempts = 1, paymentRequest_opt = None) case None => log.info(s"could not find a destination, re-scheduling") scheduleProbe() diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala index 472ec7e5b0..a74c50aed6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala @@ -48,14 +48,15 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis when(WAITING_FOR_REQUEST) { case Event(c: SendPaymentToRoute, WaitingForRequest) => - val send = SendPayment(c.amountMsat, c.paymentHash, c.hops.last, finalCltvExpiry = c.finalCltvExpiry, maxAttempts = 1, assistedRoutes = Seq.empty, routeParams = None, paymentRequest_opt = None, customDescription_opt = None) + val send = SendPayment(c.amountMsat, c.paymentHash, c.hops.last, finalCltvExpiry = c.finalCltvExpiry, maxAttempts = 1, assistedRoutes = Seq.empty, routeParams = None, paymentRequest_opt = None) paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, None, None, c.hops.last)) router ! FinalizeRoute(c.hops) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, send, failures = Nil) case Event(c: SendPayment, WaitingForRequest) => router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.amountMsat, c.assistedRoutes, routeParams = c.routeParams) - paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt, c.customDescription_opt, c.targetNodeId)) +// val desc: Option[String] = c.paymentRequest_opt.map(_.description.fold(_ => _, _.toHex)) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt, c.paymentRequest_opt.map(_.description.fold(s => s, _.toHex)), c.targetNodeId)) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, c, failures = Nil) } @@ -209,8 +210,7 @@ object PaymentLifecycle { finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY, maxAttempts: Int, routeParams: Option[RouteParams] = None, - paymentRequest_opt: Option[PaymentRequest] = None, - customDescription_opt: Option[String] = None) extends GenericSendPayment { + paymentRequest_opt: Option[PaymentRequest] = None) extends GenericSendPayment { require(amountMsat > 0, s"amountMsat must be > 0") } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala index 6c5caac418..21dbb727e0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala @@ -133,9 +133,9 @@ class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSu assert(send3.routeParams.get.maxFeeBaseMsat == 123 * 1000) // conversion sat -> msat assert(send3.routeParams.get.maxFeePct == 4.20) - // with paymentRequest and custom description + // with paymentRequest val pr = PaymentRequest.read("lnbc10u1pw2t4phpp5ezwm2gdccydhnphfyepklc0wjkxhz0r4tctg9paunh2lxgeqhcmsdqlxycrqvpqwdshgueqvfjhggr0dcsry7qcqzpgfa4ecv7447p9t5hkujy9qgrxvkkf396p9zar9p87rv2htmeuunkhydl40r64n5s2k0u7uelzc8twxmp37nkcch6m0wg5tvvx69yjz8qpk94qf3") - eclair.send(recipientNodeId = nodeId, amountMsat = 123, paymentHash = ByteVector32.Zeroes, assistedRoutes = Seq.empty, minFinalCltvExpiry_opt = None, feeThresholdSat_opt = Some(123), maxFeePct_opt = Some(4.20), paymentRequest_opt = Some(pr), customDescription_opt = Some("hey hey heyy")) + eclair.send(recipientNodeId = nodeId, amountMsat = 123, paymentHash = ByteVector32.Zeroes, assistedRoutes = Seq.empty, minFinalCltvExpiry_opt = None, feeThresholdSat_opt = Some(123), maxFeePct_opt = Some(4.20), paymentRequest_opt = Some(pr)) val send4 = paymentInitiator.expectMsgType[SendPayment] assert(send4.targetNodeId == nodeId) assert(send4.amountMsat == 123) @@ -143,7 +143,6 @@ class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSu assert(send4.routeParams.get.maxFeeBaseMsat == 123 * 1000) // conversion sat -> msat assert(send4.routeParams.get.maxFeePct == 4.20) assert(send4.paymentRequest_opt == Some(pr)) - assert(send4.customDescription_opt == Some("hey hey heyy")) } test("allupdates can filter by nodeId") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index b0cc59456f..5cc72674e5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -241,7 +241,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock val invoice = "lnbc12580n1pw2ywztpp554ganw404sh4yjkwnysgn3wjcxfcq7gtx53gxczkjr9nlpc3hzvqdq2wpskwctddyxqr4rqrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glc7z9rtvqqwngqqqqqqqlgqqqqqeqqjqrrt8smgjvfj7sg38dwtr9kc9gg3era9k3t2hvq3cup0jvsrtrxuplevqgfhd3rzvhulgcxj97yjuj8gdx8mllwj4wzjd8gdjhpz3lpqqvk2plh" val eclair = mock[Eclair] - eclair.send(any, any, any, any, any, any, any, any, any, any)(any[Timeout]) returns Future.successful(UUID.randomUUID()) + eclair.send(any, any, any, any, any, any, any, any, any)(any[Timeout]) returns Future.successful(UUID.randomUUID()) val mockService = new MockService(eclair) Post("/payinvoice", FormData("invoice" -> invoice).toEntity) ~> @@ -250,7 +250,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == OK) - eclair.send(any, 1258000, any, any, any, any, any, any, None, None)(any[Timeout]).wasCalled(once) + eclair.send(any, 1258000, any, any, any, any, any, any, None)(any[Timeout]).wasCalled(once) } @@ -260,7 +260,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == OK) - eclair.send(any, 123, any, any, any, any, Some(112233), Some(2.34), None, None)(any[Timeout]).wasCalled(once) + eclair.send(any, 123, any, any, any, any, Some(112233), Some(2.34), None)(any[Timeout]).wasCalled(once) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala index 3637914faf..0983712883 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala @@ -192,8 +192,8 @@ class SqlitePaymentsDbSpec extends FunSuite { val db = new SqlitePaymentsDb(TestConstants.sqliteInMemory()) - val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, paymentRequest_opt = Some(dummyPaymentRequest), targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) - val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, customDescription_opt = Some("custom description"), targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, paymentRequest_opt = Some(dummyPaymentRequest), description_opt = Some("custom description"), targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) assert(db.listOutgoingPayments().isEmpty) db.addOutgoingPayment(s1) @@ -203,7 +203,7 @@ class SqlitePaymentsDbSpec extends FunSuite { assert(db.getOutgoingPayment(s1.id) === Some(s1)) assert(db.getOutgoingPayment(s1.id).get.completedAt.isEmpty) assert(db.getOutgoingPayment(s1.id).get.paymentRequest_opt === Some(dummyPaymentRequest)) - assert(db.getOutgoingPayment(s2.id).get.customDescription_opt === Some("custom description")) + assert(db.getOutgoingPayment(s1.id).get.description_opt === Some("custom description")) assert(db.getOutgoingPayment(UUID.randomUUID()) === None) assert(db.getOutgoingPayments(s2.paymentHash) === Seq(s2)) assert(db.getOutgoingPayments(ByteVector32.Zeroes) === Seq.empty) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index 2e7fb43e0f..126fcfb945 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -384,7 +384,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { paymentFSM ! SubscribeTransitionCallBack(monitor.ref) val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]]) - val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d, paymentRequest_opt = Some(paymentRequest), customDescription_opt = Some("yolo"), maxAttempts = 5) + val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d, paymentRequest_opt = Some(paymentRequest), maxAttempts = 5) sender.send(paymentFSM, request) val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]]) val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]]) @@ -392,7 +392,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { payment.status == OutgoingPaymentStatus.PENDING && payment.targetNodeId == d && payment.paymentRequest_opt == Some(paymentRequest) && - payment.customDescription_opt == Some("yolo") + payment.description_opt == Some("1 cup coffee") }) sender.send(paymentFSM, UpdateFulfillHtlc(ByteVector32.Zeroes, 0, defaultPaymentHash)) From 7c2e8c9d6ffee1e7295ed2db62e33ba3bd54aba9 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 28 Jun 2019 13:27:26 +0200 Subject: [PATCH 09/11] Formatting, move getNullabeLong in ExtendedResultSet, reorg parameters in SendPayment --- .../main/scala/fr/acinq/eclair/Eclair.scala | 4 ++-- .../scala/fr/acinq/eclair/db/PaymentsDb.scala | 3 +-- .../eclair/db/sqlite/SqlitePaymentsDb.scala | 6 +++--- .../acinq/eclair/db/sqlite/SqliteUtils.scala | 18 +++++------------- .../eclair/payment/PaymentLifecycle.scala | 5 ++--- .../scala/fr/acinq/eclair/gui/Handlers.scala | 4 ++-- 6 files changed, 15 insertions(+), 25 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala index 1c0c0f2228..1a53978115 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -199,8 +199,8 @@ class EclairImpl(appKit: Kit) extends Eclair { ) val sendPayment = minFinalCltvExpiry_opt match { - case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt) - case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts, routeParams = Some(routeParams), paymentRequest_opt = paymentRequest_opt) + case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, paymentRequest_opt, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts, routeParams = Some(routeParams)) + case None => SendPayment(amountMsat, paymentHash, recipientNodeId, paymentRequest_opt, assistedRoutes, maxAttempts = maxAttempts, routeParams = Some(routeParams)) } (appKit.paymentInitiator ? sendPayment).mapTo[UUID] } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index 37d73fa257..331ef049dd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -91,8 +91,7 @@ case class OutgoingPayment( status: OutgoingPaymentStatus.Value, paymentRequest_opt: Option[PaymentRequest] = None, description_opt: Option[String] = None, - targetNodeId: PublicKey - ) + targetNodeId: PublicKey) object OutgoingPaymentStatus extends Enumeration { val PENDING = Value(1, "PENDING") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala index 1f7c5887f0..48631dfb6d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala @@ -105,7 +105,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getByteVector32Nullable("preimage"), rs.getLong("amount_msat"), rs.getLong("created_at"), - getNullableLong(rs, "completed_at"), + rs.getNullableLong("completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), rs.getStringNullable("payment_request").map(PaymentRequest.read), rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)), @@ -129,7 +129,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getByteVector32Nullable("preimage"), rs.getLong("amount_msat"), rs.getLong("created_at"), - getNullableLong(rs, "completed_at"), + rs.getNullableLong("completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), rs.getStringNullable("payment_request").map(PaymentRequest.read), rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)), @@ -151,7 +151,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getByteVector32Nullable("preimage"), rs.getLong("amount_msat"), rs.getLong("created_at"), - getNullableLong(rs, "completed_at"), + rs.getNullableLong("completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), rs.getStringNullable("payment_request").map(PaymentRequest.read), rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala index b77f237a72..2e8463d833 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala @@ -90,19 +90,6 @@ object SqliteUtils { q } - /** - * This helper retrieves the value from a nullable integer column and interprets it as an option. This is needed - * because `rs.getLong` would return `0` for a null value. - * It is used on Android only - * - * @param label - * @return - */ - def getNullableLong(rs: ResultSet, label: String) : Option[Long] = { - val result = rs.getLong(label) - if (rs.wasNull()) None else Some(result) - } - /** * Obtain an exclusive lock on a sqlite database. This is useful when we want to make sure that only one process * accesses the database file (see https://www.sqlite.org/pragma.html). @@ -136,6 +123,11 @@ object SqliteUtils { val str = rs.getString(columnLabel) if(rs.wasNull()) None else Some(str) } + + def getNullableLong(label: String) : Option[Long] = { + val result = rs.getLong(label) + if (rs.wasNull()) None else Some(result) + } } object ExtendedResultSet { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala index a74c50aed6..f08931b500 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala @@ -55,7 +55,6 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis case Event(c: SendPayment, WaitingForRequest) => router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.amountMsat, c.assistedRoutes, routeParams = c.routeParams) -// val desc: Option[String] = c.paymentRequest_opt.map(_.description.fold(_ => _, _.toHex)) paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt, c.paymentRequest_opt.map(_.description.fold(s => s, _.toHex)), c.targetNodeId)) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, c, failures = Nil) } @@ -206,11 +205,11 @@ object PaymentLifecycle { case class SendPayment(amountMsat: Long, paymentHash: ByteVector32, targetNodeId: PublicKey, + paymentRequest_opt: Option[PaymentRequest] = None, assistedRoutes: Seq[Seq[ExtraHop]] = Nil, finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY, maxAttempts: Int, - routeParams: Option[RouteParams] = None, - paymentRequest_opt: Option[PaymentRequest] = None) extends GenericSendPayment { + routeParams: Option[RouteParams] = None) extends GenericSendPayment { require(amountMsat > 0, s"amountMsat must be > 0") } diff --git a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/Handlers.scala b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/Handlers.scala index 80e286051b..c6002fc7bf 100644 --- a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/Handlers.scala +++ b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/Handlers.scala @@ -88,8 +88,8 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte (for { kit <- fKit sendPayment = req.minFinalCltvExpiry match { - case None => SendPayment(amountMsat, req.paymentHash, req.nodeId, req.routingInfo, maxAttempts = kit.nodeParams.maxPaymentAttempts) - case Some(minFinalCltvExpiry) => SendPayment(amountMsat, req.paymentHash, req.nodeId, req.routingInfo, finalCltvExpiry = minFinalCltvExpiry, maxAttempts = kit.nodeParams.maxPaymentAttempts) + case None => SendPayment(amountMsat, req.paymentHash, req.nodeId, assistedRoutes = req.routingInfo, maxAttempts = kit.nodeParams.maxPaymentAttempts) + case Some(minFinalCltvExpiry) => SendPayment(amountMsat, req.paymentHash, req.nodeId, assistedRoutes = req.routingInfo, finalCltvExpiry = minFinalCltvExpiry, maxAttempts = kit.nodeParams.maxPaymentAttempts) } res <- (kit.paymentInitiator ? sendPayment).mapTo[UUID] } yield res).recover { From 6c71ea23139a5fcbbf577f54627ee287a3910545 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 28 Jun 2019 14:54:48 +0200 Subject: [PATCH 10/11] Make OutgoingPayment.targetNodeId optional --- .../scala/fr/acinq/eclair/db/PaymentsDb.scala | 6 +++--- .../eclair/db/sqlite/SqlitePaymentsDb.scala | 19 +++++++++---------- .../eclair/payment/PaymentLifecycle.scala | 4 ++-- .../eclair/db/SqlitePaymentsDbSpec.scala | 11 ++++++----- .../eclair/payment/PaymentLifecycleSpec.scala | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index 331ef049dd..dba1bc7431 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -73,25 +73,25 @@ case class IncomingPayment(paymentHash: ByteVector32, amountMsat: Long, received * @param id internal payment identifier * @param paymentHash payment_hash * @param preimage the preimage of the payment_hash, known if the outgoing payment was successful + * @param targetNodeId the recipient of this payment * @param amountMsat amount of the payment, in milli-satoshis * @param createdAt absolute time in seconds since UNIX epoch when the payment was created. * @param completedAt absolute time in seconds since UNIX epoch when the payment succeeded. * @param status current status of the payment. * @param paymentRequest_opt the payment request that was associated with this payment * @param description_opt a custom description - * @param targetNodeId the recipient of this payment */ case class OutgoingPayment( id: UUID, paymentHash: ByteVector32, preimage:Option[ByteVector32], + targetNodeId: Option[PublicKey], amountMsat: Long, createdAt: Long, completedAt: Option[Long], status: OutgoingPaymentStatus.Value, paymentRequest_opt: Option[PaymentRequest] = None, - description_opt: Option[String] = None, - targetNodeId: PublicKey) + description_opt: Option[String] = None) object OutgoingPaymentStatus extends Enumeration { val PENDING = Value(1, "PENDING") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala index 48631dfb6d..7c0dd00523 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala @@ -26,7 +26,6 @@ import grizzled.slf4j.Logging import scala.collection.immutable.Queue import OutgoingPaymentStatus._ import fr.acinq.bitcoin.Crypto.PublicKey -import scodec.bits.ByteVector import concurrent.duration._ import scala.compat.Platform @@ -46,7 +45,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { def migration23(statement: Statement) = { statement.executeUpdate("ALTER TABLE sent_payments ADD payment_request TEXT") statement.executeUpdate("ALTER TABLE sent_payments ADD description BLOB") - statement.executeUpdate(s"ALTER TABLE sent_payments ADD target_node_id BLOB DEFAULT '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f' NOT NULL") + statement.executeUpdate(s"ALTER TABLE sent_payments ADD target_node_id BLOB") } using(sqlite.createStatement()) { statement => @@ -62,7 +61,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { setVersion(statement, DB_NAME, CURRENT_VERSION) case CURRENT_VERSION => statement.executeUpdate("CREATE TABLE IF NOT EXISTS received_payments (payment_hash BLOB NOT NULL PRIMARY KEY, preimage BLOB NOT NULL, payment_request TEXT NOT NULL, received_msat INTEGER, created_at INTEGER NOT NULL, expire_at INTEGER, received_at INTEGER)") - statement.executeUpdate("CREATE TABLE IF NOT EXISTS sent_payments (id TEXT NOT NULL PRIMARY KEY, payment_hash BLOB NOT NULL, preimage BLOB, amount_msat INTEGER NOT NULL, created_at INTEGER NOT NULL, completed_at INTEGER, status VARCHAR NOT NULL, payment_request TEXT, description BLOB, target_node_id BLOB NOT NULL)") + statement.executeUpdate("CREATE TABLE IF NOT EXISTS sent_payments (id TEXT NOT NULL PRIMARY KEY, payment_hash BLOB NOT NULL, preimage BLOB, amount_msat INTEGER NOT NULL, created_at INTEGER NOT NULL, completed_at INTEGER, status VARCHAR NOT NULL, payment_request TEXT, description BLOB, target_node_id BLOB)") statement.executeUpdate("CREATE INDEX IF NOT EXISTS payment_hash_idx ON sent_payments(payment_hash)") setVersion(statement, DB_NAME, CURRENT_VERSION) } @@ -77,7 +76,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { statement.setString(5, sent.status.toString) statement.setString(6, sent.paymentRequest_opt.map(PaymentRequest.write).orNull) statement.setBytes(7, sent.description_opt.map(_.getBytes).orNull) - statement.setBytes(8, sent.targetNodeId.value.toHex.getBytes) + statement.setBytes(8, sent.targetNodeId.map(_.value.toArray).orNull) statement.executeUpdate() } } @@ -103,13 +102,13 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { UUID.fromString(rs.getString("id")), rs.getByteVector32("payment_hash"), rs.getByteVector32Nullable("preimage"), + rs.getByteVectorNullable("target_node_id").map(PublicKey(_)), rs.getLong("amount_msat"), rs.getLong("created_at"), rs.getNullableLong("completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), rs.getStringNullable("payment_request").map(PaymentRequest.read), - rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)), - PublicKey(ByteVector.fromValidHex(rs.getString("target_node_id"))) + rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)) )) } else { None @@ -127,13 +126,13 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { UUID.fromString(rs.getString("id")), rs.getByteVector32("payment_hash"), rs.getByteVector32Nullable("preimage"), + rs.getByteVectorNullable("target_node_id").map(PublicKey(_)), rs.getLong("amount_msat"), rs.getLong("created_at"), rs.getNullableLong("completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), rs.getStringNullable("payment_request").map(PaymentRequest.read), - rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)), - PublicKey(ByteVector.fromValidHex(rs.getString("target_node_id"))) + rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)) ) } q @@ -149,13 +148,13 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { UUID.fromString(rs.getString("id")), rs.getByteVector32("payment_hash"), rs.getByteVector32Nullable("preimage"), + rs.getByteVectorNullable("target_node_id").map(PublicKey(_)), rs.getLong("amount_msat"), rs.getLong("created_at"), rs.getNullableLong("completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), rs.getStringNullable("payment_request").map(PaymentRequest.read), - rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)), - PublicKey(ByteVector.fromValidHex(rs.getString("target_node_id"))) + rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)) ) } q diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala index f08931b500..8493b51787 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala @@ -49,13 +49,13 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis when(WAITING_FOR_REQUEST) { case Event(c: SendPaymentToRoute, WaitingForRequest) => val send = SendPayment(c.amountMsat, c.paymentHash, c.hops.last, finalCltvExpiry = c.finalCltvExpiry, maxAttempts = 1, assistedRoutes = Seq.empty, routeParams = None, paymentRequest_opt = None) - paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, None, None, c.hops.last)) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, Some(c.hops.last), c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, None, None)) router ! FinalizeRoute(c.hops) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, send, failures = Nil) case Event(c: SendPayment, WaitingForRequest) => router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.amountMsat, c.assistedRoutes, routeParams = c.routeParams) - paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt, c.paymentRequest_opt.map(_.description.fold(s => s, _.toHex)), c.targetNodeId)) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, Some(c.targetNodeId), c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt, c.paymentRequest_opt.map(_.description.fold(s => s, _.toHex)))) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, c, failures = Nil) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala index 0983712883..a6cbebb541 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala @@ -79,7 +79,7 @@ class SqlitePaymentsDbSpec extends FunSuite { assert(preMigrationDb.getIncomingPayment(oldReceivedPayment.paymentHash).isEmpty) // add a few rows - val ps1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val ps1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, targetNodeId = Some(PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")), amountMsat = 12345, createdAt = 12345, None, PENDING) val i1 = PaymentRequest.read("lnbc10u1pw2t4phpp5ezwm2gdccydhnphfyepklc0wjkxhz0r4tctg9paunh2lxgeqhcmsdqlxycrqvpqwdshgueqvfjhggr0dcsry7qcqzpgfa4ecv7447p9t5hkujy9qgrxvkkf396p9zar9p87rv2htmeuunkhydl40r64n5s2k0u7uelzc8twxmp37nkcch6m0wg5tvvx69yjz8qpk94qf3") val pr1 = IncomingPayment(i1.paymentHash, 12345678, 1513871928275L) @@ -139,10 +139,10 @@ class SqlitePaymentsDbSpec extends FunSuite { } // check the old record has been migrated - assert(preMigrationDb.getOutgoingPayment(UNKNOWN_UUID).get.targetNodeId == PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) + assert(preMigrationDb.getOutgoingPayment(UNKNOWN_UUID).get.targetNodeId.isEmpty) // add a few rows - val ps1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val ps1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, targetNodeId = Some(PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")), amountMsat = 12345, createdAt = 12345, None, PENDING) val i1 = PaymentRequest.read("lnbc10u1pw2t4phpp5ezwm2gdccydhnphfyepklc0wjkxhz0r4tctg9paunh2lxgeqhcmsdqlxycrqvpqwdshgueqvfjhggr0dcsry7qcqzpgfa4ecv7447p9t5hkujy9qgrxvkkf396p9zar9p87rv2htmeuunkhydl40r64n5s2k0u7uelzc8twxmp37nkcch6m0wg5tvvx69yjz8qpk94qf3") val pr1 = IncomingPayment(i1.paymentHash, 12345678, 1513871928275L) @@ -192,8 +192,8 @@ class SqlitePaymentsDbSpec extends FunSuite { val db = new SqlitePaymentsDb(TestConstants.sqliteInMemory()) - val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, paymentRequest_opt = Some(dummyPaymentRequest), description_opt = Some("custom description"), targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) - val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, targetNodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) + val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, targetNodeId = Some(PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")), amountMsat = 12345, createdAt = 12345, None, PENDING, paymentRequest_opt = Some(dummyPaymentRequest), description_opt = Some("custom description")) + val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, targetNodeId = Some(PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")), amountMsat = 12345, createdAt = 12345, None, PENDING) assert(db.listOutgoingPayments().isEmpty) db.addOutgoingPayment(s1) @@ -204,6 +204,7 @@ class SqlitePaymentsDbSpec extends FunSuite { assert(db.getOutgoingPayment(s1.id).get.completedAt.isEmpty) assert(db.getOutgoingPayment(s1.id).get.paymentRequest_opt === Some(dummyPaymentRequest)) assert(db.getOutgoingPayment(s1.id).get.description_opt === Some("custom description")) + assert(db.getOutgoingPayment(s1.id).get.targetNodeId === Some(PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87"))) assert(db.getOutgoingPayment(UUID.randomUUID()) === None) assert(db.getOutgoingPayments(s2.paymentHash) === Seq(s2)) assert(db.getOutgoingPayments(ByteVector32.Zeroes) === Seq.empty) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index 126fcfb945..53af32e106 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -67,7 +67,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]]) val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]]) awaitCond(paymentDb.getOutgoingPayment(id).exists(_.status == OutgoingPaymentStatus.PENDING)) - awaitCond(paymentDb.getOutgoingPayment(id).exists(_.targetNodeId == d)) + awaitCond(paymentDb.getOutgoingPayment(id).exists(_.targetNodeId == Some(d))) sender.send(paymentFSM, UpdateFulfillHtlc(ByteVector32.Zeroes, 0, defaultPaymentHash)) sender.expectMsgType[PaymentSucceeded] @@ -390,7 +390,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]]) awaitCond(paymentDb.getOutgoingPayment(id).exists { payment => payment.status == OutgoingPaymentStatus.PENDING && - payment.targetNodeId == d && + payment.targetNodeId == Some(d) && payment.paymentRequest_opt == Some(paymentRequest) && payment.description_opt == Some("1 cup coffee") }) From 738216a57defbc673b2c45ba89a39b23b943fa0a Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 28 Jun 2019 16:48:26 +0200 Subject: [PATCH 11/11] Store the payment request serialized in OutgoingPayment --- .../src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala | 4 ++-- .../fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala | 8 ++++---- .../scala/fr/acinq/eclair/payment/PaymentLifecycle.scala | 2 +- .../scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala | 2 +- .../fr/acinq/eclair/payment/PaymentLifecycleSpec.scala | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index dba1bc7431..aaca372741 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -78,7 +78,7 @@ case class IncomingPayment(paymentHash: ByteVector32, amountMsat: Long, received * @param createdAt absolute time in seconds since UNIX epoch when the payment was created. * @param completedAt absolute time in seconds since UNIX epoch when the payment succeeded. * @param status current status of the payment. - * @param paymentRequest_opt the payment request that was associated with this payment + * @param paymentRequest_opt the payment request (serialized) that was associated with this payment * @param description_opt a custom description */ case class OutgoingPayment( @@ -90,7 +90,7 @@ case class OutgoingPayment( createdAt: Long, completedAt: Option[Long], status: OutgoingPaymentStatus.Value, - paymentRequest_opt: Option[PaymentRequest] = None, + paymentRequest_opt: Option[String] = None, description_opt: Option[String] = None) object OutgoingPaymentStatus extends Enumeration { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala index 7c0dd00523..7d318ac509 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala @@ -74,7 +74,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { statement.setLong(3, sent.amountMsat) statement.setLong(4, sent.createdAt) statement.setString(5, sent.status.toString) - statement.setString(6, sent.paymentRequest_opt.map(PaymentRequest.write).orNull) + statement.setString(6, sent.paymentRequest_opt.orNull) statement.setBytes(7, sent.description_opt.map(_.getBytes).orNull) statement.setBytes(8, sent.targetNodeId.map(_.value.toArray).orNull) statement.executeUpdate() @@ -107,7 +107,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getLong("created_at"), rs.getNullableLong("completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), - rs.getStringNullable("payment_request").map(PaymentRequest.read), + rs.getStringNullable("payment_request"), rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)) )) } else { @@ -131,7 +131,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getLong("created_at"), rs.getNullableLong("completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), - rs.getStringNullable("payment_request").map(PaymentRequest.read), + rs.getStringNullable("payment_request"), rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)) ) } @@ -153,7 +153,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getLong("created_at"), rs.getNullableLong("completed_at"), OutgoingPaymentStatus.withName(rs.getString("status")), - rs.getStringNullable("payment_request").map(PaymentRequest.read), + rs.getStringNullable("payment_request"), rs.getByteVectorNullable("description").map(bytes => new String(bytes.toArray)) ) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala index 8493b51787..b04d3b257c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala @@ -55,7 +55,7 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis case Event(c: SendPayment, WaitingForRequest) => router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.amountMsat, c.assistedRoutes, routeParams = c.routeParams) - paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, Some(c.targetNodeId), c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt, c.paymentRequest_opt.map(_.description.fold(s => s, _.toHex)))) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, Some(c.targetNodeId), c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, c.paymentRequest_opt.map(PaymentRequest.write), c.paymentRequest_opt.map(_.description.fold(s => s, _.toHex)))) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, c, failures = Nil) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala index a6cbebb541..6ec537809c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala @@ -37,7 +37,7 @@ import concurrent.duration._ class SqlitePaymentsDbSpec extends FunSuite { - lazy val dummyPaymentRequest = PaymentRequest.read("lnbc5450n1pw2t4qdpp5vcrf6ylgpettyng4ac3vujsk0zpc25cj0q3zp7l7w44zvxmpzh8qdzz2pshjmt9de6zqen0wgsr2dp4ypcxj7r9d3ejqct5ypekzar0wd5xjuewwpkxzcm99cxqzjccqp2rzjqtspxelp67qc5l56p6999wkatsexzhs826xmupyhk6j8lxl038t27z9tsqqqgpgqqqqqqqlgqqqqqzsqpcz8z8hmy8g3ecunle4n3edn3zg2rly8g4klsk5md736vaqqy3ktxs30ht34rkfkqaffzxmjphvd0637dk2lp6skah2hq09z6lrjna3xqp3d4vyd") + lazy val dummyPaymentRequest = "lnbc5450n1pw2t4qdpp5vcrf6ylgpettyng4ac3vujsk0zpc25cj0q3zp7l7w44zvxmpzh8qdzz2pshjmt9de6zqen0wgsr2dp4ypcxj7r9d3ejqct5ypekzar0wd5xjuewwpkxzcm99cxqzjccqp2rzjqtspxelp67qc5l56p6999wkatsexzhs826xmupyhk6j8lxl038t27z9tsqqqgpgqqqqqqqlgqqqqqzsqpcz8z8hmy8g3ecunle4n3edn3zg2rly8g4klsk5md736vaqqy3ktxs30ht34rkfkqaffzxmjphvd0637dk2lp6skah2hq09z6lrjna3xqp3d4vyd" test("init sqlite 2 times in a row") { val sqlite = TestConstants.sqliteInMemory() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index 53af32e106..4435a43036 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -391,7 +391,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { awaitCond(paymentDb.getOutgoingPayment(id).exists { payment => payment.status == OutgoingPaymentStatus.PENDING && payment.targetNodeId == Some(d) && - payment.paymentRequest_opt == Some(paymentRequest) && + payment.paymentRequest_opt == Some("lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp") && payment.description_opt == Some("1 cup coffee") }) sender.send(paymentFSM, UpdateFulfillHtlc(ByteVector32.Zeroes, 0, defaultPaymentHash))