Merge pull request #2397 from halseth/reject-commitment-expected-fee-reset-bug
[bugfix] Process fee updates as any other update message
This commit is contained in:
commit
9c59ac4383
@ -681,9 +681,14 @@ func (l *channelLink) resolveFwdPkg(fwdPkg *channeldb.FwdPkg) (bool, error) {
|
|||||||
// If the package is fully acked but not completed, it must still have
|
// If the package is fully acked but not completed, it must still have
|
||||||
// settles and fails to propagate.
|
// settles and fails to propagate.
|
||||||
if !fwdPkg.SettleFailFilter.IsFull() {
|
if !fwdPkg.SettleFailFilter.IsFull() {
|
||||||
settleFails := lnwallet.PayDescsFromRemoteLogUpdates(
|
settleFails, err := lnwallet.PayDescsFromRemoteLogUpdates(
|
||||||
fwdPkg.Source, fwdPkg.Height, fwdPkg.SettleFails,
|
fwdPkg.Source, fwdPkg.Height, fwdPkg.SettleFails,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
l.errorf("Unable to process remote log updates: %v",
|
||||||
|
err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
l.processRemoteSettleFails(fwdPkg, settleFails)
|
l.processRemoteSettleFails(fwdPkg, settleFails)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -693,9 +698,14 @@ func (l *channelLink) resolveFwdPkg(fwdPkg *channeldb.FwdPkg) (bool, error) {
|
|||||||
// batch of adds presented to the sphinx router does not ever change.
|
// batch of adds presented to the sphinx router does not ever change.
|
||||||
var needUpdate bool
|
var needUpdate bool
|
||||||
if !fwdPkg.AckFilter.IsFull() {
|
if !fwdPkg.AckFilter.IsFull() {
|
||||||
adds := lnwallet.PayDescsFromRemoteLogUpdates(
|
adds, err := lnwallet.PayDescsFromRemoteLogUpdates(
|
||||||
fwdPkg.Source, fwdPkg.Height, fwdPkg.Adds,
|
fwdPkg.Source, fwdPkg.Height, fwdPkg.Adds,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
l.errorf("Unable to process remote log updates: %v",
|
||||||
|
err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
needUpdate = l.processRemoteAdds(fwdPkg, adds)
|
needUpdate = l.processRemoteAdds(fwdPkg, adds)
|
||||||
|
|
||||||
// If the link failed during processing the adds, we must
|
// If the link failed during processing the adds, we must
|
||||||
|
@ -1781,9 +1781,14 @@ func (s *Switch) loadChannelFwdPkgs(source lnwire.ShortChannelID) ([]*channeldb.
|
|||||||
// NOTE: This should mimic the behavior processRemoteSettleFails.
|
// NOTE: This should mimic the behavior processRemoteSettleFails.
|
||||||
func (s *Switch) reforwardSettleFails(fwdPkgs []*channeldb.FwdPkg) {
|
func (s *Switch) reforwardSettleFails(fwdPkgs []*channeldb.FwdPkg) {
|
||||||
for _, fwdPkg := range fwdPkgs {
|
for _, fwdPkg := range fwdPkgs {
|
||||||
settleFails := lnwallet.PayDescsFromRemoteLogUpdates(
|
settleFails, err := lnwallet.PayDescsFromRemoteLogUpdates(
|
||||||
fwdPkg.Source, fwdPkg.Height, fwdPkg.SettleFails,
|
fwdPkg.Source, fwdPkg.Height, fwdPkg.SettleFails,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to process remote log updates: %v",
|
||||||
|
err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switchPackets := make([]*htlcPacket, 0, len(settleFails))
|
switchPackets := make([]*htlcPacket, 0, len(settleFails))
|
||||||
for i, pd := range settleFails {
|
for i, pd := range settleFails {
|
||||||
|
@ -165,6 +165,10 @@ const (
|
|||||||
// original add entry from the remote party's log after the next state
|
// original add entry from the remote party's log after the next state
|
||||||
// transition.
|
// transition.
|
||||||
Settle
|
Settle
|
||||||
|
|
||||||
|
// FeeUpdate is an update type sent by the channel initiator that
|
||||||
|
// updates the fee rate used when signing the commitment transaction.
|
||||||
|
FeeUpdate
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a human readable string that uniquely identifies the target
|
// String returns a human readable string that uniquely identifies the target
|
||||||
@ -179,6 +183,8 @@ func (u updateType) String() string {
|
|||||||
return "MalformedFail"
|
return "MalformedFail"
|
||||||
case Settle:
|
case Settle:
|
||||||
return "Settle"
|
return "Settle"
|
||||||
|
case FeeUpdate:
|
||||||
|
return "FeeUpdate"
|
||||||
default:
|
default:
|
||||||
return "<unknown type>"
|
return "<unknown type>"
|
||||||
}
|
}
|
||||||
@ -190,7 +196,7 @@ func (u updateType) String() string {
|
|||||||
// the original added HTLC.
|
// the original added HTLC.
|
||||||
//
|
//
|
||||||
// TODO(roasbeef): LogEntry interface??
|
// TODO(roasbeef): LogEntry interface??
|
||||||
// * need to separate attrs for cancel/add/settle
|
// * need to separate attrs for cancel/add/settle/feeupdate
|
||||||
type PaymentDescriptor struct {
|
type PaymentDescriptor struct {
|
||||||
// RHash is the payment hash for this HTLC. The HTLC can be settled iff
|
// RHash is the payment hash for this HTLC. The HTLC can be settled iff
|
||||||
// the preimage to this hash is presented.
|
// the preimage to this hash is presented.
|
||||||
@ -345,7 +351,7 @@ type PaymentDescriptor struct {
|
|||||||
// NOTE: The provided `logUpdates` MUST corresponding exactly to either the Adds
|
// NOTE: The provided `logUpdates` MUST corresponding exactly to either the Adds
|
||||||
// or SettleFails in this channel's forwarding package at `height`.
|
// or SettleFails in this channel's forwarding package at `height`.
|
||||||
func PayDescsFromRemoteLogUpdates(chanID lnwire.ShortChannelID, height uint64,
|
func PayDescsFromRemoteLogUpdates(chanID lnwire.ShortChannelID, height uint64,
|
||||||
logUpdates []channeldb.LogUpdate) []*PaymentDescriptor {
|
logUpdates []channeldb.LogUpdate) ([]*PaymentDescriptor, error) {
|
||||||
|
|
||||||
// Allocate enough space to hold all of the payment descriptors we will
|
// Allocate enough space to hold all of the payment descriptors we will
|
||||||
// reconstruct, and also the list of pointers that will be returned to
|
// reconstruct, and also the list of pointers that will be returned to
|
||||||
@ -417,13 +423,18 @@ func PayDescsFromRemoteLogUpdates(chanID lnwire.ShortChannelID, height uint64,
|
|||||||
Index: uint16(i),
|
Index: uint16(i),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: UpdateFee is not expected since they are not forwarded.
|
||||||
|
case *lnwire.UpdateFee:
|
||||||
|
return nil, fmt.Errorf("unexpected update fee")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
payDescs = append(payDescs, pd)
|
payDescs = append(payDescs, pd)
|
||||||
payDescPtrs = append(payDescPtrs, &payDescs[i])
|
payDescPtrs = append(payDescPtrs, &payDescs[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
return payDescPtrs
|
return payDescPtrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitment represents a commitment to a new state within an active channel.
|
// commitment represents a commitment to a new state within an active channel.
|
||||||
@ -1200,6 +1211,9 @@ func compactLogs(ourLog, theirLog *updateLog,
|
|||||||
nextA = e.Next()
|
nextA = e.Next()
|
||||||
|
|
||||||
htlc := e.Value.(*PaymentDescriptor)
|
htlc := e.Value.(*PaymentDescriptor)
|
||||||
|
|
||||||
|
// We skip Adds, as they will be removed along with the
|
||||||
|
// fail/settles below.
|
||||||
if htlc.EntryType == Add {
|
if htlc.EntryType == Add {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -1218,6 +1232,16 @@ func compactLogs(ourLog, theirLog *updateLog,
|
|||||||
if remoteChainTail >= htlc.removeCommitHeightRemote &&
|
if remoteChainTail >= htlc.removeCommitHeightRemote &&
|
||||||
localChainTail >= htlc.removeCommitHeightLocal {
|
localChainTail >= htlc.removeCommitHeightLocal {
|
||||||
|
|
||||||
|
// Fee updates have no parent htlcs, so we only
|
||||||
|
// remove the update itself.
|
||||||
|
if htlc.EntryType == FeeUpdate {
|
||||||
|
logA.removeUpdate(htlc.LogIndex)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// The other types (fail/settle) do have a
|
||||||
|
// parent HTLC, so we'll remove that HTLC from
|
||||||
|
// the other log.
|
||||||
logA.removeUpdate(htlc.LogIndex)
|
logA.removeUpdate(htlc.LogIndex)
|
||||||
logB.removeHtlc(htlc.ParentIndex)
|
logB.removeHtlc(htlc.ParentIndex)
|
||||||
}
|
}
|
||||||
@ -1322,17 +1346,6 @@ type LightningChannel struct {
|
|||||||
localUpdateLog *updateLog
|
localUpdateLog *updateLog
|
||||||
remoteUpdateLog *updateLog
|
remoteUpdateLog *updateLog
|
||||||
|
|
||||||
// pendingFeeUpdate is set to the fee-per-kw we last sent (if we are
|
|
||||||
// channel initiator) or received (if non-initiator) in an update fee
|
|
||||||
// message, which haven't yet been included in a commitment. It will
|
|
||||||
// be nil if no fee update is un-committed.
|
|
||||||
pendingFeeUpdate *SatPerKWeight
|
|
||||||
|
|
||||||
// pendingAckFeeUpdate is set to the last committed fee update which is
|
|
||||||
// not yet ACKed. This value will be nil if a fee update hasn't been
|
|
||||||
// initiated.
|
|
||||||
pendingAckFeeUpdate *SatPerKWeight
|
|
||||||
|
|
||||||
// LocalFundingKey is the public key under control by the wallet that
|
// LocalFundingKey is the public key under control by the wallet that
|
||||||
// was used for the 2-of-2 funding output which created this channel.
|
// was used for the 2-of-2 funding output which created this channel.
|
||||||
LocalFundingKey *btcec.PublicKey
|
LocalFundingKey *btcec.PublicKey
|
||||||
@ -1562,6 +1575,23 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
|
|||||||
ShaOnionBlob: wireMsg.ShaOnionBlob,
|
ShaOnionBlob: wireMsg.ShaOnionBlob,
|
||||||
removeCommitHeightRemote: commitHeight,
|
removeCommitHeightRemote: commitHeight,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For fee updates we'll create a FeeUpdate type to add to the log. We
|
||||||
|
// reuse the amount field to hold the fee rate. Since the amount field
|
||||||
|
// is denominated in msat we won't lose precision when storing the
|
||||||
|
// sat/kw denominated feerate. Note that we set both the add and remove
|
||||||
|
// height to the same value, as we consider the fee update locked in by
|
||||||
|
// adding and removing it at the same height.
|
||||||
|
case *lnwire.UpdateFee:
|
||||||
|
pd = &PaymentDescriptor{
|
||||||
|
LogIndex: logUpdate.LogIndex,
|
||||||
|
Amount: lnwire.NewMSatFromSatoshis(
|
||||||
|
btcutil.Amount(wireMsg.FeePerKw),
|
||||||
|
),
|
||||||
|
EntryType: FeeUpdate,
|
||||||
|
addCommitHeightRemote: commitHeight,
|
||||||
|
removeCommitHeightRemote: commitHeight,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pd, nil
|
return pd, nil
|
||||||
@ -1765,15 +1795,6 @@ func (lc *LightningChannel) restoreStateLogs(
|
|||||||
// If we did have a dangling commit, then we'll examine which updates
|
// If we did have a dangling commit, then we'll examine which updates
|
||||||
// we included in that state and re-insert them into our update log.
|
// we included in that state and re-insert them into our update log.
|
||||||
for _, logUpdate := range pendingRemoteCommitDiff.LogUpdates {
|
for _, logUpdate := range pendingRemoteCommitDiff.LogUpdates {
|
||||||
// If the log update is a fee update, then it doesn't need an
|
|
||||||
// entry within the updateLog, so we'll just apply it and move
|
|
||||||
// on.
|
|
||||||
if feeUpdate, ok := logUpdate.UpdateMsg.(*lnwire.UpdateFee); ok {
|
|
||||||
newFeeRate := SatPerKWeight(feeUpdate.FeePerKw)
|
|
||||||
lc.pendingAckFeeUpdate = &newFeeRate
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
payDesc, err := lc.logUpdateToPayDesc(
|
payDesc, err := lc.logUpdateToPayDesc(
|
||||||
&logUpdate, lc.remoteUpdateLog, pendingHeight,
|
&logUpdate, lc.remoteUpdateLog, pendingHeight,
|
||||||
SatPerKWeight(pendingCommit.FeePerKw), pendingRemoteKeys,
|
SatPerKWeight(pendingCommit.FeePerKw), pendingRemoteKeys,
|
||||||
@ -1783,7 +1804,8 @@ func (lc *LightningChannel) restoreStateLogs(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if payDesc.EntryType == Add {
|
switch payDesc.EntryType {
|
||||||
|
case Add:
|
||||||
// The HtlcIndex of the added HTLC _must_ be equal to
|
// The HtlcIndex of the added HTLC _must_ be equal to
|
||||||
// the log's htlcCounter at this point. If it is not we
|
// the log's htlcCounter at this point. If it is not we
|
||||||
// panic to catch this.
|
// panic to catch this.
|
||||||
@ -1796,7 +1818,11 @@ func (lc *LightningChannel) restoreStateLogs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
lc.localUpdateLog.appendHtlc(payDesc)
|
lc.localUpdateLog.appendHtlc(payDesc)
|
||||||
} else {
|
|
||||||
|
case FeeUpdate:
|
||||||
|
lc.localUpdateLog.appendUpdate(payDesc)
|
||||||
|
|
||||||
|
default:
|
||||||
lc.localUpdateLog.appendUpdate(payDesc)
|
lc.localUpdateLog.appendUpdate(payDesc)
|
||||||
|
|
||||||
lc.remoteUpdateLog.markHtlcModified(payDesc.ParentIndex)
|
lc.remoteUpdateLog.markHtlcModified(payDesc.ParentIndex)
|
||||||
@ -2171,6 +2197,7 @@ func htlcIsDust(incoming, ourCommit bool, feePerKw SatPerKWeight,
|
|||||||
type htlcView struct {
|
type htlcView struct {
|
||||||
ourUpdates []*PaymentDescriptor
|
ourUpdates []*PaymentDescriptor
|
||||||
theirUpdates []*PaymentDescriptor
|
theirUpdates []*PaymentDescriptor
|
||||||
|
feePerKw SatPerKWeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchHTLCView returns all the candidate HTLC updates which should be
|
// fetchHTLCView returns all the candidate HTLC updates which should be
|
||||||
@ -2228,8 +2255,10 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
|
|||||||
// in order to update their commitment addition height, and to adjust
|
// in order to update their commitment addition height, and to adjust
|
||||||
// the balances on the commitment transaction accordingly.
|
// the balances on the commitment transaction accordingly.
|
||||||
htlcView := lc.fetchHTLCView(theirLogIndex, ourLogIndex)
|
htlcView := lc.fetchHTLCView(theirLogIndex, ourLogIndex)
|
||||||
ourBalance, theirBalance, _, filteredHTLCView, feePerKw :=
|
ourBalance, theirBalance, _, filteredHTLCView := lc.computeView(
|
||||||
lc.computeView(htlcView, remoteChain, true)
|
htlcView, remoteChain, true,
|
||||||
|
)
|
||||||
|
feePerKw := filteredHTLCView.feePerKw
|
||||||
|
|
||||||
// Determine how many current HTLCs are over the dust limit, and should
|
// Determine how many current HTLCs are over the dust limit, and should
|
||||||
// be counted for the purpose of fee calculation.
|
// be counted for the purpose of fee calculation.
|
||||||
@ -2431,9 +2460,9 @@ func (lc *LightningChannel) createCommitmentTx(c *commitment,
|
|||||||
|
|
||||||
// evaluateHTLCView processes all update entries in both HTLC update logs,
|
// evaluateHTLCView processes all update entries in both HTLC update logs,
|
||||||
// producing a final view which is the result of properly applying all adds,
|
// producing a final view which is the result of properly applying all adds,
|
||||||
// settles, and timeouts found in both logs. The resulting view returned
|
// settles, timeouts and fee updates found in both logs. The resulting view
|
||||||
// reflects the current state of HTLCs within the remote or local commitment
|
// returned reflects the current state of HTLCs within the remote or local
|
||||||
// chain.
|
// commitment chain, and the current commitment fee rate.
|
||||||
//
|
//
|
||||||
// If mutateState is set to true, then the add height of all added HTLCs
|
// If mutateState is set to true, then the add height of all added HTLCs
|
||||||
// will be set to nextHeight, and the remove height of all removed HTLCs
|
// will be set to nextHeight, and the remove height of all removed HTLCs
|
||||||
@ -2445,7 +2474,12 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
|||||||
theirBalance *lnwire.MilliSatoshi, nextHeight uint64,
|
theirBalance *lnwire.MilliSatoshi, nextHeight uint64,
|
||||||
remoteChain, mutateState bool) *htlcView {
|
remoteChain, mutateState bool) *htlcView {
|
||||||
|
|
||||||
newView := &htlcView{}
|
// We initialize the view's fee rate to the fee rate of the unfiltered
|
||||||
|
// view. If any fee updates are found when evaluating the view, it will
|
||||||
|
// be updated.
|
||||||
|
newView := &htlcView{
|
||||||
|
feePerKw: view.feePerKw,
|
||||||
|
}
|
||||||
|
|
||||||
// We use two maps, one for the local log and one for the remote log to
|
// We use two maps, one for the local log and one for the remote log to
|
||||||
// keep track of which entries we need to skip when creating the final
|
// keep track of which entries we need to skip when creating the final
|
||||||
@ -2458,7 +2492,17 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
|||||||
// skip sets and mutating the current chain state (crediting balances,
|
// skip sets and mutating the current chain state (crediting balances,
|
||||||
// etc) to reflect the settle/timeout entry encountered.
|
// etc) to reflect the settle/timeout entry encountered.
|
||||||
for _, entry := range view.ourUpdates {
|
for _, entry := range view.ourUpdates {
|
||||||
if entry.EntryType == Add {
|
switch entry.EntryType {
|
||||||
|
// Skip adds for now. They will be processed below.
|
||||||
|
case Add:
|
||||||
|
continue
|
||||||
|
|
||||||
|
// Process fee updates, updating the current feePerKw.
|
||||||
|
case FeeUpdate:
|
||||||
|
processFeeUpdate(
|
||||||
|
entry, nextHeight, remoteChain, mutateState,
|
||||||
|
newView,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2492,7 +2536,17 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
|||||||
nextHeight, remoteChain, true, mutateState)
|
nextHeight, remoteChain, true, mutateState)
|
||||||
}
|
}
|
||||||
for _, entry := range view.theirUpdates {
|
for _, entry := range view.theirUpdates {
|
||||||
if entry.EntryType == Add {
|
switch entry.EntryType {
|
||||||
|
// Skip adds for now. They will be processed below.
|
||||||
|
case Add:
|
||||||
|
continue
|
||||||
|
|
||||||
|
// Process fee updates, updating the current feePerKw.
|
||||||
|
case FeeUpdate:
|
||||||
|
processFeeUpdate(
|
||||||
|
entry, nextHeight, remoteChain, mutateState,
|
||||||
|
newView,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2642,6 +2696,38 @@ func processRemoveEntry(htlc *PaymentDescriptor, ourBalance,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processFeeUpdate processes a log update that updates the current commitment
|
||||||
|
// fee.
|
||||||
|
func processFeeUpdate(feeUpdate *PaymentDescriptor, nextHeight uint64,
|
||||||
|
remoteChain bool, mutateState bool, view *htlcView) {
|
||||||
|
|
||||||
|
// Fee updates are applied for all commitments after they are
|
||||||
|
// sent/received, so we consider them being added and removed at the
|
||||||
|
// same height.
|
||||||
|
var addHeight *uint64
|
||||||
|
var removeHeight *uint64
|
||||||
|
if remoteChain {
|
||||||
|
addHeight = &feeUpdate.addCommitHeightRemote
|
||||||
|
removeHeight = &feeUpdate.removeCommitHeightRemote
|
||||||
|
} else {
|
||||||
|
addHeight = &feeUpdate.addCommitHeightLocal
|
||||||
|
removeHeight = &feeUpdate.removeCommitHeightLocal
|
||||||
|
}
|
||||||
|
|
||||||
|
if *addHeight != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the update wasn't already locked in, update the current fee rate
|
||||||
|
// to reflect this update.
|
||||||
|
view.feePerKw = SatPerKWeight(feeUpdate.Amount.ToSatoshis())
|
||||||
|
|
||||||
|
if mutateState {
|
||||||
|
*addHeight = nextHeight
|
||||||
|
*removeHeight = nextHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// generateRemoteHtlcSigJobs generates a series of HTLC signature jobs for the
|
// generateRemoteHtlcSigJobs generates a series of HTLC signature jobs for the
|
||||||
// sig pool, along with a channel that if closed, will cancel any jobs after
|
// sig pool, along with a channel that if closed, will cancel any jobs after
|
||||||
// they have been submitted to the sigPool. This method is to be used when
|
// they have been submitted to the sigPool. This method is to be used when
|
||||||
@ -2792,21 +2878,8 @@ func (lc *LightningChannel) createCommitDiff(
|
|||||||
// out.
|
// out.
|
||||||
chanID := lnwire.NewChanIDFromOutPoint(&lc.channelState.FundingOutpoint)
|
chanID := lnwire.NewChanIDFromOutPoint(&lc.channelState.FundingOutpoint)
|
||||||
|
|
||||||
// If we have a fee update that we're waiting on an ACK of, then we'll
|
|
||||||
// create an entry so this is properly retransmitted. Note that we can
|
|
||||||
// only send an UpdateFee message if we're the initiator of the
|
|
||||||
// channel.
|
|
||||||
var logUpdates []channeldb.LogUpdate
|
|
||||||
if lc.channelState.IsInitiator && lc.pendingFeeUpdate != nil {
|
|
||||||
logUpdates = append(logUpdates, channeldb.LogUpdate{
|
|
||||||
UpdateMsg: &lnwire.UpdateFee{
|
|
||||||
ChanID: chanID,
|
|
||||||
FeePerKw: uint32(*lc.pendingFeeUpdate),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
logUpdates []channeldb.LogUpdate
|
||||||
ackAddRefs []channeldb.AddRef
|
ackAddRefs []channeldb.AddRef
|
||||||
settleFailRefs []channeldb.SettleFailRef
|
settleFailRefs []channeldb.SettleFailRef
|
||||||
openCircuitKeys []channeldb.CircuitKey
|
openCircuitKeys []channeldb.CircuitKey
|
||||||
@ -2887,6 +2960,15 @@ func (lc *LightningChannel) createCommitDiff(
|
|||||||
ShaOnionBlob: pd.ShaOnionBlob,
|
ShaOnionBlob: pd.ShaOnionBlob,
|
||||||
FailureCode: pd.FailCode,
|
FailureCode: pd.FailCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case FeeUpdate:
|
||||||
|
// The Amount field holds the feerate denominated in
|
||||||
|
// msat. Since feerates are only denominated in sat/kw,
|
||||||
|
// we can convert it without loss of precision.
|
||||||
|
logUpdate.UpdateMsg = &lnwire.UpdateFee{
|
||||||
|
ChanID: chanID,
|
||||||
|
FeePerKw: uint32(pd.Amount.ToSatoshis()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather the fwd pkg references from any settle or fail
|
// Gather the fwd pkg references from any settle or fail
|
||||||
@ -3074,16 +3156,6 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, erro
|
|||||||
// latest commitment update.
|
// latest commitment update.
|
||||||
lc.remoteCommitChain.addCommitment(newCommitView)
|
lc.remoteCommitChain.addCommitment(newCommitView)
|
||||||
|
|
||||||
// If we are the channel initiator then we would have signed any sent
|
|
||||||
// fee update at this point, so mark this update as pending ACK, and
|
|
||||||
// set pendingFeeUpdate to nil. We can do this since we know we won't
|
|
||||||
// sign any new commitment before receiving a RevokeAndAck, because of
|
|
||||||
// the revocation window of 1.
|
|
||||||
if lc.channelState.IsInitiator {
|
|
||||||
lc.pendingAckFeeUpdate = lc.pendingFeeUpdate
|
|
||||||
lc.pendingFeeUpdate = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return sig, htlcSigs, nil
|
return sig, htlcSigs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3483,7 +3555,7 @@ func ChanSyncMsg(c *channeldb.OpenChannel) (*lnwire.ChannelReestablish, error) {
|
|||||||
// HTLCs will be set to the next commitment height.
|
// HTLCs will be set to the next commitment height.
|
||||||
func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
||||||
updateState bool) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, int64,
|
updateState bool) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, int64,
|
||||||
*htlcView, SatPerKWeight) {
|
*htlcView) {
|
||||||
|
|
||||||
commitChain := lc.localCommitChain
|
commitChain := lc.localCommitChain
|
||||||
dustLimit := lc.localChanCfg.DustLimit
|
dustLimit := lc.localChanCfg.DustLimit
|
||||||
@ -3511,47 +3583,20 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
|||||||
}
|
}
|
||||||
nextHeight := commitChain.tip().height + 1
|
nextHeight := commitChain.tip().height + 1
|
||||||
|
|
||||||
// We evaluate the view at this stage, meaning settled and failed HTLCs
|
|
||||||
// will remove their corresponding added HTLCs. The resulting filtered
|
|
||||||
// view will only have Add entries left, making it easy to compare the
|
|
||||||
// channel constraints to the final commitment state.
|
|
||||||
filteredHTLCView := lc.evaluateHTLCView(view, &ourBalance,
|
|
||||||
&theirBalance, nextHeight, remoteChain, updateState)
|
|
||||||
|
|
||||||
// Initiate feePerKw to the last committed fee for this chain as we'll
|
// Initiate feePerKw to the last committed fee for this chain as we'll
|
||||||
// need this to determine which HTLCs are dust, and also the final fee
|
// need this to determine which HTLCs are dust, and also the final fee
|
||||||
// rate.
|
// rate.
|
||||||
feePerKw := commitChain.tip().feePerKw
|
view.feePerKw = commitChain.tip().feePerKw
|
||||||
|
|
||||||
// Check if any fee updates have taken place since that last
|
// We evaluate the view at this stage, meaning settled and failed HTLCs
|
||||||
// commitment.
|
// will remove their corresponding added HTLCs. The resulting filtered
|
||||||
if lc.channelState.IsInitiator {
|
// view will only have Add entries left, making it easy to compare the
|
||||||
switch {
|
// channel constraints to the final commitment state. If any fee
|
||||||
// We've sent an update_fee message since our last commitment,
|
// updates are found in the logs, the comitment fee rate should be
|
||||||
// and now are now creating a commitment that reflects the new
|
// changed, so we'll also set the feePerKw to this new value.
|
||||||
// fee update.
|
filteredHTLCView := lc.evaluateHTLCView(view, &ourBalance,
|
||||||
case remoteChain && lc.pendingFeeUpdate != nil:
|
&theirBalance, nextHeight, remoteChain, updateState)
|
||||||
feePerKw = *lc.pendingFeeUpdate
|
feePerKw := filteredHTLCView.feePerKw
|
||||||
|
|
||||||
// We've created a new commitment for the remote chain that
|
|
||||||
// includes a fee update, and have not received a commitment
|
|
||||||
// after the fee update has been ACKed.
|
|
||||||
case !remoteChain && lc.pendingAckFeeUpdate != nil:
|
|
||||||
feePerKw = *lc.pendingAckFeeUpdate
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch {
|
|
||||||
// We've received a fee update since the last local commitment,
|
|
||||||
// so we'll include the fee update in the current view.
|
|
||||||
case !remoteChain && lc.pendingFeeUpdate != nil:
|
|
||||||
feePerKw = *lc.pendingFeeUpdate
|
|
||||||
|
|
||||||
// Earlier we received a commitment that signed an earlier fee
|
|
||||||
// update, and now we must ACK that update.
|
|
||||||
case remoteChain && lc.pendingAckFeeUpdate != nil:
|
|
||||||
feePerKw = *lc.pendingAckFeeUpdate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now go through all HTLCs at this stage, to calculate the total
|
// Now go through all HTLCs at this stage, to calculate the total
|
||||||
// weight, needed to calculate the transaction fee.
|
// weight, needed to calculate the transaction fee.
|
||||||
@ -3574,7 +3619,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
totalCommitWeight := CommitWeight + totalHtlcWeight
|
totalCommitWeight := CommitWeight + totalHtlcWeight
|
||||||
return ourBalance, theirBalance, totalCommitWeight, filteredHTLCView, feePerKw
|
return ourBalance, theirBalance, totalCommitWeight, filteredHTLCView
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateCommitmentSanity is used to validate the current state of the
|
// validateCommitmentSanity is used to validate the current state of the
|
||||||
@ -3605,9 +3650,10 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
|
|||||||
ourInitialBalance := commitChain.tip().ourBalance
|
ourInitialBalance := commitChain.tip().ourBalance
|
||||||
theirInitialBalance := commitChain.tip().theirBalance
|
theirInitialBalance := commitChain.tip().theirBalance
|
||||||
|
|
||||||
ourBalance, theirBalance, commitWeight, filteredView, feePerKw := lc.computeView(
|
ourBalance, theirBalance, commitWeight, filteredView := lc.computeView(
|
||||||
view, remoteChain, false,
|
view, remoteChain, false,
|
||||||
)
|
)
|
||||||
|
feePerKw := filteredView.feePerKw
|
||||||
|
|
||||||
// Calculate the commitment fee, and subtract it from the initiator's
|
// Calculate the commitment fee, and subtract it from the initiator's
|
||||||
// balance.
|
// balance.
|
||||||
@ -4089,16 +4135,6 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig,
|
|||||||
localCommitmentView.sig = commitSig.ToSignatureBytes()
|
localCommitmentView.sig = commitSig.ToSignatureBytes()
|
||||||
lc.localCommitChain.addCommitment(localCommitmentView)
|
lc.localCommitChain.addCommitment(localCommitmentView)
|
||||||
|
|
||||||
// If we are not channel initiator, then the commitment just received
|
|
||||||
// would've signed any received fee update since last commitment. Mark
|
|
||||||
// any such fee update as pending ACK (so we remember to ACK it on our
|
|
||||||
// next commitment), and set pendingFeeUpdate to nil. We can do this
|
|
||||||
// since we won't receive any new commitment before ACKing.
|
|
||||||
if !lc.channelState.IsInitiator {
|
|
||||||
lc.pendingAckFeeUpdate = lc.pendingFeeUpdate
|
|
||||||
lc.pendingFeeUpdate = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4119,21 +4155,7 @@ func (lc *LightningChannel) FullySynced() bool {
|
|||||||
remoteUpdatesSynced := (lastLocalCommit.theirMessageIndex ==
|
remoteUpdatesSynced := (lastLocalCommit.theirMessageIndex ==
|
||||||
lastRemoteCommit.theirMessageIndex)
|
lastRemoteCommit.theirMessageIndex)
|
||||||
|
|
||||||
pendingFeeAck := false
|
return localUpdatesSynced && remoteUpdatesSynced
|
||||||
|
|
||||||
// If we have received a fee update which we haven't yet ACKed, then
|
|
||||||
// we owe a commitment.
|
|
||||||
if !lc.channelState.IsInitiator {
|
|
||||||
pendingFeeAck = lc.pendingAckFeeUpdate != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have sent a fee update which we haven't yet signed, then
|
|
||||||
// we owe a commitment.
|
|
||||||
if lc.channelState.IsInitiator {
|
|
||||||
pendingFeeAck = lc.pendingFeeUpdate != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return localUpdatesSynced && remoteUpdatesSynced && !pendingFeeAck
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeCurrentCommitment revokes the next lowest unrevoked commitment
|
// RevokeCurrentCommitment revokes the next lowest unrevoked commitment
|
||||||
@ -4258,6 +4280,12 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
|
|||||||
for e := lc.remoteUpdateLog.Front(); e != nil; e = e.Next() {
|
for e := lc.remoteUpdateLog.Front(); e != nil; e = e.Next() {
|
||||||
pd := e.Value.(*PaymentDescriptor)
|
pd := e.Value.(*PaymentDescriptor)
|
||||||
|
|
||||||
|
// Fee updates are local to this particular channel, and should
|
||||||
|
// never be forwarded.
|
||||||
|
if pd.EntryType == FeeUpdate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if pd.isForwarded {
|
if pd.isForwarded {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -5883,12 +5911,12 @@ func (lc *LightningChannel) availableBalance() (lnwire.MilliSatoshi, int64) {
|
|||||||
lc.localUpdateLog.logIndex)
|
lc.localUpdateLog.logIndex)
|
||||||
|
|
||||||
// Then compute our current balance for that view.
|
// Then compute our current balance for that view.
|
||||||
ourBalance, _, commitWeight, _, feePerKw :=
|
ourBalance, _, commitWeight, filteredView :=
|
||||||
lc.computeView(htlcView, false, false)
|
lc.computeView(htlcView, false, false)
|
||||||
|
|
||||||
// If we are the channel initiator, we must remember to subtract the
|
// If we are the channel initiator, we must remember to subtract the
|
||||||
// commitment fee from our available balance.
|
// commitment fee from our available balance.
|
||||||
commitFee := feePerKw.FeeForWeight(commitWeight)
|
commitFee := filteredView.feePerKw.FeeForWeight(commitWeight)
|
||||||
if lc.channelState.IsInitiator {
|
if lc.channelState.IsInitiator {
|
||||||
ourBalance -= lnwire.NewMSatFromSatoshis(commitFee)
|
ourBalance -= lnwire.NewMSatFromSatoshis(commitFee)
|
||||||
}
|
}
|
||||||
@ -5971,7 +5999,13 @@ func (lc *LightningChannel) UpdateFee(feePerKw SatPerKWeight) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lc.pendingFeeUpdate = &feePerKw
|
pd := &PaymentDescriptor{
|
||||||
|
LogIndex: lc.localUpdateLog.logIndex,
|
||||||
|
Amount: lnwire.NewMSatFromSatoshis(btcutil.Amount(feePerKw)),
|
||||||
|
EntryType: FeeUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
lc.localUpdateLog.appendUpdate(pd)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -5989,8 +6023,13 @@ func (lc *LightningChannel) ReceiveUpdateFee(feePerKw SatPerKWeight) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): or just modify to use the other balance?
|
// TODO(roasbeef): or just modify to use the other balance?
|
||||||
|
pd := &PaymentDescriptor{
|
||||||
|
LogIndex: lc.remoteUpdateLog.logIndex,
|
||||||
|
Amount: lnwire.NewMSatFromSatoshis(btcutil.Amount(feePerKw)),
|
||||||
|
EntryType: FeeUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
lc.pendingFeeUpdate = &feePerKw
|
lc.remoteUpdateLog.appendUpdate(pd)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1395,6 +1395,45 @@ func TestStateUpdatePersistence(t *testing.T) {
|
|||||||
t.Fatalf("unable to recv bob's htlc: %v", err)
|
t.Fatalf("unable to recv bob's htlc: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also add a fee update to the update logs.
|
||||||
|
fee := SatPerKWeight(111)
|
||||||
|
if err := aliceChannel.UpdateFee(fee); err != nil {
|
||||||
|
t.Fatalf("unable to send fee update")
|
||||||
|
}
|
||||||
|
if err := bobChannel.ReceiveUpdateFee(fee); err != nil {
|
||||||
|
t.Fatalf("unable to receive fee update")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method that asserts the expected number of updates are found
|
||||||
|
// in the update logs.
|
||||||
|
assertNumLogUpdates := func(numAliceUpdates, numBobUpdates int) {
|
||||||
|
if aliceChannel.localUpdateLog.Len() != numAliceUpdates {
|
||||||
|
t.Fatalf("expected %d local updates, found %d",
|
||||||
|
numAliceUpdates,
|
||||||
|
aliceChannel.localUpdateLog.Len())
|
||||||
|
}
|
||||||
|
if aliceChannel.remoteUpdateLog.Len() != numBobUpdates {
|
||||||
|
t.Fatalf("expected %d remote updates, found %d",
|
||||||
|
numBobUpdates,
|
||||||
|
aliceChannel.remoteUpdateLog.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
if bobChannel.localUpdateLog.Len() != numBobUpdates {
|
||||||
|
t.Fatalf("expected %d local updates, found %d",
|
||||||
|
numBobUpdates,
|
||||||
|
bobChannel.localUpdateLog.Len())
|
||||||
|
}
|
||||||
|
if bobChannel.remoteUpdateLog.Len() != numAliceUpdates {
|
||||||
|
t.Fatalf("expected %d remote updates, found %d",
|
||||||
|
numAliceUpdates,
|
||||||
|
bobChannel.remoteUpdateLog.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both nodes should now have Alice's 3 Adds and 1 FeeUpdate in the
|
||||||
|
// log, and Bob's 1 Add.
|
||||||
|
assertNumLogUpdates(4, 1)
|
||||||
|
|
||||||
// Next, Alice initiates a state transition to include the HTLC's she
|
// Next, Alice initiates a state transition to include the HTLC's she
|
||||||
// added above in a new commitment state.
|
// added above in a new commitment state.
|
||||||
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
if err := forceStateTransition(aliceChannel, bobChannel); err != nil {
|
||||||
@ -1409,6 +1448,16 @@ func TestStateUpdatePersistence(t *testing.T) {
|
|||||||
t.Fatalf("unable to complete bob's state transition: %v", err)
|
t.Fatalf("unable to complete bob's state transition: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After the state transition the fee update is fully locked in, and
|
||||||
|
// should've been removed from both channels' update logs.
|
||||||
|
if aliceChannel.localCommitChain.tail().feePerKw != fee {
|
||||||
|
t.Fatalf("fee not locked in")
|
||||||
|
}
|
||||||
|
if bobChannel.localCommitChain.tail().feePerKw != fee {
|
||||||
|
t.Fatalf("fee not locked in")
|
||||||
|
}
|
||||||
|
assertNumLogUpdates(3, 1)
|
||||||
|
|
||||||
// The latest commitment from both sides should have all the HTLCs.
|
// The latest commitment from both sides should have all the HTLCs.
|
||||||
numAliceOutgoing := aliceChannel.localCommitChain.tail().outgoingHTLCs
|
numAliceOutgoing := aliceChannel.localCommitChain.tail().outgoingHTLCs
|
||||||
numAliceIncoming := aliceChannel.localCommitChain.tail().incomingHTLCs
|
numAliceIncoming := aliceChannel.localCommitChain.tail().incomingHTLCs
|
||||||
@ -1999,6 +2048,88 @@ func TestUpdateFeeFail(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestUpdateFeeConcurrentSig tests that the channel can properly handle a fee
|
||||||
|
// update that it receives concurrently with signing its next commitment.
|
||||||
|
func TestUpdateFeeConcurrentSig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
paymentPreimage := bytes.Repeat([]byte{1}, 32)
|
||||||
|
paymentHash := sha256.Sum256(paymentPreimage)
|
||||||
|
htlc := &lnwire.UpdateAddHTLC{
|
||||||
|
PaymentHash: paymentHash,
|
||||||
|
Amount: btcutil.SatoshiPerBitcoin,
|
||||||
|
Expiry: uint32(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
// First Alice adds the outgoing HTLC to her local channel's state
|
||||||
|
// update log. Then Alice sends this wire message over to Bob who
|
||||||
|
// adds this htlc to his remote state update log.
|
||||||
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
||||||
|
t.Fatalf("unable to add htlc: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||||
|
t.Fatalf("unable to recv htlc: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate Alice sending update fee message to bob.
|
||||||
|
fee := SatPerKWeight(111)
|
||||||
|
if err := aliceChannel.UpdateFee(fee); err != nil {
|
||||||
|
t.Fatalf("unable to send fee update")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alice signs a commitment, and sends this to bob.
|
||||||
|
aliceSig, aliceHtlcSigs, err := aliceChannel.SignNextCommitment()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("alice unable to sign commitment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At the same time, Bob signs a commitment.
|
||||||
|
bobSig, bobHtlcSigs, err := bobChannel.SignNextCommitment()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bob unable to sign alice's commitment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...that Alice receives.
|
||||||
|
err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("alice unable to process bob's new commitment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now let Bob receive the fee update + commitment that Alice sent.
|
||||||
|
if err := bobChannel.ReceiveUpdateFee(fee); err != nil {
|
||||||
|
t.Fatalf("unable to receive fee update")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bob receives this signature message, and verifies that it is
|
||||||
|
// consistent with the state he had for Alice, including the received
|
||||||
|
// HTLC and fee update.
|
||||||
|
err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bob unable to process alice's new commitment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) == fee {
|
||||||
|
t.Fatalf("bob's feePerKw was unexpectedly locked in")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bob can revoke the prior commitment he had. This should lock in the
|
||||||
|
// fee update for him.
|
||||||
|
_, _, err = bobChannel.RevokeCurrentCommitment()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate bob revocation: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != fee {
|
||||||
|
t.Fatalf("bob's feePerKw was not locked in")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestUpdateFeeSenderCommits verifies that the state machine progresses as
|
// TestUpdateFeeSenderCommits verifies that the state machine progresses as
|
||||||
// expected if we send a fee update, and then the sender of the fee update
|
// expected if we send a fee update, and then the sender of the fee update
|
||||||
// sends a commitment signature.
|
// sends a commitment signature.
|
||||||
|
Loading…
Reference in New Issue
Block a user