diff --git a/payments/db/errors.go b/payments/db/errors.go index fee71b05f..0457db600 100644 --- a/payments/db/errors.go +++ b/payments/db/errors.go @@ -84,6 +84,12 @@ var ( ErrMixedBlindedAndNonBlindedPayments = errors.New("mixed blinded and " + "non-blinded payments") + // ErrBlindedPaymentMissingTotalAmount is returned if we try to + // register a blinded payment attempt where the final hop doesn't set + // the total amount. + ErrBlindedPaymentMissingTotalAmount = errors.New("blinded payment " + + "final hop must set total amount") + // ErrMPPPaymentAddrMismatch is returned if we try to register an MPP // shard where the payment address doesn't match existing shards. ErrMPPPaymentAddrMismatch = errors.New("payment address mismatch") diff --git a/payments/db/payment.go b/payments/db/payment.go index 147ccdb1e..ddceedfb0 100644 --- a/payments/db/payment.go +++ b/payments/db/payment.go @@ -744,6 +744,13 @@ func verifyAttempt(payment *MPPayment, attempt *HTLCAttemptInfo) error { // in the split payment is correct. isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0 + // For blinded payments, the last hop must set the total amount. + if isBlinded { + if attempt.Route.FinalHop().TotalAmtMsat == 0 { + return ErrBlindedPaymentMissingTotalAmount + } + } + // Make sure any existing shards match the new one with regards // to MPP options. mpp := attempt.Route.FinalHop().MPP diff --git a/payments/db/payment_test.go b/payments/db/payment_test.go index a7369c14b..e6a2e735a 100644 --- a/payments/db/payment_test.go +++ b/payments/db/payment_test.go @@ -1388,6 +1388,45 @@ func TestVerifyAttemptBlindedValidation(t *testing.T) { require.NoError(t, verifyAttempt(payment, &matching)) } +// TestVerifyAttemptBlindedMissingTotalAmount tests that we return an error if +// we try to register a blinded payment attempt where the final hop doesn't set +// the total amount. +func TestVerifyAttemptBlindedMissingTotalAmount(t *testing.T) { + t.Parallel() + + total := lnwire.MilliSatoshi(5000) + + // Payment with no existing attempts. + payment := makePayment(total) + + // Attempt with encrypted data (blinded payment) but missing total + // amount. + attemptMissingTotal := makeLastHopAttemptInfo( + 1, + lastHopArgs{ + amt: 2500, + total: 0, + encrypted: []byte{1, 2, 3}, + }, + ) + require.ErrorIs( + t, + verifyAttempt(payment, &attemptMissingTotal), + ErrBlindedPaymentMissingTotalAmount, + ) + + // Attempt with encrypted data and valid total amount should succeed. + attemptWithTotal := makeLastHopAttemptInfo( + 2, + lastHopArgs{ + amt: 2500, + total: total, + encrypted: []byte{4, 5, 6}, + }, + ) + require.NoError(t, verifyAttempt(payment, &attemptWithTotal)) +} + // TestVerifyAttemptBlindedMixedWithNonBlinded tests that we return an error if // we try to register a non-MPP attempt for a blinded payment. func TestVerifyAttemptBlindedMixedWithNonBlinded(t *testing.T) {