diff --git a/lnwallet/channel.go b/lnwallet/channel.go index b1675a3c..3ab638e0 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1304,10 +1304,6 @@ func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier, go lc.closeObserver(channelCloseNtfn) } - // Initialize the available local balance - s := lc.StateSnapshot() - lc.availableLocalBalance = s.LocalBalance - // Finally, we'll kick of the signature job pool to handle any upcoming // commitment state generation and validation. if lc.sigPool.Start(); err != nil { @@ -3146,6 +3142,8 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, htlcCount++ } + // TODO(roasbeef): call availableBalance in here re-using htlcView + // Run through all the HTLCs that will be covered by this transaction // in order to calculate theirs count. view := lc.fetchHTLCView(theirLogCounter, ourLogCounter) @@ -3662,10 +3660,33 @@ func (lc *LightningChannel) AddHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, error) return 0, err } - if lc.availableLocalBalance < htlc.Amount { + // To ensure that we can actually fully accept this new HTLC, we'll + // calculate the current available bandwidth, and subtract the value + // ofthe HTLC from it. + availableBalance := lc.availableBalance() + availableBalance -= htlc.Amount + + feePerKw := lc.channelState.LocalCommitment.FeePerKw + dustLimit := lc.channelState.LocalChanCfg.DustLimit + htlcIsDust := htlcIsDust( + false, true, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, + ) + + // If this HTLC is not dust, and we're the initiator, then we'll also + // subtract the amount we'll need to pay in fees for this HTLC. + if !htlcIsDust && lc.channelState.IsInitiator { + htlcFee := lnwire.NewMSatFromSatoshis( + btcutil.Amount((int64(feePerKw) * HtlcWeight) / 1000), + ) + availableBalance -= htlcFee + } + + // If this value is negative, then we can't accept the HTLC, so we'll + // reject it with an error. + if availableBalance < 0 { + // TODO(roasbeef): also needs to respect reservation return 0, ErrInsufficientBalance } - lc.availableLocalBalance -= htlc.Amount pd := &PaymentDescriptor{ EntryType: Add, @@ -3745,7 +3766,6 @@ func (lc *LightningChannel) SettleHTLC(preimage [32]byte) (uint64, delete(lc.rHashMap, paymentHash) } - lc.availableLocalBalance += pd.Amount return targetHTLC.HtlcIndex, targetHTLC.Amount, nil } @@ -3879,7 +3899,6 @@ func (lc *LightningChannel) ReceiveFailHTLC(htlcIndex uint64, } lc.remoteUpdateLog.appendUpdate(pd) - lc.availableLocalBalance += pd.Amount return htlc.Amount, nil } @@ -4532,6 +4551,141 @@ func (lc *LightningChannel) DeleteState(c *channeldb.ChannelCloseSummary) error return lc.channelState.CloseChannel(c) } +// AvailableBalance returns the current available balance within the channel. +// By available balance, we mean that if at this very instance s new commitment +// were to be created which evals all the log entries, what would our available +// balance me. This method is useful when deciding if a given channel can +// accept an HTLC in the multi-hop forwarding scenario. +func (lc *LightningChannel) AvailableBalance() lnwire.MilliSatoshi { + lc.RLock() + defer lc.RUnlock() + + return lc.availableBalance() +} + +// availableBalance is the private, non mutexed version of AvailableBalance. +// This method is provided so methods that already hold the lock can access +// this method. +func (lc *LightningChannel) availableBalance() lnwire.MilliSatoshi { + // First, we'll grab the current local balance. If we're the initiator + // of the channel then we paid the fees on the last commitment state, + // so we'll re apply those. + settledBalance := lc.channelState.LocalCommitment.LocalBalance + if lc.channelState.IsInitiator { + settledBalance += lnwire.NewMSatFromSatoshis( + lc.localCommitChain.tip().fee, + ) + } + + // Next we'll grab the current set of log updates that are still active + // and haven't been garbage collected. + htlcView := lc.fetchHTLCView(lc.remoteUpdateLog.logIndex, + lc.localUpdateLog.logIndex) + feePerKw := lc.channelState.LocalCommitment.FeePerKw + dustLimit := lc.channelState.LocalChanCfg.DustLimit + + // We'll now re-compute the current weight of all the active HTLC's. We + // make sure to skip any HTLC's that would be dust on our version of + // the commitment transaction. + var totalHtlcWeight int64 + for _, htlc := range lc.channelState.LocalCommitment.Htlcs { + if htlcIsDust(false, true, feePerKw, htlc.Amt.ToSatoshis(), + dustLimit) { + continue + } + + totalHtlcWeight += HtlcWeight + } + + // Next we'll run through our set of updates and modify the + // settledBalance and totalHtlcWeight fields accordingly. + for _, entry := range htlcView.ourUpdates { + switch { + + // For any new HTLC's added as a part of this state, we'll + // subtract the total balance, and tally the weight increase if + // it isn't dust. + case entry.EntryType == Add && entry.addCommitHeightLocal == 0: + settledBalance -= entry.Amount + + if htlcIsDust(false, true, feePerKw, entry.Amount.ToSatoshis(), + dustLimit) { + continue + + } + + totalHtlcWeight += HtlcWeight + + // For any new HTLC's we newly settled as part of this state, + // we'll subtract the HTLC weight and increase our balance + // accordingly. + case entry.EntryType == Settle && entry.removeCommitHeightLocal == 0: + totalHtlcWeight -= HtlcWeight + + settledBalance += entry.Amount + + // For any new fails added as a part of this state, we'll + // subtract the weight of the HTLC we're failing. + case entry.EntryType == Fail && entry.removeCommitHeightLocal == 0: + fallthrough + case entry.EntryType == MalformedFail && entry.removeCommitHeightLocal == 0: + totalHtlcWeight -= HtlcWeight + } + } + for _, entry := range htlcView.theirUpdates { + switch { + // If the remote party has an HTLC that will be included as + // part of this state, then we'll account for the additional + // weight of the HTLC. + case entry.EntryType == Add && entry.addCommitHeightLocal == 0: + if htlcIsDust(true, true, feePerKw, entry.Amount.ToSatoshis(), + dustLimit) { + continue + + } + + totalHtlcWeight += HtlcWeight + + // If the remote party is settling one of our HTLC's for the + // first time as part of this state, then we'll subtract the + // weight of the HTLC. + case entry.EntryType == Settle && entry.removeCommitHeightLocal == 0: + totalHtlcWeight -= HtlcWeight + + // For any HTLC's that they're failing as a part of the next, + // state, we'll subtract the weight of the HTLC and also credit + // ourselves back the value of the HTLC. + case entry.EntryType == Fail && entry.removeCommitHeightLocal == 0: + fallthrough + case entry.EntryType == MalformedFail && entry.removeCommitHeightLocal == 0: + totalHtlcWeight -= HtlcWeight + + settledBalance += entry.Amount + } + + } + + // If we subtracted dust HTLC's, then we'll need to reset the weight of + // the HTLCs back to zero. + if totalHtlcWeight < 0 { + totalHtlcWeight = 0 + } + + // If we're the initiator then we need to pay fees for this state, so + // taking into account the number of active HTLC's we'll calculate the + // fee that must be paid. + if lc.channelState.IsInitiator { + totalCommitWeight := CommitWeight + totalHtlcWeight + additionalFee := lnwire.NewMSatFromSatoshis( + btcutil.Amount((int64(feePerKw) * totalCommitWeight) / 1000), + ) + + settledBalance -= additionalFee + } + + return settledBalance +} + // StateSnapshot returns a snapshot of the current fully committed state within // the channel. func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot {