lnwallet: remove availableLocalBalance field, add new AvailableBalance method

In this commit we do away with the existing availableLocalBalance
attribute and instead add a new, more accurate AvailableBalance method.
The new method will compute the available balance within the channel ,
assuming a new state was created at the instance the method was called.
This new method will now properly account for HTLC fees.

AvailableBalance is now called within AddHTLC in order to ensure we
don’t add any HTLC’s that are unable to be paid for from the PoV of the
fees on the commitment transaction.
This commit is contained in:
Olaoluwa Osuntokun 2017-11-09 23:08:50 -08:00
parent ded7c11bfe
commit c8479410d2
No known key found for this signature in database
GPG Key ID: 964EA263DD637C21

@ -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 {