Skip to content

Conversation

@ziggie1984
Copy link
Collaborator

@ziggie1984 ziggie1984 commented Oct 13, 2025

Depends on #10287

This PR implements most of the Insert and Delete methods we need for the unit tests in the following PR.

@ziggie1984 ziggie1984 changed the base branch from master to elle-payment-sql-series-new October 13, 2025 19:13
@ziggie1984 ziggie1984 self-assigned this Oct 13, 2025
@ziggie1984 ziggie1984 added payments Related to invoices/payments sql labels Oct 13, 2025
@ziggie1984 ziggie1984 added this to v0.21 Oct 13, 2025
@ziggie1984 ziggie1984 added this to the v0.21.0 milestone Oct 13, 2025
@saubyk saubyk moved this to In progress in v0.21 Oct 14, 2025
@ziggie1984 ziggie1984 changed the title [Part 3|*] Implement FetchPayment for SQL Backend [Part 3|*] Implement Second Part for SQL Backend functions Oct 15, 2025
@ziggie1984 ziggie1984 force-pushed the introduce-sql-schema-payments-part-3 branch 2 times, most recently from 80de0dd to c0134ed Compare October 15, 2025 15:12
@ziggie1984 ziggie1984 marked this pull request as ready for review October 15, 2025 15:13
@ziggie1984 ziggie1984 force-pushed the introduce-sql-schema-payments-part-3 branch 3 times, most recently from f0e3302 to 99ddf07 Compare October 17, 2025 07:53
@ziggie1984
Copy link
Collaborator Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a significant portion of the insert and delete methods for the new SQL backend for the payments database. The changes are extensive and include new SQL queries, data conversion functions, and the implementation of several methods on the SQLStore.

The use of ExecuteBatchQuery and ExecutePaginatedQuery is great for performance. The new PaymentAndIntent interface is a good abstraction.

I've found a few areas for improvement:

  • Adherence to the function commenting style guide for exported functions.
  • Consistent use of context.Context propagation instead of context.TODO().
  • Opportunities for performance improvements by using batch inserts and optimizing queries to avoid extra fetches.
  • A potential code smell related to handling of nil slices from the database.

Overall, this is a solid step forward in the SQL backend implementation. Addressing these points will improve the code's maintainability, robustness, and performance.

