lnwallet: check Reserved value on funding request
This commit is contained in:
parent
3cc31ae841
commit
eaf97418be
@ -316,7 +316,12 @@ 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
|
||||||
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
|
// lockedOutPoints is a set of the currently locked outpoint. This
|
||||||
// information is kept in order to provide an easy way to unlock all
|
// 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),
|
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()
|
||||||
|
Loading…
Reference in New Issue
Block a user