Skip to content

Commit 3681ba6

Browse files
committed
peer: for RBF state machine block req if RBF iterating is outstanding
This fixes an issue in the itests in the restart case. We'd see an error like: ``` 2025-03-12 23:41:10.754 [ERR] PFSM state_machine.go:661: FSM(rbf_chan_closer(2f20725d9004f7fda7ef280f77dd8d419fd6669bda1a5231dd58d6f6597066e0:0)): Unable to apply event err="invalid state transition: received *chancloser.SendOfferEvent while in ClosingNegotiation(local=LocalOfferSent(proposed_fee=0.00000193 BTC), remote=ClosePending(txid=07229915459cb439bdb8ad4f5bf112dc6f42fca0192ea16a7d6dd05e607b92ae, party=Remote, fee_rate=1 sat/vb))" ``` We resolve this by waiting to send in the new request unil the old one has been completed.
1 parent 8df58a9 commit 3681ba6

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

lntest/harness.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,7 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode,
13331333
notifyRate := pendingClose.ClosePending.FeePerVbyte
13341334
if closeOpts.localTxOnly &&
13351335
notifyRate != int64(closeReq.SatPerVbyte) {
1336+
13361337
continue
13371338
}
13381339

peer/brontide.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3981,6 +3981,57 @@ func newRPCShutdownInit(req *htlcswitch.ChanClose) shutdownInit {
39813981
)
39823982
}
39833983

3984+
// waitUntilRbfCoastClear waits until the RBF co-op close state machine has
3985+
// advanced to a terminal state before attempting another fee bump.
3986+
func waitUntilRbfCoastClear(ctx context.Context,
3987+
rbfCloser *chancloser.RbfChanCloser) error {
3988+
3989+
coopCloseStates := rbfCloser.RegisterStateEvents()
3990+
newStateChan := coopCloseStates.NewItemCreated.ChanOut()
3991+
defer rbfCloser.RemoveStateSub(coopCloseStates)
3992+
3993+
isTerminalState := func(newState chancloser.RbfState) bool {
3994+
// If we're not in the negotiation sub-state, then we aren't at
3995+
// the terminal state yet.
3996+
state, ok := newState.(*chancloser.ClosingNegotiation)
3997+
if !ok {
3998+
return false
3999+
}
4000+
4001+
localState := state.PeerState.GetForParty(lntypes.Local)
4002+
4003+
// If this isn't the close pending state, we aren't at the
4004+
// terminal state yet.
4005+
_, ok = localState.(*chancloser.ClosePending)
4006+
4007+
return ok
4008+
}
4009+
4010+
// Before we enter the subscription loop below, check to see if we're
4011+
// already in the terminal state.
4012+
rbfState, err := rbfCloser.CurrentState()
4013+
if err != nil {
4014+
return err
4015+
}
4016+
if isTerminalState(rbfState) {
4017+
return nil
4018+
}
4019+
4020+
peerLog.Debugf("Waiting for RBF iteration to complete...")
4021+
4022+
for {
4023+
select {
4024+
case newState := <-newStateChan:
4025+
if isTerminalState(newState) {
4026+
return nil
4027+
}
4028+
4029+
case <-ctx.Done():
4030+
return fmt.Errorf("context canceled")
4031+
}
4032+
}
4033+
}
4034+
39844035
// startRbfChanCloser kicks off the co-op close process using the new RBF based
39854036
// co-op close protocol. This is called when we're the one that's initiating
39864037
// the cooperative channel close.
@@ -4070,6 +4121,17 @@ func (p *Brontide) startRbfChanCloser(shutdown shutdownInit,
40704121
// the prior fee rate), or we've sent an offer, then we'll
40714122
// trigger a new offer event.
40724123
case *chancloser.ClosingNegotiation:
4124+
// Before we send the event below, we'll wait until
4125+
// we're in a semi-terminal state.
4126+
err := waitUntilRbfCoastClear(ctx, rbfCloser)
4127+
if err != nil {
4128+
peerLog.Warnf("ChannelPoint(%v): unable to "+
4129+
"wait for coast to clear: %v",
4130+
chanPoint, err)
4131+
4132+
return
4133+
}
4134+
40734135
event := chancloser.ProtocolEvent(
40744136
&chancloser.SendOfferEvent{
40754137
TargetFeeRate: feeRate,

0 commit comments

Comments
 (0)