@@ -2895,6 +2895,137 @@ func TestUpdateFeeReceiverCommits(t *testing.T) {
28952895 require .NoError (t , err , "bob unable to process alice's revocation" )
28962896}
28972897
2898+ // TestChanSyncUpdateFeeRestartAfterRevoke tests a scenario where Alice (channel
2899+ // initiator) sends update_fee and commit_sig, Bob responds with revoke_and_ack
2900+ // and commit_sig, but Alice receives only the revoke_and_ack and then restarts
2901+ // before Bob's commit_sig arrives.
2902+ //
2903+ // After restarting, channel reestablishment causes Bob to retransmit his
2904+ // commit_sig. Alice should be able to validate it, but currently fails because
2905+ // the fee update is not properly restored in her update log from disk.
2906+ //
2907+ // Root cause: during ReceiveRevocation, the fee update is persisted under
2908+ // remoteUnsignedLocalUpdatesKey. However, restoreStateLogs only handles
2909+ // HTLC-type updates (UpdateFulfillHTLC, UpdateFailHTLC, etc.) in that
2910+ // restoration path and silently skips UpdateFee via "default: continue". So
2911+ // after restart Alice's update log is empty, she computes the expected
2912+ // commitment with the old fee rate, Bob's signature (covering the new fee
2913+ // rate) fails verification, and LND incorrectly triggers a force close.
2914+ func TestChanSyncUpdateFeeRestartAfterRevoke (t * testing.T ) {
2915+ t .Parallel ()
2916+
2917+ aliceChannel , bobChannel , err := CreateTestChannels (
2918+ t , channeldb .SingleFunderTweaklessBit ,
2919+ )
2920+ require .NoError (t , err , "unable to create test channels" )
2921+
2922+ // Alice is the channel initiator and sends an update_fee to Bob.
2923+ newFee := chainfee .SatPerKWeight (12500 )
2924+ err = aliceChannel .UpdateFee (newFee )
2925+ require .NoError (t , err , "alice unable to update fee" )
2926+ err = bobChannel .ReceiveUpdateFee (newFee )
2927+ require .NoError (t , err , "bob unable to receive fee update" )
2928+
2929+ // Alice signs the next commitment, which includes the fee update.
2930+ aliceNewCommit , err := aliceChannel .SignNextCommitment (ctxb )
2931+ require .NoError (t , err , "alice unable to sign commitment" )
2932+
2933+ // Bob receives and processes Alice's commitment.
2934+ err = bobChannel .ReceiveNewCommitment (aliceNewCommit .CommitSigs )
2935+ require .NoError (t , err , "bob unable to process alice's new commitment" )
2936+
2937+ // Bob revokes his old commitment (producing a revoke_and_ack)...
2938+ bobRevocation , _ , _ , err := bobChannel .RevokeCurrentCommitment ()
2939+ require .NoError (t , err , "bob unable to revoke commitment" )
2940+
2941+ // ...and signs a new commitment for Alice.
2942+ _ , err = bobChannel .SignNextCommitment (ctxb )
2943+ require .NoError (t , err , "bob unable to sign alice's commitment" )
2944+
2945+ // Alice receives Bob's revoke_and_ack. This advances Bob's remote tail
2946+ // in Alice's view. During ReceiveRevocation, the still-unacknowledged
2947+ // fee update is written to disk under remoteUnsignedLocalUpdatesKey.
2948+ //
2949+ // The connection then drops before Alice receives Bob's commit_sig.
2950+ _ , _ , err = aliceChannel .ReceiveRevocation (bobRevocation )
2951+ require .NoError (t , err , "alice unable to process bob's revocation" )
2952+
2953+ // Alice restarts. Due to the bug, the fee update stored under
2954+ // remoteUnsignedLocalUpdatesKey is not restored into Alice's update
2955+ // log (UpdateFee is skipped by the default: continue in restoreStateLogs).
2956+ aliceChannel , err = restartChannel (aliceChannel )
2957+ require .NoError (t , err , "unable to restart alice channel" )
2958+
2959+ // Both sides produce their ChannelReestablish messages.
2960+ aliceSyncMsg , err := aliceChannel .channelState .ChanSyncMsg ()
2961+ require .NoError (t , err , "alice unable to produce chan sync msg" )
2962+ bobSyncMsg , err := bobChannel .channelState .ChanSyncMsg ()
2963+ require .NoError (t , err , "bob unable to produce chan sync msg" )
2964+
2965+ // Alice processes Bob's ChannelReestablish. Bob's NextLocalCommitHeight
2966+ // is 2, and Alice's remoteTipHeight is 1, so 2 == 1+1: Alice concludes
2967+ // the remote side is in sync and has nothing to retransmit.
2968+ aliceMsgsToSend , _ , _ , err := aliceChannel .ProcessChanSyncMsg (
2969+ ctxb , bobSyncMsg ,
2970+ )
2971+ require .NoError (t , err , "alice unable to process bob's chan sync msg" )
2972+ require .Empty (t , aliceMsgsToSend ,
2973+ "alice should have no messages to retransmit after reestablish" ,
2974+ )
2975+
2976+ // Bob processes Alice's ChannelReestablish. Alice's NextLocalCommitHeight
2977+ // is 1, which equals Bob's remoteTipHeight (1), so Bob detects he owes
2978+ // Alice a commitment signature and prepares to retransmit it.
2979+ bobMsgsToSend , _ , _ , err := bobChannel .ProcessChanSyncMsg (
2980+ ctxb , aliceSyncMsg ,
2981+ )
2982+ require .NoError (t , err , "bob unable to process alice's chan sync msg" )
2983+ require .Len (t , bobMsgsToSend , 1 ,
2984+ "bob should retransmit exactly one message (his commit_sig)" ,
2985+ )
2986+
2987+ retransmittedSig , ok := bobMsgsToSend [0 ].(* lnwire.CommitSig )
2988+ require .True (t , ok , "expected bob to retransmit a CommitSig" )
2989+
2990+ // Alice receives Bob's retransmitted commit_sig. This should succeed:
2991+ // Alice must have the fee update in her update log to compute the
2992+ // correct commitment and verify Bob's signature.
2993+ //
2994+ // Currently this FAILS because the fee update is not restored after
2995+ // restart, so Alice uses the old fee rate and Bob's signature (covering
2996+ // the new fee rate) does not verify. In the link layer this causes
2997+ // LinkFailureForceClose — an incorrect force close of a healthy channel.
2998+ err = aliceChannel .ReceiveNewCommitment (& CommitSigs {
2999+ CommitSig : retransmittedSig .CommitSig ,
3000+ HtlcSigs : retransmittedSig .HtlcSigs ,
3001+ })
3002+ require .NoError (t , err ,
3003+ "alice should process bob's retransmitted commit_sig without " +
3004+ "error; fee update not restored after restart causes " +
3005+ "spurious force close" ,
3006+ )
3007+
3008+ // Complete the commit dance and verify the fee is locked in on both sides.
3009+ aliceRevocation , _ , _ , err := aliceChannel .RevokeCurrentCommitment ()
3010+ require .NoError (t , err , "alice unable to revoke commitment" )
3011+
3012+ _ , _ , err = bobChannel .ReceiveRevocation (aliceRevocation )
3013+ require .NoError (t , err , "bob unable to process alice's revocation" )
3014+
3015+ require .Equal (t , newFee ,
3016+ chainfee .SatPerKWeight (
3017+ aliceChannel .channelState .LocalCommitment .FeePerKw ,
3018+ ),
3019+ "alice's fee rate was not properly locked in" ,
3020+ )
3021+ require .Equal (t , newFee ,
3022+ chainfee .SatPerKWeight (
3023+ bobChannel .channelState .LocalCommitment .FeePerKw ,
3024+ ),
3025+ "bob's fee rate was not properly locked in" ,
3026+ )
3027+ }
3028+
28983029// TestUpdateFeeReceiverSendsUpdate tests that receiving a fee update as channel
28993030// initiator fails, and that trying to initiate fee update as non-initiation
29003031// fails.
0 commit comments