From 0d350ce534d159e7cf9351ca36722e424b65a240 Mon Sep 17 00:00:00 2001 From: rorp Date: Sun, 14 Jul 2019 15:27:50 -0700 Subject: [PATCH 1/5] Store failure messages for outgoing payments --- .../scala/fr/acinq/eclair/db/PaymentsDb.scala | 4 +- .../eclair/db/sqlite/SqlitePaymentsDb.scala | 57 +++++++++++++++---- .../eclair/payment/PaymentLifecycle.scala | 44 +++++++++----- .../fr/acinq/eclair/payment/Relayer.scala | 6 +- .../eclair/db/SqlitePaymentsDbSpec.scala | 16 +++--- 5 files changed, 90 insertions(+), 37 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 b13e190446..8e1147c342 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 @@ -26,7 +26,7 @@ trait PaymentsDb { def addOutgoingPayment(outgoingPayment: OutgoingPayment) // updates the status of the payment, if the newStatus is SUCCEEDED you must supply a preimage - def updateOutgoingPayment(id: UUID, newStatus: OutgoingPaymentStatus.Value, preimage: Option[ByteVector32] = None) + def updateOutgoingPayment(id: UUID, newStatus: OutgoingPaymentStatus.Value, preimage: Option[ByteVector32] = None, failures: Traversable[String] = Seq()) def getOutgoingPayment(id: UUID): Option[OutgoingPayment] @@ -77,7 +77,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. */ -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, failures: Seq[String]) 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..d06823c7b0 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 @@ -18,14 +18,18 @@ package fr.acinq.eclair.db.sqlite import java.sql.Connection import java.util.UUID + import fr.acinq.bitcoin.ByteVector32 import fr.acinq.eclair.db.sqlite.SqliteUtils._ import fr.acinq.eclair.db.{IncomingPayment, OutgoingPayment, OutgoingPaymentStatus, PaymentsDb} import fr.acinq.eclair.payment.PaymentRequest import grizzled.slf4j.Logging + import scala.collection.immutable.Queue import OutgoingPaymentStatus._ + import concurrent.duration._ +import scala.collection.mutable import scala.compat.Platform class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { @@ -33,12 +37,13 @@ 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 + require(getVersion(statement, DB_NAME, CURRENT_VERSION) <= CURRENT_VERSION, s"incompatible version of $DB_NAME DB found") // version 3 is "backward compatible" in the sense that it uses separate tables from versions 1 and 2. There is no migration though 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 TABLE IF NOT EXISTS sent_payments_failures (id TEXT NOT NULL, failure VARCHAR NOT NULL, PRIMARY KEY (id, failure))") statement.executeUpdate("CREATE INDEX IF NOT EXISTS payment_hash_idx ON sent_payments(payment_hash)") setVersion(statement, DB_NAME, CURRENT_VERSION) } @@ -53,10 +58,12 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { val res = statement.executeUpdate() logger.debug(s"inserted $res payment=${sent.paymentHash} into payment DB") } + insertFailures(sent.id, sent.failures) } - override def updateOutgoingPayment(id: UUID, newStatus: OutgoingPaymentStatus.Value, preimage: Option[ByteVector32] = None) = { + override def updateOutgoingPayment(id: UUID, newStatus: OutgoingPaymentStatus.Value, preimage: Option[ByteVector32] = None, failures: Traversable[String] = Seq()): Unit = { require((newStatus == SUCCEEDED && preimage.isDefined) || (newStatus == FAILED && preimage.isEmpty), "Wrong combination of state/preimage") + require((newStatus == SUCCEEDED && failures.isEmpty) || (newStatus == FAILED && failures.nonEmpty), "Wrong combination of state/failures") using(sqlite.prepareStatement("UPDATE sent_payments SET (completed_at, preimage, status) = (?, ?, ?) WHERE id = ? AND completed_at IS NULL")) { statement => statement.setLong(1, Platform.currentTime) @@ -65,30 +72,34 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { statement.setString(4, id.toString) if (statement.executeUpdate() == 0) throw new IllegalArgumentException(s"Tried to update an outgoing payment (id=$id) already in final status with=$newStatus") } + insertFailures(id, failures) } 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 => + val res = using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status FROM sent_payments WHERE id = ?")) { statement => statement.setString(1, id.toString) val rs = statement.executeQuery() if (rs.next()) { + val id = UUID.fromString(rs.getString("id")) Some(OutgoingPayment( - UUID.fromString(rs.getString("id")), + id, rs.getByteVector32("payment_hash"), rs.getByteVector32Nullable("preimage"), rs.getLong("amount_msat"), rs.getLong("created_at"), getNullableLong(rs, "completed_at"), - OutgoingPaymentStatus.withName(rs.getString("status")) + OutgoingPaymentStatus.withName(rs.getString("status")), + Nil )) } else { None } } + res.map(op => op.copy(failures = selectFailures(op.id))) } 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 => + val res = using(sqlite.prepareStatement("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status FROM sent_payments WHERE payment_hash = ?")) { statement => statement.setBytes(1, paymentHash.toArray) val rs = statement.executeQuery() var q: Queue[OutgoingPayment] = Queue() @@ -100,15 +111,17 @@ 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")), + Nil ) } q } + res.map(op => op.copy(failures = selectFailures(op.id))) } override def listOutgoingPayments(): Seq[OutgoingPayment] = { - using(sqlite.createStatement()) { statement => + val res = using(sqlite.createStatement()) { statement => val rs = statement.executeQuery("SELECT id, payment_hash, preimage, amount_msat, created_at, completed_at, status FROM sent_payments") var q: Queue[OutgoingPayment] = Queue() while (rs.next()) { @@ -119,11 +132,13 @@ 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")), + Nil ) } q } + res.map(op => op.copy(failures = selectFailures(op.id))) } override def addPaymentRequest(pr: PaymentRequest, preimage: ByteVector32): Unit = { @@ -225,4 +240,26 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { } } + private def insertFailures(id: UUID, failures: Traversable[String]): Unit = { + failures.filter(_.nonEmpty).foreach { failure => + using(sqlite.prepareStatement("INSERT INTO sent_payments_failures (id, failure) VALUES (?, ?)")) { statement => + statement.setString(1, id.toString) + statement.setString(2, failure) + val res = statement.executeUpdate() + } + } + } + + private def selectFailures(id: UUID): Seq[String] = { + val res = new mutable.ListBuffer[String] + using(sqlite.prepareStatement("SELECT failure FROM sent_payments_failures WHERE id = ?")) { statement => + statement.setString(1, id.toString) + val rs = statement.executeQuery() + while (rs.next()) { + res += rs.getString(1) + } + } + res.toList + } + } \ No newline at end of file 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..5a61646f02 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) - 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, Seq())) 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, Seq())) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, c, failures = Nil) } @@ -71,8 +71,9 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis goto(WAITING_FOR_PAYMENT_COMPLETE) using WaitingForComplete(s, c, cmd, failures, sharedSecrets, ignoreNodes, ignoreChannels, hops) case Event(Status.Failure(t), WaitingForRoute(s, c, failures)) => - reply(s, PaymentFailed(id, c.paymentHash, failures = failures :+ LocalFailure(t))) - paymentsDb.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED) + val paymentFailed = PaymentFailed(id, c.paymentHash, failures = failures :+ LocalFailure(t)) + reply(s, paymentFailed) + paymentsDb.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = paymentFailed.errorMessages) stop(FSM.Normal) } @@ -90,8 +91,9 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis case Success(e@ErrorPacket(nodeId, failureMessage)) if nodeId == c.targetNodeId => // if destination node returns an error, we fail the payment immediately log.warning(s"received an error message from target nodeId=$nodeId, failing the payment (failure=$failureMessage)") - reply(s, PaymentFailed(id, c.paymentHash, failures = failures :+ RemoteFailure(hops, e))) - paymentsDb.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED) + val paymentFailed = PaymentFailed(id, c.paymentHash, failures = failures :+ RemoteFailure(hops, e)) + reply(s, paymentFailed) + paymentsDb.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = paymentFailed.errorMessages) stop(FSM.Normal) case res if failures.size + 1 >= c.maxAttempts => // otherwise we never try more than maxAttempts, no matter the kind of error returned @@ -104,8 +106,9 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis UnreadableRemoteFailure(hops) } log.warning(s"too many failed attempts, failing the payment") - reply(s, PaymentFailed(id, c.paymentHash, failures = failures :+ failure)) - paymentsDb.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED) + val paymentFailed = PaymentFailed(id, c.paymentHash, failures = failures :+ failure) + reply(s, paymentFailed) + paymentsDb.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = paymentFailed.errorMessages) stop(FSM.Normal) case Failure(t) => log.warning(s"cannot parse returned error: ${t.getMessage}") @@ -170,8 +173,9 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis case Event(Status.Failure(t), WaitingForComplete(s, c, _, failures, _, ignoreNodes, ignoreChannels, hops)) => if (failures.size + 1 >= c.maxAttempts) { - paymentsDb.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED) - reply(s, PaymentFailed(id, c.paymentHash, failures :+ LocalFailure(t))) + val paymentFailed = PaymentFailed(id, c.paymentHash, failures :+ LocalFailure(t)) + paymentsDb.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = paymentFailed.errorMessages) + reply(s, paymentFailed) stop(FSM.Normal) } else { log.info(s"received an error message from local, trying to use a different channel (failure=${t.getMessage})") @@ -214,11 +218,21 @@ object PaymentLifecycle { sealed trait PaymentResult case class PaymentSucceeded(id: UUID, amountMsat: Long, paymentHash: ByteVector32, paymentPreimage: ByteVector32, route: Seq[Hop]) extends PaymentResult // note: the amount includes fees - sealed trait PaymentFailure - case class LocalFailure(t: Throwable) extends PaymentFailure - case class RemoteFailure(route: Seq[Hop], e: ErrorPacket) extends PaymentFailure - case class UnreadableRemoteFailure(route: Seq[Hop]) extends PaymentFailure - case class PaymentFailed(id: UUID, paymentHash: ByteVector32, failures: Seq[PaymentFailure]) extends PaymentResult + sealed trait PaymentFailure { + def errorMessage: String + } + case class LocalFailure(t: Throwable) extends PaymentFailure { + override def errorMessage: String = t.getMessage + } + case class RemoteFailure(route: Seq[Hop], e: ErrorPacket) extends PaymentFailure { + override def errorMessage: String = e.failureMessage.message + } + case class UnreadableRemoteFailure(route: Seq[Hop]) extends PaymentFailure { + override def errorMessage: String = "unreadable remote failure" + } + case class PaymentFailed(id: UUID, paymentHash: ByteVector32, failures: Seq[PaymentFailure]) extends PaymentResult { + def errorMessages: Seq[String] = failures.map(_.errorMessage).filter(_.nonEmpty) + } sealed trait Data case object WaitingForRequest extends Data diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala index 1a75ee2083..fb014aaa8f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala @@ -137,7 +137,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR case Local(id, None) => // we sent the payment, but we probably restarted and the reference to the original sender was lost, // we publish the failure on the event stream and update the status in paymentDb - nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED) + nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = Nil) context.system.eventStream.publish(PaymentFailed(id, paymentHash, Nil)) case Local(_, Some(sender)) => sender ! Status.Failure(addFailed) @@ -177,7 +177,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR case Local(id, None) => // we sent the payment, but we probably restarted and the reference to the original sender was lost // we publish the failure on the event stream and update the status in paymentDb - nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED) + nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = Nil) context.system.eventStream.publish(PaymentFailed(id, add.paymentHash, Nil)) case Local(_, Some(sender)) => sender ! fail @@ -191,7 +191,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR case Local(id, None) => // we sent the payment, but we probably restarted and the reference to the original sender was lost // we publish the failure on the event stream and update the status in paymentDb - nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED) + nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = Nil) context.system.eventStream.publish(PaymentFailed(id, add.paymentHash, Nil)) case Local(_, Some(sender)) => sender ! fail 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..aadef7e846 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 @@ -36,9 +36,10 @@ class SqlitePaymentsDbSpec extends FunSuite { val sqlite = TestConstants.sqliteInMemory() val db1 = new SqlitePaymentsDb(sqlite) val db2 = new SqlitePaymentsDb(sqlite) + val db3 = new SqlitePaymentsDb(sqlite) } - test("handle version migration 1->2") { + test("handle version migration 1->2->3") { val connection = TestConstants.sqliteInMemory() @@ -64,14 +65,14 @@ 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 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, Seq()) val i1 = PaymentRequest.read("lnbc10u1pw2t4phpp5ezwm2gdccydhnphfyepklc0wjkxhz0r4tctg9paunh2lxgeqhcmsdqlxycrqvpqwdshgueqvfjhggr0dcsry7qcqzpgfa4ecv7447p9t5hkujy9qgrxvkkf396p9zar9p87rv2htmeuunkhydl40r64n5s2k0u7uelzc8twxmp37nkcch6m0wg5tvvx69yjz8qpk94qf3") val pr1 = IncomingPayment(i1.paymentHash, 12345678, 1513871928275L) @@ -86,7 +87,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", 2) == 3) // version still to 2 } assert(postMigrationDb.listIncomingPayments() == Seq(pr1)) @@ -121,8 +122,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, Seq()) + val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, Seq()) assert(db.listOutgoingPayments().isEmpty) db.addOutgoingPayment(s1) @@ -138,10 +139,11 @@ class SqlitePaymentsDbSpec extends FunSuite { val s3 = s2.copy(id = UUID.randomUUID(), amountMsat = 88776655) db.addOutgoingPayment(s3) - db.updateOutgoingPayment(s3.id, FAILED) + db.updateOutgoingPayment(s3.id, FAILED, failures = Seq("one", "two", "three")) assert(db.getOutgoingPayment(s3.id).get.status == FAILED) assert(db.getOutgoingPayment(s3.id).get.preimage.isEmpty) // failed sent payments don't have a preimage assert(db.getOutgoingPayment(s3.id).get.completedAt.isDefined) + assert(db.getOutgoingPayment(s3.id).get.failures.toSet == Set("one", "two", "three")) // can't update again once it's in a final state assertThrows[IllegalArgumentException](db.updateOutgoingPayment(s3.id, SUCCEEDED)) From a2b1a489b204fe5eb03714841f4d26a5cd1dcb02 Mon Sep 17 00:00:00 2001 From: rorp Date: Sun, 14 Jul 2019 15:43:29 -0700 Subject: [PATCH 2/5] tweak the schema --- .../scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 d06823c7b0..668d954c01 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 @@ -43,7 +43,8 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { require(getVersion(statement, DB_NAME, CURRENT_VERSION) <= CURRENT_VERSION, s"incompatible version of $DB_NAME DB found") // version 3 is "backward compatible" in the sense that it uses separate tables from versions 1 and 2. There is no migration though 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 TABLE IF NOT EXISTS sent_payments_failures (id TEXT NOT NULL, failure VARCHAR NOT NULL, PRIMARY KEY (id, failure))") + statement.executeUpdate("CREATE TABLE IF NOT EXISTS sent_payments_failures (id TEXT NOT NULL, failure TEXT NOT NULL)") + statement.executeUpdate("CREATE INDEX IF NOT EXISTS sent_payments_failures_idx ON sent_payments_failures(id)") statement.executeUpdate("CREATE INDEX IF NOT EXISTS payment_hash_idx ON sent_payments(payment_hash)") setVersion(statement, DB_NAME, CURRENT_VERSION) } From 51a9e2367c417b147f65907e15f679998cb63005 Mon Sep 17 00:00:00 2001 From: rorp Date: Sat, 20 Jul 2019 08:27:37 +0200 Subject: [PATCH 3/5] addressed PR comments --- .../src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala | 2 +- .../fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala | 10 +++------- .../fr/acinq/eclair/payment/PaymentLifecycle.scala | 4 ++-- .../main/scala/fr/acinq/eclair/payment/Relayer.scala | 4 ++-- .../fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala | 4 ++-- 5 files changed, 10 insertions(+), 14 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 8e1147c342..4ef7c9d5e1 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 @@ -26,7 +26,7 @@ trait PaymentsDb { def addOutgoingPayment(outgoingPayment: OutgoingPayment) // updates the status of the payment, if the newStatus is SUCCEEDED you must supply a preimage - def updateOutgoingPayment(id: UUID, newStatus: OutgoingPaymentStatus.Value, preimage: Option[ByteVector32] = None, failures: Traversable[String] = Seq()) + def updateOutgoingPayment(id: UUID, newStatus: OutgoingPaymentStatus.Value, preimage: Option[ByteVector32] = None, failures: Seq[String] = Seq.empty) def getOutgoingPayment(id: UUID): Option[OutgoingPayment] 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 668d954c01..a4f5be4094 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 @@ -18,16 +18,13 @@ package fr.acinq.eclair.db.sqlite import java.sql.Connection import java.util.UUID - import fr.acinq.bitcoin.ByteVector32 import fr.acinq.eclair.db.sqlite.SqliteUtils._ import fr.acinq.eclair.db.{IncomingPayment, OutgoingPayment, OutgoingPaymentStatus, PaymentsDb} import fr.acinq.eclair.payment.PaymentRequest import grizzled.slf4j.Logging - import scala.collection.immutable.Queue import OutgoingPaymentStatus._ - import concurrent.duration._ import scala.collection.mutable import scala.compat.Platform @@ -62,7 +59,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { insertFailures(sent.id, sent.failures) } - override def updateOutgoingPayment(id: UUID, newStatus: OutgoingPaymentStatus.Value, preimage: Option[ByteVector32] = None, failures: Traversable[String] = Seq()): Unit = { + override def updateOutgoingPayment(id: UUID, newStatus: OutgoingPaymentStatus.Value, preimage: Option[ByteVector32] = None, failures: Seq[String] = Seq.empty): Unit = { require((newStatus == SUCCEEDED && preimage.isDefined) || (newStatus == FAILED && preimage.isEmpty), "Wrong combination of state/preimage") require((newStatus == SUCCEEDED && failures.isEmpty) || (newStatus == FAILED && failures.nonEmpty), "Wrong combination of state/failures") @@ -81,9 +78,8 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { statement.setString(1, id.toString) val rs = statement.executeQuery() if (rs.next()) { - val id = UUID.fromString(rs.getString("id")) Some(OutgoingPayment( - id, + UUID.fromString(rs.getString("id")), rs.getByteVector32("payment_hash"), rs.getByteVector32Nullable("preimage"), rs.getLong("amount_msat"), @@ -257,7 +253,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { statement.setString(1, id.toString) val rs = statement.executeQuery() while (rs.next()) { - res += rs.getString(1) + res += rs.getString("failure") } } res.toList 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 5a61646f02..41c20bc4fe 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) - paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, Seq())) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, Seq.empty)) 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, Seq())) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amountMsat, Platform.currentTime, None, OutgoingPaymentStatus.PENDING, Seq.empty)) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, c, failures = Nil) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala index fb014aaa8f..87012f1e25 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala @@ -137,8 +137,8 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR case Local(id, None) => // we sent the payment, but we probably restarted and the reference to the original sender was lost, // we publish the failure on the event stream and update the status in paymentDb - nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = Nil) - context.system.eventStream.publish(PaymentFailed(id, paymentHash, Nil)) + nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = Seq.empty) + context.system.eventStream.publish(PaymentFailed(id, paymentHash, Seq.empty)) case Local(_, Some(sender)) => sender ! Status.Failure(addFailed) case Relayed(originChannelId, originHtlcId, _, _) => 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 aadef7e846..68c7da2061 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 @@ -139,11 +139,11 @@ class SqlitePaymentsDbSpec extends FunSuite { val s3 = s2.copy(id = UUID.randomUUID(), amountMsat = 88776655) db.addOutgoingPayment(s3) - db.updateOutgoingPayment(s3.id, FAILED, failures = Seq("one", "two", "three")) + db.updateOutgoingPayment(s3.id, FAILED, failures = Seq("one", "one", "two", "three")) assert(db.getOutgoingPayment(s3.id).get.status == FAILED) assert(db.getOutgoingPayment(s3.id).get.preimage.isEmpty) // failed sent payments don't have a preimage assert(db.getOutgoingPayment(s3.id).get.completedAt.isDefined) - assert(db.getOutgoingPayment(s3.id).get.failures.toSet == Set("one", "two", "three")) + assert(db.getOutgoingPayment(s3.id).get.failures.sorted == Seq("one", "one", "two", "three").sorted) // can't update again once it's in a final state assertThrows[IllegalArgumentException](db.updateOutgoingPayment(s3.id, SUCCEEDED)) From 094a256df26870b387385ac6718d7ad6ceafbd85 Mon Sep 17 00:00:00 2001 From: rorp Date: Thu, 25 Jul 2019 16:06:12 +0200 Subject: [PATCH 4/5] keep payments and remotely generated strings separated --- .../scala/fr/acinq/eclair/db/Databases.scala | 8 +++++--- .../eclair/db/sqlite/SqlitePaymentsDb.scala | 16 +++++++++++----- .../scala/fr/acinq/eclair/TestConstants.scala | 2 +- .../acinq/eclair/db/SqlitePaymentsDbSpec.scala | 18 ++++++++++-------- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala index eb982b0f7e..de035a6be5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala @@ -50,17 +50,19 @@ object Databases { val sqliteEclair = DriverManager.getConnection(s"jdbc:sqlite:${new File(dbdir, "eclair.sqlite")}") val sqliteNetwork = DriverManager.getConnection(s"jdbc:sqlite:${new File(dbdir, "network.sqlite")}") val sqliteAudit = DriverManager.getConnection(s"jdbc:sqlite:${new File(dbdir, "audit.sqlite")}") + val sqliteExt = DriverManager.getConnection(s"jdbc:sqlite:${new File(dbdir, "ext.sqlite")}") SqliteUtils.obtainExclusiveLock(sqliteEclair) // there should only be one process writing to this file + SqliteUtils.obtainExclusiveLock(sqliteExt) // there should only be one process writing to this file - databaseByConnections(sqliteAudit, sqliteNetwork, sqliteEclair) + databaseByConnections(sqliteAudit, sqliteNetwork, sqliteEclair, sqliteExt) } - def databaseByConnections(auditJdbc: Connection, networkJdbc: Connection, eclairJdbc: Connection) = new Databases { + def databaseByConnections(auditJdbc: Connection, networkJdbc: Connection, eclairJdbc: Connection, extJdbc: Connection) = new Databases { override val network = new SqliteNetworkDb(networkJdbc) override val audit = new SqliteAuditDb(auditJdbc) override val channels = new SqliteChannelsDb(eclairJdbc) override val peers = new SqlitePeersDb(eclairJdbc) - override val payments = new SqlitePaymentsDb(eclairJdbc) + override val payments = new SqlitePaymentsDb(eclairJdbc, extJdbc) override val pendingRelay = new SqlitePendingRelayDb(eclairJdbc) override def backup(file: File): Unit = { SqliteUtils.using(eclairJdbc.createStatement()) { 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 a4f5be4094..9adffa929d 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 @@ -29,23 +29,29 @@ import concurrent.duration._ import scala.collection.mutable import scala.compat.Platform -class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { +class SqlitePaymentsDb(sqlite: Connection, extSqlite: Connection) extends PaymentsDb with Logging { import SqliteUtils.ExtendedResultSet._ val DB_NAME = "payments" + val EXT_DB_NAME = "ext" 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 3 is "backward compatible" in the sense that it uses separate tables from versions 1 and 2. There is no migration though 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 TABLE IF NOT EXISTS sent_payments_failures (id TEXT NOT NULL, failure TEXT NOT NULL)") - statement.executeUpdate("CREATE INDEX IF NOT EXISTS sent_payments_failures_idx ON sent_payments_failures(id)") statement.executeUpdate("CREATE INDEX IF NOT EXISTS payment_hash_idx ON sent_payments(payment_hash)") setVersion(statement, DB_NAME, CURRENT_VERSION) } + using(extSqlite.createStatement()) { statement => + require(getVersion(statement, EXT_DB_NAME, CURRENT_VERSION) <= CURRENT_VERSION, s"incompatible version of $EXT_DB_NAME DB found") // version 3 is "backward compatible" in the sense that it uses separate tables from versions 1 and 2. There is no migration though + statement.executeUpdate("CREATE TABLE IF NOT EXISTS sent_payments_failures (id TEXT NOT NULL, failure TEXT NOT NULL)") + statement.executeUpdate("CREATE INDEX IF NOT EXISTS sent_payments_failures_idx ON sent_payments_failures(id)") + setVersion(statement, EXT_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 => statement.setString(1, sent.id.toString) @@ -239,7 +245,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { private def insertFailures(id: UUID, failures: Traversable[String]): Unit = { failures.filter(_.nonEmpty).foreach { failure => - using(sqlite.prepareStatement("INSERT INTO sent_payments_failures (id, failure) VALUES (?, ?)")) { statement => + using(extSqlite.prepareStatement("INSERT INTO sent_payments_failures (id, failure) VALUES (?, ?)")) { statement => statement.setString(1, id.toString) statement.setString(2, failure) val res = statement.executeUpdate() @@ -249,7 +255,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { private def selectFailures(id: UUID): Seq[String] = { val res = new mutable.ListBuffer[String] - using(sqlite.prepareStatement("SELECT failure FROM sent_payments_failures WHERE id = ?")) { statement => + using(extSqlite.prepareStatement("SELECT failure FROM sent_payments_failures WHERE id = ?")) { statement => statement.setString(1, id.toString) val rs = statement.executeQuery() while (rs.next()) { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index e604a51dc2..09ad24a2ec 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -42,7 +42,7 @@ object TestConstants { def sqliteInMemory() = DriverManager.getConnection("jdbc:sqlite::memory:") - def inMemoryDb(connection: Connection = sqliteInMemory()): Databases = Databases.databaseByConnections(connection, connection, connection) + def inMemoryDb(connection: Connection = sqliteInMemory()): Databases = Databases.databaseByConnections(connection, connection, connection, connection) object Alice { val seed = ByteVector32(ByteVector.fill(32)(1)) 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 68c7da2061..2e5573f1db 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 @@ -34,9 +34,9 @@ class SqlitePaymentsDbSpec extends FunSuite { test("init sqlite 2 times in a row") { val sqlite = TestConstants.sqliteInMemory() - val db1 = new SqlitePaymentsDb(sqlite) - val db2 = new SqlitePaymentsDb(sqlite) - val db3 = new SqlitePaymentsDb(sqlite) + val db1 = new SqlitePaymentsDb(sqlite = sqlite, extSqlite = sqlite) + val db2 = new SqlitePaymentsDb(sqlite = sqlite, extSqlite = sqlite) + val db3 = new SqlitePaymentsDb(sqlite = sqlite, extSqlite = sqlite) } test("handle version migration 1->2->3") { @@ -62,7 +62,7 @@ class SqlitePaymentsDbSpec extends FunSuite { statement.executeUpdate() } - val preMigrationDb = new SqlitePaymentsDb(connection) + val preMigrationDb = new SqlitePaymentsDb(sqlite = connection, extSqlite = connection) using(connection.createStatement()) { statement => assert(getVersion(statement, "payments", 1) == 3) // version has changed from 1 to 3! @@ -84,7 +84,7 @@ class SqlitePaymentsDbSpec extends FunSuite { assert(preMigrationDb.listOutgoingPayments() == Seq(ps1)) assert(preMigrationDb.listPaymentRequests(0, (Platform.currentTime.milliseconds + 1.minute).toSeconds) == Seq(i1)) - val postMigrationDb = new SqlitePaymentsDb(connection) + val postMigrationDb = new SqlitePaymentsDb(sqlite = connection, extSqlite = connection) using(connection.createStatement()) { statement => assert(getVersion(statement, "payments", 2) == 3) // version still to 2 @@ -97,7 +97,7 @@ class SqlitePaymentsDbSpec extends FunSuite { 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) + val db = new SqlitePaymentsDb(sqlite = sqlite, extSqlite = sqlite) // can't receive a payment without an invoice associated with it assertThrows[IllegalArgumentException](db.addIncomingPayment(IncomingPayment(ByteVector32(hex"6e7e8018f05e169cf1d99e77dc22cb372d09f10b6a81f1eae410718c56cad188"), 12345678, 1513871928275L))) @@ -120,7 +120,8 @@ class SqlitePaymentsDbSpec extends FunSuite { test("add/retrieve/update sent payments") { - val db = new SqlitePaymentsDb(TestConstants.sqliteInMemory()) + val connection = TestConstants.sqliteInMemory() + val db = new SqlitePaymentsDb(sqlite = connection, extSqlite = connection) val s1 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"0f059ef9b55bb70cc09069ee4df854bf0fab650eee6f2b87ba26d1ad08ab114f"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, Seq()) val s2 = OutgoingPayment(id = UUID.randomUUID(), paymentHash = ByteVector32(hex"08d47d5f7164d4b696e8f6b62a03094d4f1c65f16e9d7b11c4a98854707e55cf"), None, amountMsat = 12345, createdAt = 12345, None, PENDING, Seq()) @@ -156,7 +157,8 @@ class SqlitePaymentsDbSpec extends FunSuite { test("add/retrieve payment requests") { val someTimestamp = 12345 - val db = new SqlitePaymentsDb(TestConstants.sqliteInMemory()) + val connection = TestConstants.sqliteInMemory() + val db = new SqlitePaymentsDb(sqlite = connection, extSqlite = connection) val bob = Bob.keyManager From 4966e0d814f0cb4888cb81078ad6379a6e2e92cd Mon Sep 17 00:00:00 2001 From: rorp Date: Thu, 25 Jul 2019 16:13:08 +0200 Subject: [PATCH 5/5] cleanup --- .../main/scala/fr/acinq/eclair/payment/Relayer.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala index 001e0ca707..ed0b793880 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala @@ -162,7 +162,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR // we sent the payment, but we probably restarted and the reference to the original sender was lost, // we publish the failure on the event stream and update the status in paymentDb nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.SUCCEEDED, Some(fulfill.paymentPreimage)) - context.system.eventStream.publish(PaymentSucceeded(id, add.amountMsat, add.paymentHash, fulfill.paymentPreimage, Nil)) // + context.system.eventStream.publish(PaymentSucceeded(id, add.amountMsat, add.paymentHash, fulfill.paymentPreimage, Seq.empty)) // case Local(_, Some(sender)) => sender ! fulfill case Relayed(originChannelId, originHtlcId, amountMsatIn, amountMsatOut) => @@ -176,8 +176,8 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR case Local(id, None) => // we sent the payment, but we probably restarted and the reference to the original sender was lost // we publish the failure on the event stream and update the status in paymentDb - nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = Nil) - context.system.eventStream.publish(PaymentFailed(id, add.paymentHash, Nil)) + nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = Seq.empty) + context.system.eventStream.publish(PaymentFailed(id, add.paymentHash, Seq.empty)) case Local(_, Some(sender)) => sender ! fail case Relayed(originChannelId, originHtlcId, _, _) => @@ -190,8 +190,8 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR case Local(id, None) => // we sent the payment, but we probably restarted and the reference to the original sender was lost // we publish the failure on the event stream and update the status in paymentDb - nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = Nil) - context.system.eventStream.publish(PaymentFailed(id, add.paymentHash, Nil)) + nodeParams.db.payments.updateOutgoingPayment(id, OutgoingPaymentStatus.FAILED, failures = Seq.empty) + context.system.eventStream.publish(PaymentFailed(id, add.paymentHash, Seq.empty)) case Local(_, Some(sender)) => sender ! fail case Relayed(originChannelId, originHtlcId, _, _) =>