Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions loopd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
depoCfg := &deposit.ManagerConfig{
AddressClient: staticAddressClient,
AddressManager: staticAddressManager,
LndClient: d.lnd.Client,
SwapClient: swapClient,
Store: depositStore,
WalletKit: d.lnd.WalletKit,
Expand Down
5 changes: 4 additions & 1 deletion staticaddr/deposit/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const (
// MinConfs is the minimum number of confirmations we require for a
// deposit to be considered available for loop-ins, coop-spends and
// timeouts.
MinConfs = 6
MinConfs = 3

// MaxConfs is unset since we don't require a max number of
// confirmations for deposits.
Expand Down Expand Up @@ -65,6 +65,9 @@ type ManagerConfig struct {

// Signer is the signer client that is used to sign transactions.
Signer lndclient.SignerClient

//
LndClient lndclient.LightningClient
}

// Manager manages the address state machines.
Expand Down
31 changes: 31 additions & 0 deletions staticaddr/loopin/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)

err = fmt.Errorf("unable to initiate the loop-in with the "+
"server: %w", err)

Expand Down Expand Up @@ -910,3 +915,29 @@ 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)
Comment on lines +933 to +934

Choose a reason for hiding this comment

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

medium

For better readability and to avoid string concatenation, you can include the full format string directly in log.Warnf.

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)
Comment on lines +937 to +940

Choose a reason for hiding this comment

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

medium

For better readability and to avoid string concatenation, you can include the full format string directly in log.Warnf.

log.Warnf("  Deposit %s: %d/%d confirmations (need %d more blocks)",
    dep.Outpoint, dep.CurrentConfirmations,
    dep.RequiredConfirmations, dep.BlocksToWait)

}
}
}
31 changes: 22 additions & 9 deletions staticaddr/loopin/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -888,18 +888,31 @@ 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 (to use 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.
iConfs := blockHeight - uint32(deposits[i].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
})

// Select the deposits that are needed to cover the swap amount without
Expand Down
19 changes: 13 additions & 6 deletions staticaddr/loopin/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ type testCase struct {

// TestSelectDeposits tests the selectDeposits function, which selects
// deposits that can cover a target value while respecting the dust limit.
// Sorting priority: 1) more confirmations first, 2) larger amounts first,
// 3) expiring sooner first.
func TestSelectDeposits(t *testing.T) {
// Note: confirmations = blockHeight - ConfirmationHeight
// Lower ConfirmationHeight means more confirmations at a given block.
d1, d2, d3, d4 := &deposit.Deposit{
Value: 1_000_000,
ConfirmationHeight: 5_000,
ConfirmationHeight: 5_000, // most confs at height 5100
}, &deposit.Deposit{
Value: 2_000_000,
ConfirmationHeight: 5_001,
Expand All @@ -38,7 +42,7 @@ func TestSelectDeposits(t *testing.T) {
ConfirmationHeight: 5_002,
}, &deposit.Deposit{
Value: 3_000_000,
ConfirmationHeight: 5_003,
ConfirmationHeight: 5_003, // fewest confs at height 5100
}
d1.Hash = chainhash.Hash{1}
d1.Index = 0
Expand All @@ -58,17 +62,20 @@ func TestSelectDeposits(t *testing.T) {
expectedErr: "",
},
{
name: "prefer larger deposit when both cover",
// d1 has more confirmations, so it's preferred even
// though d2 is larger.
name: "prefer more confirmed deposit over larger",
deposits: []*deposit.Deposit{d1, d2},
targetValue: 1_000_000,
expected: []*deposit.Deposit{d2},
expected: []*deposit.Deposit{d1},
expectedErr: "",
},
{
name: "prefer largest among three when one is enough",
// d1 has the most confirmations among d1, d2, d3.
name: "prefer most confirmed among three",
deposits: []*deposit.Deposit{d1, d2, d3},
targetValue: 1_000_000,
expected: []*deposit.Deposit{d3},
expected: []*deposit.Deposit{d1},
expectedErr: "",
},
{
Expand Down
Loading
Loading