Skip to content

Commit 62a52b4

Browse files
committed
multi: Utxo restriction single funding case.
Restrict the utxo selection when opening a single internal wallet funded backed channel.
1 parent 2a88cf8 commit 62a52b4

File tree

6 files changed

+87
-12
lines changed

6 files changed

+87
-12
lines changed

funding/manager.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,12 @@ type Config struct {
537537
// AliasManager is an implementation of the aliasHandler interface that
538538
// abstracts away the handling of many alias functions.
539539
AliasManager aliasHandler
540+
541+
// IsSweeperOutpoint queries the sweeper store for successfully
542+
// published sweeps. This is useful to decide for the internal wallet
543+
// backed funding flow to not use utxos still being swept by the sweeper
544+
// subsystem.
545+
IsSweeperOutpoint func(wire.OutPoint) bool
540546
}
541547

542548
// Manager acts as an orchestrator/bridge between the wallet's
@@ -4600,10 +4606,26 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
46004606
MinConfs: msg.MinConfs,
46014607
CommitType: commitType,
46024608
ChanFunder: msg.ChanFunder,
4603-
ZeroConf: zeroConf,
4604-
OptionScidAlias: scid,
4605-
ScidAliasFeature: scidFeatureVal,
4606-
Memo: msg.Memo,
4609+
// Unconfirmed Utxos which are marked by the sweeper subsystem
4610+
// are excluded from the coin selection because they are not
4611+
// final and can be RBFed by the sweeper subsystem.
4612+
AllowUtxoForFunding: func(u lnwallet.Utxo) bool {
4613+
// Utxos with at least 1 confirmation are safe to use
4614+
// for channel openings because they don't bare the risk
4615+
// of being replaced (BIP 125 RBF).
4616+
if u.Confirmations > 0 {
4617+
return true
4618+
}
4619+
4620+
// Query the sweeper storage to make sure we don't use
4621+
// an unconfirmed utxo still in use by the sweeper
4622+
// subsystem.
4623+
return !f.cfg.IsSweeperOutpoint(u.OutPoint)
4624+
},
4625+
ZeroConf: zeroConf,
4626+
OptionScidAlias: scid,
4627+
ScidAliasFeature: scidFeatureVal,
4628+
Memo: msg.Memo,
46074629
}
46084630

46094631
reservation, err := f.cfg.Wallet.InitChannelReservation(req)

