-
Notifications
You must be signed in to change notification settings - Fork 126
staticaddr: dynamic deposit conf target #1097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ import ( | |
| "github.com/lightningnetwork/lnd/lnwallet" | ||
| "github.com/lightningnetwork/lnd/lnwallet/chainfee" | ||
| "github.com/lightningnetwork/lnd/lnwire" | ||
| "google.golang.org/grpc/status" | ||
| ) | ||
|
|
||
| const ( | ||
|
|
@@ -146,6 +147,10 @@ func (f *FSM) InitHtlcAction(ctx context.Context, | |
| ctx, loopInReq, | ||
| ) | ||
| if err != nil { | ||
| // Check if this is an insufficient confirmations error and log | ||
| // the details to help the user understand what's needed. | ||
| logInsufficientConfirmationsDetails(err) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new normal insufficient-confirmation path leaks an invoice. The only invoice cancel path I found is much later in the payment-timeout monitor in Early |
||
|
|
||
| err = fmt.Errorf("unable to initiate the loop-in with the "+ | ||
| "server: %w", err) | ||
|
|
||
|
|
@@ -914,3 +919,30 @@ func byteSliceTo66ByteSlice(b []byte) ([musig2.PubNonceSize]byte, error) { | |
|
|
||
| return res, nil | ||
| } | ||
|
|
||
| // logInsufficientConfirmationsDetails extracts and logs the per-deposit | ||
| // confirmation details from a gRPC error if present. | ||
| func logInsufficientConfirmationsDetails(err error) { | ||
| st, ok := status.FromError(err) | ||
| if !ok { | ||
| return | ||
| } | ||
|
|
||
| for _, detail := range st.Details() { | ||
| confDetails, ok := | ||
| detail.(*swapserverrpc.InsufficientConfirmationsDetails) | ||
|
|
||
| if !ok { | ||
| continue | ||
| } | ||
|
|
||
| log.Warnf("Insufficient deposit confirmations, max wait: %d blocks", | ||
| confDetails.MaxBlocksToWait) | ||
|
|
||
| for _, dep := range confDetails.Deposits { | ||
| log.Warnf(" Deposit %s: %d/%d confirmations (need %d more blocks)", | ||
| dep.Outpoint, dep.CurrentConfirmations, | ||
| dep.RequiredConfirmations, dep.BlocksToWait) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -850,11 +850,14 @@ func (m *Manager) GetAllSwaps(ctx context.Context) ([]*StaticAddressLoopIn, | |
| return swaps, nil | ||
| } | ||
|
|
||
| // SelectDeposits sorts the deposits by amount in descending order, then by | ||
| // blocks-until-expiry in ascending order. It then selects the deposits that | ||
| // are needed to cover the amount requested without leaving a dust change. It | ||
| // returns an error if the sum of deposits minus dust is less than the requested | ||
| // amount. | ||
| // SelectDeposits sorts the deposits to optimize for successful swaps with | ||
| // dynamic confirmation requirements: 1) more confirmations first (higher chance | ||
| // of server acceptance), 2) larger amounts first (to minimize number of deposits | ||
| // used), 3) expiring sooner first (prioritize time-sensitive deposits). | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That means a set like If the client wants to prefer older deposits, they need a subset selection strategy that still minimizes input count and fee impact, or at least a second pass that prefers exact or lower-input covers before falling back to the confirmations-first prefix. |
||
| // Deposits that are too close to expiry are filtered out before sorting. | ||
| // It then selects the deposits that are needed to cover the amount requested | ||
| // without leaving a dust change. It returns an error if the sum of deposits | ||
| // minus dust is less than the requested amount. | ||
| func SelectDeposits(targetAmount btcutil.Amount, | ||
| unfilteredDeposits []*deposit.Deposit, csvExpiry uint32, | ||
| blockHeight uint32) ([]*deposit.Deposit, error) { | ||
|
|
@@ -875,18 +878,40 @@ func SelectDeposits(targetAmount btcutil.Amount, | |
| deposits = append(deposits, d) | ||
| } | ||
|
|
||
| // Sort the deposits by amount in descending order, then by | ||
| // blocks-until-expiry in ascending order. | ||
| // Sort deposits to optimize for successful swaps with dynamic | ||
| // confirmation requirements: | ||
| // 1. More confirmations first (higher chance of server acceptance). | ||
| // 2. Larger amounts first (to minimize number of deposits used). | ||
| // 3. Expiring sooner first (prioritize time-sensitive deposits). | ||
| sort.Slice(deposits, func(i, j int) bool { | ||
| if deposits[i].Value == deposits[j].Value { | ||
| iExp := uint32(deposits[i].ConfirmationHeight) + | ||
| csvExpiry - blockHeight | ||
| jExp := uint32(deposits[j].ConfirmationHeight) + | ||
| csvExpiry - blockHeight | ||
| // Primary: more confirmations first. Guard against the | ||
| // theoretical case where ConfirmationHeight > blockHeight | ||
| // (e.g. during a transient reorg inconsistency). | ||
| var iConfs, jConfs uint32 | ||
| if blockHeight > uint32(deposits[i].ConfirmationHeight) { | ||
| iConfs = blockHeight - | ||
| uint32(deposits[i].ConfirmationHeight) | ||
| } | ||
| if blockHeight > uint32(deposits[j].ConfirmationHeight) { | ||
| jConfs = blockHeight - | ||
| uint32(deposits[j].ConfirmationHeight) | ||
| } | ||
| if iConfs != jConfs { | ||
| return iConfs > jConfs | ||
| } | ||
|
|
||
| return iExp < jExp | ||
| // Secondary: larger amounts first. | ||
| if deposits[i].Value != deposits[j].Value { | ||
| return deposits[i].Value > deposits[j].Value | ||
| } | ||
| return deposits[i].Value > deposits[j].Value | ||
|
|
||
| // Tertiary: expiring sooner first. | ||
| iExp := uint32(deposits[i].ConfirmationHeight) + | ||
| csvExpiry - blockHeight | ||
| jExp := uint32(deposits[j].ConfirmationHeight) + | ||
| csvExpiry - blockHeight | ||
|
|
||
| return iExp < jExp | ||
| }) | ||
|
hieblmi marked this conversation as resolved.
|
||
|
|
||
| // Select the deposits that are needed to cover the swap amount without | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to hold this PR until we deploy the server side. Otherwise a new client might send its deposits too early to an old server.