Skip to content

Commit e20a34d

Browse files
committed
paymentsdb: refactor attempt verification
1 parent 0320dfc commit e20a34d

File tree

2 files changed

+95
-82
lines changed

2 files changed

+95
-82
lines changed

payments/db/kv_store.go

Lines changed: 3 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -389,88 +389,9 @@ func (p *KVStore) RegisterAttempt(paymentHash lntypes.Hash,
389389
return err
390390
}
391391

392-
// If the final hop has encrypted data, then we know this is a
393-
// blinded payment. In blinded payments, MPP records are not set
394-
// for split payments and the recipient is responsible for using
395-
// a consistent PathID across the various encrypted data
396-
// payloads that we received from them for this payment. All we
397-
// need to check is that the total amount field for each HTLC
398-
// in the split payment is correct.
399-
isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0
400-
401-
// Make sure any existing shards match the new one with regards
402-
// to MPP options.
403-
mpp := attempt.Route.FinalHop().MPP
404-
405-
// MPP records should not be set for attempts to blinded paths.
406-
if isBlinded && mpp != nil {
407-
return ErrMPPRecordInBlindedPayment
408-
}
409-
410-
for _, h := range payment.InFlightHTLCs() {
411-
hMpp := h.Route.FinalHop().MPP
412-
413-
// If this is a blinded payment, then no existing HTLCs
414-
// should have MPP records.
415-
if isBlinded && hMpp != nil {
416-
return ErrMPPRecordInBlindedPayment
417-
}
418-
419-
// If this is a blinded payment, then we just need to
420-
// check that the TotalAmtMsat field for this shard
421-
// is equal to that of any other shard in the same
422-
// payment.
423-
if isBlinded {
424-
if attempt.Route.FinalHop().TotalAmtMsat !=
425-
h.Route.FinalHop().TotalAmtMsat {
426-
427-
//nolint:ll
428-
return ErrBlindedPaymentTotalAmountMismatch
429-
}
430-
431-
continue
432-
}
433-
434-
switch {
435-
// We tried to register a non-MPP attempt for a MPP
436-
// payment.
437-
case mpp == nil && hMpp != nil:
438-
return ErrMPPayment
439-
440-
// We tried to register a MPP shard for a non-MPP
441-
// payment.
442-
case mpp != nil && hMpp == nil:
443-
return ErrNonMPPayment
444-
445-
// Non-MPP payment, nothing more to validate.
446-
case mpp == nil:
447-
continue
448-
}
449-
450-
// Check that MPP options match.
451-
if mpp.PaymentAddr() != hMpp.PaymentAddr() {
452-
return ErrMPPPaymentAddrMismatch
453-
}
454-
455-
if mpp.TotalMsat() != hMpp.TotalMsat() {
456-
return ErrMPPTotalAmountMismatch
457-
}
458-
}
459-
460-
// If this is a non-MPP attempt, it must match the total amount
461-
// exactly. Note that a blinded payment is considered an MPP
462-
// attempt.
463-
amt := attempt.Route.ReceiverAmt()
464-
if !isBlinded && mpp == nil && amt != payment.Info.Value {
465-
return ErrValueMismatch
466-
}
467-
468-
// Ensure we aren't sending more than the total payment amount.
469-
sentAmt, _ := payment.SentAmt()
470-
if sentAmt+amt > payment.Info.Value {
471-
return fmt.Errorf("%w: attempted=%v, payment amount="+
472-
"%v", ErrValueExceedsAmt,
473-
sentAmt+amt, payment.Info.Value)
392+
// Verify the attempt is compatible with the existing payment.
393+
if err := verifyAttempt(payment, attempt); err != nil {
394+
return err
474395
}
475396

476397
htlcsBucket, err := bucket.CreateBucketIfNotExists(

payments/db/payment.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,3 +712,95 @@ func generateSphinxPacket(rt *route.Route, paymentHash []byte,
712712
PaymentPath: sphinxPath.NodeKeys(),
713713
}, nil
714714
}
715+
716+
// verifyAttempt validates that a new HTLC attempt is compatible with the
717+
// existing payment and its in-flight HTLCs. This function checks:
718+
// 1. MPP (Multi-Path Payment) compatibility between attempts
719+
// 2. Blinded payment consistency
720+
// 3. Amount validation
721+
// 4. Total payment amount limits
722+
func verifyAttempt(payment *MPPayment, attempt *HTLCAttemptInfo) error {
723+
// If the final hop has encrypted data, then we know this is a
724+
// blinded payment. In blinded payments, MPP records are not set
725+
// for split payments and the recipient is responsible for using
726+
// a consistent PathID across the various encrypted data
727+
// payloads that we received from them for this payment. All we
728+
// need to check is that the total amount field for each HTLC
729+
// in the split payment is correct.
730+
isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0
731+
732+
// Make sure any existing shards match the new one with regards
733+
// to MPP options.
734+
mpp := attempt.Route.FinalHop().MPP
735+
736+
// MPP records should not be set for attempts to blinded paths.
737+
if isBlinded && mpp != nil {
738+
return ErrMPPRecordInBlindedPayment
739+
}
740+
741+
for _, h := range payment.InFlightHTLCs() {
742+
hMpp := h.Route.FinalHop().MPP
743+
744+
// If this is a blinded payment, then no existing HTLCs
745+
// should have MPP records.
746+
if isBlinded && hMpp != nil {
747+
return ErrMPPRecordInBlindedPayment
748+
}
749+
750+
// If this is a blinded payment, then we just need to
751+
// check that the TotalAmtMsat field for this shard
752+
// is equal to that of any other shard in the same
753+
// payment.
754+
if isBlinded {
755+
if attempt.Route.FinalHop().TotalAmtMsat !=
756+
h.Route.FinalHop().TotalAmtMsat {
757+
758+
return ErrBlindedPaymentTotalAmountMismatch
759+
}
760+
761+
continue
762+
}
763+
764+
switch {
765+
// We tried to register a non-MPP attempt for a MPP
766+
// payment.
767+
case mpp == nil && hMpp != nil:
768+
return ErrMPPayment
769+
770+
// We tried to register a MPP shard for a non-MPP
771+
// payment.
772+
case mpp != nil && hMpp == nil:
773+
return ErrNonMPPayment
774+
775+
// Non-MPP payment, nothing more to validate.
776+
case mpp == nil:
777+
continue
778+
}
779+
780+
// Check that MPP options match.
781+
if mpp.PaymentAddr() != hMpp.PaymentAddr() {
782+
return ErrMPPPaymentAddrMismatch
783+
}
784+
785+
if mpp.TotalMsat() != hMpp.TotalMsat() {
786+
return ErrMPPTotalAmountMismatch
787+
}
788+
}
789+
790+
// If this is a non-MPP attempt, it must match the total amount
791+
// exactly. Note that a blinded payment is considered an MPP
792+
// attempt.
793+
amt := attempt.Route.ReceiverAmt()
794+
if !isBlinded && mpp == nil && amt != payment.Info.Value {
795+
return ErrValueMismatch
796+
}
797+
798+
// Ensure we aren't sending more than the total payment amount.
799+
sentAmt, _ := payment.SentAmt()
800+
if sentAmt+amt > payment.Info.Value {
801+
return fmt.Errorf("%w: attempted=%v, payment amount=%v",
802+
ErrValueExceedsAmt, sentAmt+amt, payment.Info.Value)
803+
}
804+
805+
return nil
806+
}

0 commit comments

Comments
 (0)