Comment on lines +510 to +511
func (s *SQLStore) FetchPayment(paymentHash lntypes.Hash) (*MPPayment, error) {
ctx := context.TODO()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This function should accept a context.Context from its caller instead of using context.TODO(). This allows for proper context propagation, which is crucial for handling cancellations and deadlines. This change will require updating the DB interface as well. Several other methods in this file (DeletePayment, DeleteFailedAttempts, InitPayment, RegisterAttempt, SettleAttempt) have the same issue and should also be updated.

Suggested change
func (s *SQLStore) FetchPayment(paymentHash lntypes.Hash) (*MPPayment, error) {
ctx := context.TODO()
func (s *SQLStore) FetchPayment(ctx context.Context, paymentHash lntypes.Hash) (*MPPayment, error) {

Comment on lines +281 to +286
// TODO(ziggie): Can we get rid of this?
// This has to be in place otherwise the
// comparison will not match.
if record.Value == nil {
record.Value = []byte{}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The TODO here highlights a potential code smell. This workaround of converting a nil slice to an empty slice might be hiding an issue or could be simplified. It would be beneficial to investigate whether the downstream code that consumes these records can be made to handle nil slices gracefully. This would remove the need for this conversion and make the data handling more consistent.

Comment on lines +350 to +353
// QueryPayments queries the payments from the database.
//
// This is part of the DB interface.
func (s *SQLStore) QueryPayments(ctx context.Context,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment for this exported function does not fully adhere to the repository's style guide.1 The style guide requires that function comments begin with the function name and provide detailed information for the caller. Please update this comment to start with QueryPayments and add more details about its behavior, parameters, and return values. This applies to other new exported functions in this file as well, such as FetchPayment, DeletePayment, etc.

Style Guide References

Footnotes

  1. Exported functions should have detailed comments that start with the function name, as per the style guide. (link)

Comment on lines 707 to 712
// Get the payment ID from the inserted payment
// We need to fetch the payment we just inserted to get its ID
insertedPayment, err := db.FetchPayment(ctx, paymentHash[:])
if err != nil {
return fmt.Errorf("failed to fetch inserted payment: %w", err)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve efficiency, this extra FetchPayment call can be avoided. The InsertPayment SQL query can be modified to return the ID of the newly inserted row. You can achieve this by changing the query in sqldb/sqlc/queries/payments.sql from :exec to :one and adding RETURNING id. The Go code would then be simplified to use the returned ID directly.

Comment on lines +739 to +742
func (s *SQLStore) insertRouteHops(ctx context.Context, db SQLQueries,
hops []*route.Hop, attemptID uint64) error {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function inserts route hops and their associated data one by one in a loop. For routes with many hops, this can result in a large number of database queries, impacting performance. Consider refactoring this to use batch inserts. sqlc supports batch inserts, which would require modifying the SQL queries to accept slices of parameters and then calling them once with all the data for the hops.

@ziggie1984 ziggie1984 force-pushed the introduce-sql-schema-payments-part-3 branch from 99ddf07 to 6145682 Compare October 17, 2025 08:43
@ziggie1984 ziggie1984 moved this from In progress to In review in v0.21 Oct 18, 2025
@lightninglabs-deploy
Copy link

@ziggie1984, remember to re-request review from reviewers when ready

Comment on lines +562 to +563
}, func() {
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NoOpReset

Comment on lines +540 to +543
completePayment, err := s.fetchPaymentWithCompleteData(
ctx, db, fetchPayment,
)
if err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think we need to fetch the complete payment here?

Comment on lines 554 to 558
if failedHtlcsOnly {
return s.db.DeleteFailedAttempts(
ctx, fetchPayment.Payment.ID,
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like the next commit should be before this one?

Comment on lines 555 to 562
return db.DeleteFailedAttempts(
ctx, fetchPayment.Payment.ID,
)
}

// Be careful to not use s.db here, because we are in a
// transaction, is there a way to make this more secure?
return db.DeletePayment(ctx, fetchPayment.Payment.ID)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these changes should be in the prior commit (was confusing to have it here since it seemed like it might be related to the change below but it is not)

Comment on lines +729 to +730
}, func() {
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sqldb.NoOpReset

}

// If payment exists and is failed, delete it first.
if existingPayment.Payment.ID != 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i find this part quite hard to read. I suggest using a switch with 3 different cases.
Cause here, you are referencing the returned existingPayment in the case that the error is not nil which is an antipattern.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will only be !=0 in the case where err == nil - so just put it in that block

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so like:

existingPayment, err := db.FetchPayment(ctx, paymentHash[:])
		switch {
		case err == nil:
			completePayment, err := s.fetchPaymentWithCompleteData(
				ctx, db, existingPayment,
			)
			if err != nil {
				return fmt.Errorf("failed to fetch payment "+
					"with complete data: %w", err)
			}

			// Check if the payment is initializable otherwise
			// we'll return early.
			err = completePayment.Status.initializable()
			if err != nil {
				return err
			}

			// If the initializable check above passes, then the
			// existing payment has failed. So we delete it and
			// all of its previous artifacts. We rely on
			// cascading deletes to clean up the rest.
			err = db.DeletePayment(ctx, existingPayment.Payment.ID)
			if err != nil {
				return fmt.Errorf("failed to delete "+
					"payment: %w", err)
			}

		case !errors.Is(err, sql.ErrNoRows):
			// Some other error occurred
			return fmt.Errorf("failed to check existing "+
				"payment: %w", err)

		// The payment does not yet exist.
		default:
		}

Comment on lines +691 to +695
// Only set the intent ID if it's not nil.
var intentIDParam sql.NullInt64
if intentID != nil {
intentIDParam = sqldb.SQLInt64(*intentID)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think rather then dont use the sqldb.SQLInt64 helper

	var intentID int64
		if len(paymentCreationInfo.PaymentRequest) > 0 {
			intentID, err = db.InsertPaymentIntent(ctx,
				sqlc.InsertPaymentIntentParams{
					IntentType: int16(
						PaymentIntentTypeBolt11,
					),
					IntentPayload: paymentCreationInfo.
						PaymentRequest,
				})
			if err != nil {
				return fmt.Errorf("failed to initialize "+
					"payment intent: %w", err)
			}
		}

		paymentID, err := db.InsertPayment(ctx,
			sqlc.InsertPaymentParams{
				IntentID: sql.NullInt64{
					Int64: intentID,
					Valid: intentID != 0,
				},

var mpPayment *MPPayment

err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
// 1. First Fetch the payment and check if it is registrable.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: started with numbering and then stopped

Comment on lines +918 to +919
// Add the attempt to the payment without fetching it from the
// DB again.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +951 to +958
payment, err := db.FetchPayment(ctx, paymentHash[:])
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("failed to fetch payment: %w", err)
}
if errors.Is(err, sql.ErrNoRows) {
return ErrPaymentNotInitiated
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why dont we check Status.updatable() as is done for KVStore?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

payments Related to invoices/payments sql

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

3 participants