diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 296fbf87..82ba1983 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -316,7 +316,12 @@ type LightningWallet struct { // monotonically integer. All requests concerning the channel MUST // carry a valid, active funding ID. fundingLimbo map[uint64]*ChannelReservation - limboMtx sync.RWMutex + + // reservationIDs maps a pending channel ID to the reservation ID used + // as key in the fundingLimbo map. Used to easily look up a channel + // reservation given a pending channel ID. + reservationIDs map[[32]byte]uint64 + limboMtx sync.RWMutex // lockedOutPoints is a set of the currently locked outpoint. This // information is kept in order to provide an easy way to unlock all @@ -347,6 +352,7 @@ func NewLightningWallet(Cfg Config) (*LightningWallet, error) { msgChan: make(chan interface{}, msgBufferSize), nextFundingID: 0, fundingLimbo: make(map[uint64]*ChannelReservation), + reservationIDs: make(map[[32]byte]uint64), lockedOutPoints: make(map[wire.OutPoint]struct{}), fundingIntents: make(map[[32]byte]chanfunding.Intent), quit: make(chan struct{}), @@ -417,6 +423,7 @@ func (l *LightningWallet) LockedOutpoints() []*wire.OutPoint { func (l *LightningWallet) ResetReservations() { l.nextFundingID = 0 l.fundingLimbo = make(map[uint64]*ChannelReservation) + l.reservationIDs = make(map[[32]byte]uint64) for outpoint := range l.lockedOutPoints { l.UnlockOutpoint(outpoint) @@ -523,16 +530,16 @@ func (l *LightningWallet) RegisterFundingIntent(expectedID [32]byte, // PsbtFundingVerify looks up a previously registered funding intent by its // pending channel ID and tries to advance the state machine by verifying the // passed PSBT. -func (l *LightningWallet) PsbtFundingVerify(pid [32]byte, +func (l *LightningWallet) PsbtFundingVerify(pendingChanID [32]byte, packet *psbt.Packet) error { l.intentMtx.Lock() defer l.intentMtx.Unlock() - intent, ok := l.fundingIntents[pid] + intent, ok := l.fundingIntents[pendingChanID] if !ok { return fmt.Errorf("no funding intent found for "+ - "pendingChannelID(%x)", pid[:]) + "pendingChannelID(%x)", pendingChanID[:]) } psbtIntent, ok := intent.(*chanfunding.PsbtIntent) if !ok { @@ -543,7 +550,49 @@ func (l *LightningWallet) PsbtFundingVerify(pid [32]byte, return fmt.Errorf("error verifying PSBT: %v", err) } - return nil + // Get the channel reservation for that corresponds to this pending + // channel ID. + l.limboMtx.Lock() + pid, ok := l.reservationIDs[pendingChanID] + if !ok { + l.limboMtx.Unlock() + return fmt.Errorf("no channel reservation found for "+ + "pendingChannelID(%x)", pendingChanID[:]) + } + + pendingReservation, ok := l.fundingLimbo[pid] + l.limboMtx.Unlock() + + if !ok { + return fmt.Errorf("no channel reservation found for "+ + "reservation ID %v", pid) + } + + // Now the the PSBT has been verified, we can again check whether the + // value reserved for anchor fee bumping is respected. + numAnchors, err := l.currentNumAnchorChans() + if err != nil { + return err + } + + // If this commit type is an anchor channel we add that to our counter, + // but only if we are contributing funds to the channel. This is done + // to still allow incoming channels even though we have no UTXOs + // available, as in bootstrapping phases. + if pendingReservation.partialState.ChanType.HasAnchors() && + intent.LocalFundingAmt() > 0 { + numAnchors++ + } + + // We check the reserve value again, this should already have been + // checked for regular FullIntents, but now the PSBT intent is also + // populated. + return l.WithCoinSelectLock(func() error { + _, err := l.CheckReservedValue( + intent.Inputs(), intent.Outputs(), numAnchors, + ) + return err + }) } // PsbtFundingFinalize looks up a previously registered funding intent by its @@ -727,6 +776,46 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg thawHeight = shimIntent.ThawHeight() } + // Now that we have a funding intent, we'll check whether funding a + // channel using it would violate our reserved value for anchor channel + // fee bumping. We first get our current number of anchor channels. + numAnchors, err := l.currentNumAnchorChans() + if err != nil { + fundingIntent.Cancel() + + req.err <- err + req.resp <- nil + return + } + + // If this commit type is an anchor channel we add that to our counter, + // but only if we are contributing funds to the channel. This is done + // to still allow incoming channels even though we have no UTXOs + // available, as in bootstrapping phases. + if req.CommitType == CommitmentTypeAnchorsZeroFeeHtlcTx && + fundingIntent.LocalFundingAmt() > 0 { + numAnchors++ + } + + // Check the reserved value using the inputs and outputs given by the + // intent. Not that for the PSBT intent type we don't yet have the + // funding tx ready, so this will always pass. We'll do another check + // when the PSBT has been verified. + err = l.WithCoinSelectLock(func() error { + _, err := l.CheckReservedValue( + fundingIntent.Inputs(), fundingIntent.Outputs(), + numAnchors, + ) + return err + }) + if err != nil { + fundingIntent.Cancel() + + req.err <- err + req.resp <- nil + return + } + // The total channel capacity will be the size of the funding output we // created plus the remote contribution. capacity := localFundingAmt + remoteFundingAmt @@ -761,6 +850,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg // request. l.limboMtx.Lock() l.fundingLimbo[id] = reservation + l.reservationIDs[req.PendingChanID] = id l.limboMtx.Unlock() // Funding reservation request successfully handled. The funding inputs @@ -770,6 +860,51 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg req.err <- nil } +// currentNumAnchorChans returns the current number of anchor channels the +// wallet should be ready to fee bump if needed. +func (l *LightningWallet) currentNumAnchorChans() (int, error) { + // Count all anchor channels that are open or pending + // open, or waiting close. + chans, err := l.Cfg.Database.FetchAllChannels() + if err != nil { + return 0, err + } + + var numAnchors int + for _, c := range chans { + if c.ChanType.HasAnchors() { + numAnchors++ + } + } + + // We also count pending close channels. + pendingClosed, err := l.Cfg.Database.FetchClosedChannels( + true, + ) + if err != nil { + return 0, err + } + + for _, c := range pendingClosed { + c, err := l.Cfg.Database.FetchHistoricalChannel( + &c.ChanPoint, + ) + if err != nil { + // We don't have a guarantee that all channels re found + // in the historical channels bucket, so we continue. + walletLog.Warnf("Unable to fetch historical "+ + "channel: %v", err) + continue + } + + if c.ChanType.HasAnchors() { + numAnchors++ + } + } + + return numAnchors, nil +} + // CheckReservedValue checks whether publishing a transaction with the given // inputs and outputs would violate the value we reserve in the wallet for // bumping the fee of anchor channels. The numAnchorChans argument should be @@ -984,6 +1119,7 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs delete(l.fundingLimbo, req.pendingFundingID) pid := pendingReservation.pendingChanID + delete(l.reservationIDs, pid) l.intentMtx.Lock() if intent, ok := l.fundingIntents[pid]; ok { @@ -1534,6 +1670,7 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs // Funding complete, this entry can be removed from limbo. l.limboMtx.Lock() delete(l.fundingLimbo, res.reservationID) + delete(l.reservationIDs, res.pendingChanID) l.limboMtx.Unlock() l.intentMtx.Lock()