lnwallet: check Reserved value on funding request

This commit is contained in:
Johan T. Halseth 2021-01-11 08:47:38 +01:00
parent 3cc31ae841
commit eaf97418be
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26

@ -316,6 +316,11 @@ type LightningWallet struct {
// monotonically integer. All requests concerning the channel MUST // monotonically integer. All requests concerning the channel MUST
// carry a valid, active funding ID. // carry a valid, active funding ID.
fundingLimbo map[uint64]*ChannelReservation fundingLimbo map[uint64]*ChannelReservation
// 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 limboMtx sync.RWMutex
// lockedOutPoints is a set of the currently locked outpoint. This // lockedOutPoints is a set of the currently locked outpoint. This
@ -347,6 +352,7 @@ func NewLightningWallet(Cfg Config) (*LightningWallet, error) {
msgChan: make(chan interface{}, msgBufferSize), msgChan: make(chan interface{}, msgBufferSize),
nextFundingID: 0, nextFundingID: 0,
fundingLimbo: make(map[uint64]*ChannelReservation), fundingLimbo: make(map[uint64]*ChannelReservation),
reservationIDs: make(map[[32]byte]uint64),
lockedOutPoints: make(map[wire.OutPoint]struct{}), lockedOutPoints: make(map[wire.OutPoint]struct{}),
fundingIntents: make(map[[32]byte]chanfunding.Intent), fundingIntents: make(map[[32]byte]chanfunding.Intent),
quit: make(chan struct{}), quit: make(chan struct{}),
@ -417,6 +423,7 @@ func (l *LightningWallet) LockedOutpoints() []*wire.OutPoint {
func (l *LightningWallet) ResetReservations() { func (l *LightningWallet) ResetReservations() {
l.nextFundingID = 0 l.nextFundingID = 0
l.fundingLimbo = make(map[uint64]*ChannelReservation) l.fundingLimbo = make(map[uint64]*ChannelReservation)
l.reservationIDs = make(map[[32]byte]uint64)
for outpoint := range l.lockedOutPoints { for outpoint := range l.lockedOutPoints {
l.UnlockOutpoint(outpoint) l.UnlockOutpoint(outpoint)
@ -523,16 +530,16 @@ func (l *LightningWallet) RegisterFundingIntent(expectedID [32]byte,
// PsbtFundingVerify looks up a previously registered funding intent by its // PsbtFundingVerify looks up a previously registered funding intent by its
// pending channel ID and tries to advance the state machine by verifying the // pending channel ID and tries to advance the state machine by verifying the
// passed PSBT. // passed PSBT.
func (l *LightningWallet) PsbtFundingVerify(pid [32]byte, func (l *LightningWallet) PsbtFundingVerify(pendingChanID [32]byte,
packet *psbt.Packet) error { packet *psbt.Packet) error {
l.intentMtx.Lock() l.intentMtx.Lock()
defer l.intentMtx.Unlock() defer l.intentMtx.Unlock()
intent, ok := l.fundingIntents[pid] intent, ok := l.fundingIntents[pendingChanID]
if !ok { if !ok {
return fmt.Errorf("no funding intent found for "+ return fmt.Errorf("no funding intent found for "+
"pendingChannelID(%x)", pid[:]) "pendingChannelID(%x)", pendingChanID[:])
} }
psbtIntent, ok := intent.(*chanfunding.PsbtIntent) psbtIntent, ok := intent.(*chanfunding.PsbtIntent)
if !ok { if !ok {
@ -543,7 +550,49 @@ func (l *LightningWallet) PsbtFundingVerify(pid [32]byte,
return fmt.Errorf("error verifying PSBT: %v", err) 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 // PsbtFundingFinalize looks up a previously registered funding intent by its
@ -727,6 +776,46 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
thawHeight = shimIntent.ThawHeight() 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 // The total channel capacity will be the size of the funding output we
// created plus the remote contribution. // created plus the remote contribution.
capacity := localFundingAmt + remoteFundingAmt capacity := localFundingAmt + remoteFundingAmt
@ -761,6 +850,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
// request. // request.
l.limboMtx.Lock() l.limboMtx.Lock()
l.fundingLimbo[id] = reservation l.fundingLimbo[id] = reservation
l.reservationIDs[req.PendingChanID] = id
l.limboMtx.Unlock() l.limboMtx.Unlock()
// Funding reservation request successfully handled. The funding inputs // Funding reservation request successfully handled. The funding inputs
@ -770,6 +860,51 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
req.err <- nil 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 // CheckReservedValue checks whether publishing a transaction with the given
// inputs and outputs would violate the value we reserve in the wallet for // inputs and outputs would violate the value we reserve in the wallet for
// bumping the fee of anchor channels. The numAnchorChans argument should be // 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) delete(l.fundingLimbo, req.pendingFundingID)
pid := pendingReservation.pendingChanID pid := pendingReservation.pendingChanID
delete(l.reservationIDs, pid)
l.intentMtx.Lock() l.intentMtx.Lock()
if intent, ok := l.fundingIntents[pid]; ok { 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. // Funding complete, this entry can be removed from limbo.
l.limboMtx.Lock() l.limboMtx.Lock()
delete(l.fundingLimbo, res.reservationID) delete(l.fundingLimbo, res.reservationID)
delete(l.reservationIDs, res.pendingChanID)
l.limboMtx.Unlock() l.limboMtx.Unlock()
l.intentMtx.Lock() l.intentMtx.Lock()