lnwallet/chanfunding/wallet_assembler.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,8 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
334334
}
335335
for _, coin := range manuallySelectedCoins {
336336
if _, ok := unspent[coin.OutPoint]; !ok {
337-
return fmt.Errorf("outpoint already spent: %v",
337+
return fmt.Errorf("outpoint already spent or "+
338+
"locked by another subsystem: %v",
338339
coin.OutPoint)
339340
}
340341
}

lnwallet/test/test_interface.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2963,7 +2963,9 @@ func testSingleFunderExternalFundingTx(miner *rpctest.Harness,
29632963
// we'll create a new chanfunding.Assembler hacked by Alice's wallet.
29642964
aliceChanFunder := chanfunding.NewWalletAssembler(
29652965
chanfunding.WalletConfig{
2966-
CoinSource: lnwallet.NewCoinSource(alice),
2966+
CoinSource: lnwallet.NewCoinSource(
2967+
alice, nil,
2968+
),
29672969
CoinSelectLocker: alice,
29682970
CoinLeaser: alice,
29692971
Signer: alice.Cfg.Signer,

lnwallet/wallet.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,15 @@ type InitFundingReserveMsg struct {
184184
// used.
185185
ChanFunder chanfunding.Assembler
186186

187+
// AllowUtxoForFunding enables the channel funding workflow to restrict
188+
// the selection of utxos when selecting the inputs for the channel
189+
// opening. This does ONLY apply for the internal wallet backed channel
190+
// opening case.
191+
//
192+
// NOTE: This is very useful when opening channels with unconfirmed
193+
// inputs to make sure stable non-replaceable inputs are used.
194+
AllowUtxoForFunding func(Utxo) bool
195+
187196
// ZeroConf is a boolean that is true if a zero-conf channel was
188197
// negotiated.
189198
ZeroConf bool
@@ -849,7 +858,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
849858
// P2WPKH dust limit and to avoid threading through two
850859
// different dust limits.
851860
cfg := chanfunding.WalletConfig{
852-
CoinSource: &CoinSource{l},
861+
CoinSource: NewCoinSource(
862+
l, req.AllowUtxoForFunding,
863+
),
853864
CoinSelectLocker: l,
854865
CoinLeaser: l,
855866
Signer: l.Cfg.Signer,
@@ -2525,12 +2536,16 @@ func (l *LightningWallet) CancelRebroadcast(txid chainhash.Hash) {
25252536
// CoinSource is a wrapper around the wallet that implements the
25262537
// chanfunding.CoinSource interface.
25272538
type CoinSource struct {
2528-
wallet *LightningWallet
2539+
wallet *LightningWallet
2540+
allowUtxo func(Utxo) bool
25292541
}
25302542

25312543
// NewCoinSource creates a new instance of the CoinSource wrapper struct.
2532-
func NewCoinSource(w *LightningWallet) *CoinSource {
2533-
return &CoinSource{wallet: w}
2544+
func NewCoinSource(w *LightningWallet, allowUtxo func(Utxo) bool) *CoinSource {
2545+
return &CoinSource{
2546+
wallet: w,
2547+
allowUtxo: allowUtxo,
2548+
}
25342549
}
25352550

25362551
// ListCoins returns all UTXOs from the source that have between
@@ -2546,7 +2561,18 @@ func (c *CoinSource) ListCoins(minConfs int32,
25462561
}
25472562

25482563
var coins []wallet.Coin
2564+
25492565
for _, utxo := range utxos {
2566+
// If there is a filter function supplied all utxos not adhering
2567+
// to these conditions will be discared.
2568+
if c.allowUtxo != nil && !c.allowUtxo(*utxo) {
2569+
walletLog.Infof("Cannot use unconfirmed "+
2570+
"utxo=%v because it is unstable and could be "+
2571+
"replaced", utxo.OutPoint)
2572+
2573+
continue
2574+
}
2575+
25502576
coins = append(coins, wallet.Coin{
25512577
TxOut: wire.TxOut{
25522578
Value: int64(utxo.Value),

server.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,8 +1489,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
14891489
EnableUpfrontShutdown: cfg.EnableUpfrontShutdown,
14901490
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
14911491
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
1492-
DeleteAliasEdge: deleteAliasEdge,
1493-
AliasManager: s.aliasMgr,
1492+
DeleteAliasEdge: deleteAliasEdge,
1493+
AliasManager: s.aliasMgr,
1494+
IsSweeperOutpoint: s.sweeper.IsSweeperOutpoint,
14941495
})
14951496
if err != nil {
14961497
return nil, err

sweep/sweeper.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,3 +1735,26 @@ func (s *UtxoSweeper) handleBumpEvent(r *BumpResult) error {
17351735

17361736
return nil
17371737
}
1738+
1739+
// IsSweeperOutpoint determines whether the outpoint was created by the sweeper.
1740+
//
1741+
// NOTE: It is enough to check the txid because the sweeper will create
1742+
// outpoints which solely belong to the internal LND wallet.
1743+
func (s *UtxoSweeper) IsSweeperOutpoint(op wire.OutPoint) bool {
1744+
found, err := s.cfg.Store.IsOurTx(op.Hash)
1745+
// In case there is an error fetching the transaction details from the
1746+
// sweeper store we assume the outpoint is still used by the sweeper
1747+
// (worst case scenario).
1748+
//
1749+
// TODO(ziggie): Ensure that confirmed outpoints are deleted from the
1750+
// bucket.
1751+
if err != nil && !errors.Is(err, errNoTxHashesBucket) {
1752+
log.Errorf("failed to fetch info for outpoint(%v:%d) "+
1753+
"with: %v, we assume it is still in use by the sweeper",
1754+
op.Hash, op.Index, err)
1755+
1756+
return true
1757+
}
1758+
1759+
return found
1760+
}

0 commit comments

Comments
 (0)