1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-13 19:37:35 +01:00

Add DB entry for payment router error (#1513)

When using MPP, if we can't find a route, we need to add an entry to the
DB. Otherwise when users query their payment status, nothing will be
returned which is a bad UX.

Fixes #1512
This commit is contained in:
Bastien Teinturier 2020-09-29 18:08:08 +02:00 committed by pm47
parent ebed70b93c
commit 9bb992bbc9
No known key found for this signature in database
GPG key ID: E434ED292E85643A
4 changed files with 22 additions and 0 deletions

View file

@ -200,6 +200,7 @@ object HopSummary {
case class FailureSummary(failureType: FailureType.Value, failureMessage: String, failedRoute: List[HopSummary])
object FailureType extends Enumeration {
type FailureType = Value
val LOCAL = Value(1, "Local")
val REMOTE = Value(2, "Remote")
val UNREADABLE_REMOTE = Value(3, "UnreadableRemote")

View file

@ -24,6 +24,7 @@ import akka.event.Logging.MDC
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.channel.Upstream
import fr.acinq.eclair.db.{OutgoingPayment, OutgoingPaymentStatus, PaymentType}
import fr.acinq.eclair.payment.Monitoring.{Metrics, Tags}
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
import fr.acinq.eclair.payment.PaymentSent.PartialPayment
@ -113,6 +114,13 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig,
} else {
val failure = LocalFailure(Nil, t)
Metrics.PaymentError.withTag(Tags.Failure, Tags.FailureType(failure)).increment()
if (cfg.storeInDb && d.pending.isEmpty && d.failures.isEmpty) {
// In cases where we fail early (router error during the first attempt), the DB won't have an entry for that
// payment, which may be confusing for users.
val dummyPayment = OutgoingPayment(id, cfg.parentId, cfg.externalId, paymentHash, PaymentType.Standard, cfg.recipientAmount, cfg.recipientAmount, cfg.recipientNodeId, System.currentTimeMillis, cfg.paymentRequest, OutgoingPaymentStatus.Pending)
nodeParams.db.payments.addOutgoingPayment(dummyPayment)
nodeParams.db.payments.updateOutgoingPayment(PaymentFailed(id, paymentHash, failure :: Nil))
}
gotoAbortedOrStop(PaymentAborted(d.sender, d.request, d.failures :+ failure, d.pending.keySet))
}

View file

@ -24,6 +24,7 @@ import fr.acinq.bitcoin.{Block, Crypto}
import fr.acinq.eclair._
import fr.acinq.eclair.channel.{AddHtlcFailed, ChannelFlags, ChannelUnavailable, Upstream}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.db.{FailureSummary, FailureType, OutgoingPaymentStatus}
import fr.acinq.eclair.payment.send.MultiPartPaymentLifecycle
import fr.acinq.eclair.payment.send.MultiPartPaymentLifecycle._
import fr.acinq.eclair.payment.send.PaymentError.RetryExhausted
@ -275,6 +276,10 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
assert(result.paymentHash === paymentHash)
assert(result.failures === Seq(LocalFailure(Nil, RouteNotFound)))
val Some(outgoing) = nodeParams.db.payments.getOutgoingPayment(cfg.id)
assert(outgoing.status.isInstanceOf[OutgoingPaymentStatus.Failed])
assert(outgoing.status.asInstanceOf[OutgoingPaymentStatus.Failed].failures === Seq(FailureSummary(FailureType.LOCAL, RouteNotFound.getMessage, Nil)))
sender.expectTerminated(payFsm)
sender.expectNoMsg(100 millis)
router.expectNoMsg(100 millis)

View file

@ -25,6 +25,7 @@ import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transact
import fr.acinq.eclair.ApiTypes.ChannelIdentifier
import fr.acinq.eclair.channel.{ChannelCommandResponse, ChannelVersion, State}
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.db.FailureType.FailureType
import fr.acinq.eclair.db.{IncomingPaymentStatus, OutgoingPaymentStatus}
import fr.acinq.eclair.payment._
import fr.acinq.eclair.router.Router.RouteResponse
@ -210,6 +211,12 @@ class FailureMessageSerializer extends CustomSerializer[FailureMessage](_ => ( {
case m: FailureMessage => JString(m.message)
}))
class FailureTypeSerializer extends CustomSerializer[FailureType](_ => ( {
null
}, {
case ft: FailureType => JString(ft.toString)
}))
class NodeAddressSerializer extends CustomSerializer[NodeAddress](_ => ( {
null
}, {
@ -295,6 +302,7 @@ object JsonSupport extends Json4sJacksonSupport {
new RouteResponseSerializer +
new ThrowableSerializer +
new FailureMessageSerializer +
new FailureTypeSerializer +
new NodeAddressSerializer +
new DirectedHtlcSerializer +
new PaymentRequestSerializer +