Skip to content

Commit 9080f98

Browse files
committed
Remove spurious interactive-tx commit_sig retransmission
We fully implement lightning/bolts#1214 to stop retransmitting `commit_sig` when our peer has already received it. We also correctly set `next_commitment_number` to let our peer know whether we have received their `commit_sig` or not.
1 parent 194f673 commit 9080f98

File tree

5 files changed

+196
-153
lines changed

5 files changed

+196
-153
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,7 +2195,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
21952195
val nextFundingTlv: Set[ChannelReestablishTlv] = Set(ChannelReestablishTlv.NextFundingTlv(d.signingSession.fundingTx.txId))
21962196
val channelReestablish = ChannelReestablish(
21972197
channelId = d.channelId,
2198-
nextLocalCommitmentNumber = 1,
2198+
nextLocalCommitmentNumber = d.signingSession.reconnectNextLocalCommitmentNumber,
21992199
nextRemoteRevocationNumber = 0,
22002200
yourLastPerCommitmentSecret = PrivateKey(ByteVector32.Zeroes),
22012201
myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint,
@@ -2210,6 +2210,19 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22102210
val yourLastPerCommitmentSecret = remotePerCommitmentSecrets.lastIndex.flatMap(remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
22112211
val channelKeyPath = keyManager.keyPath(d.commitments.params.localParams, d.commitments.params.channelConfig)
22122212
val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommitIndex)
2213+
// If we disconnected while signing a funding transaction, we may need our peer to retransmit their commit_sig.
2214+
val nextLocalCommitmentNumber = d match {
2215+
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
2216+
case DualFundingStatus.RbfWaitingForSigs(status) => status.reconnectNextLocalCommitmentNumber
2217+
case _ => d.commitments.localCommitIndex + 1
2218+
}
2219+
case d: DATA_NORMAL => d.spliceStatus match {
2220+
case SpliceStatus.SpliceWaitingForSigs(status) => status.reconnectNextLocalCommitmentNumber
2221+
case _ => d.commitments.localCommitIndex + 1
2222+
}
2223+
case _ => d.commitments.localCommitIndex + 1
2224+
}
2225+
// If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures.
22132226
val rbfTlv: Set[ChannelReestablishTlv] = d match {
22142227
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
22152228
case DualFundingStatus.RbfWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId))
@@ -2229,7 +2242,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22292242
}
22302243
val channelReestablish = ChannelReestablish(
22312244
channelId = d.channelId,
2232-
nextLocalCommitmentNumber = d.commitments.localCommitIndex + 1,
2245+
nextLocalCommitmentNumber = nextLocalCommitmentNumber,
22332246
nextRemoteRevocationNumber = d.commitments.remoteCommitIndex,
22342247
yourLastPerCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret),
22352248
myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint,
@@ -2270,8 +2283,9 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22702283

22712284
case Event(channelReestablish: ChannelReestablish, d: DATA_WAIT_FOR_DUAL_FUNDING_SIGNED) =>
22722285
channelReestablish.nextFundingTxId_opt match {
2273-
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId =>
2274-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2286+
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.nextLocalCommitmentNumber == 0 =>
2287+
// They haven't received our commit_sig: we retransmit it, and will send our tx_signatures once we've received
2288+
// their commit_sig or their tx_signatures (depending on who must send tx_signatures first).
22752289
val commitSig = d.signingSession.remoteCommit.sign(keyManager, d.channelParams, d.signingSession.fundingTxIndex, d.signingSession.fundingParams.remoteFundingPubKey, d.signingSession.commitInput)
22762290
goto(WAIT_FOR_DUAL_FUNDING_SIGNED) sending commitSig
22772291
case _ => goto(WAIT_FOR_DUAL_FUNDING_SIGNED)
@@ -2282,20 +2296,25 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22822296
case Some(fundingTxId) =>
22832297
d.status match {
22842298
case DualFundingStatus.RbfWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
2285-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2286-
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2287-
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig
2299+
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2300+
// They haven't received our commit_sig: we retransmit it.
2301+
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
2302+
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2303+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig
2304+
} else {
2305+
// They have already received our commit_sig, but we were waiting for them to send either commit_sig or
2306+
// tx_signatures first. We wait for their message before sending our tx_signatures.
2307+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED)
2308+
}
22882309
case _ if d.latestFundingTx.sharedTx.txId == fundingTxId =>
2289-
val toSend = d.latestFundingTx.sharedTx match {
2290-
case fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction =>
2291-
// We have not received their tx_signatures: we retransmit our commit_sig because we don't know if they received it.
2292-
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2293-
Seq(commitSig, fundingTx.localSigs)
2294-
case fundingTx: InteractiveTxBuilder.FullySignedSharedTransaction =>
2295-
// We've already received their tx_signatures, which means they've received and stored our commit_sig, we only need to retransmit our tx_signatures.
2296-
Seq(fundingTx.localSigs)
2310+
// We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures
2311+
// and our commit_sig if they haven't received it already.
2312+
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2313+
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2314+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending Seq(commitSig, d.latestFundingTx.sharedTx.localSigs)
2315+
} else {
2316+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending d.latestFundingTx.sharedTx.localSigs
22972317
}
2298-
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending toSend
22992318
case _ =>
23002319
// The fundingTxId must be for an RBF attempt that we didn't store (we got disconnected before receiving
23012320
// their tx_complete): we tell them to abort that RBF attempt.
@@ -2337,23 +2356,26 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
23372356
case Some(fundingTxId) =>
23382357
d.spliceStatus match {
23392358
case SpliceStatus.SpliceWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
2340-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2341-
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
2342-
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2343-
sendQueue = sendQueue :+ commitSig
2359+
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
2360+
// They haven't received our commit_sig: we retransmit it.
2361+
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
2362+
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
2363+
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2364+
sendQueue = sendQueue :+ commitSig
2365+
}
23442366
d.spliceStatus
23452367
case _ if d.commitments.latest.fundingTxId == fundingTxId =>
23462368
d.commitments.latest.localFundingStatus match {
23472369
case dfu: LocalFundingStatus.DualFundedUnconfirmedFundingTx =>
2348-
dfu.sharedTx match {
2349-
case fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction =>
2350-
// If we have not received their tx_signatures, we can't tell whether they had received our commit_sig, so we need to retransmit it
2351-
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2352-
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2353-
sendQueue = sendQueue :+ commitSig :+ fundingTx.localSigs
2354-
case fundingTx: InteractiveTxBuilder.FullySignedSharedTransaction =>
2355-
log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2356-
sendQueue = sendQueue :+ fundingTx.localSigs
2370+
// We've already received their commit_sig and sent our tx_signatures. We retransmit our
2371+
// tx_signatures and our commit_sig if they haven't received it already.
2372+
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
2373+
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2374+
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2375+
sendQueue = sendQueue :+ commitSig :+ dfu.sharedTx.localSigs
2376+
} else {
2377+
log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2378+
sendQueue = sendQueue :+ dfu.sharedTx.localSigs
23572379
}
23582380
case fundingStatus =>
23592381
// They have not received our tx_signatures, but they must have received our commit_sig, otherwise we would be in the case above.

eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,11 @@ object InteractiveTxSigningSession {
10821082
liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends InteractiveTxSigningSession {
10831083
val commitInput: InputInfo = localCommit.fold(_.commitTx.input, _.commitTxAndRemoteSig.commitTx.input)
10841084
val localCommitIndex: Long = localCommit.fold(_.index, _.index)
1085+
// This value tells our peer whether we need them to retransmit their commit_sig on reconnection or not.
1086+
val reconnectNextLocalCommitmentNumber: Long = localCommit match {
1087+
case Left(commit) => commit.index
1088+
case Right(commit) => commit.index + 1
1089+
}
10851090

10861091
def receiveCommitSig(nodeParams: NodeParams, channelParams: ChannelParams, remoteCommitSig: CommitSig)(implicit log: LoggingAdapter): Either[ChannelException, InteractiveTxSigningSession] = {
10871092
localCommit match {

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -375,15 +375,16 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
375375
bob ! INPUT_DISCONNECTED
376376
awaitCond(bob.stateName == OFFLINE)
377377

378-
reconnect(f, fundingTxId)
378+
reconnect(f, fundingTxId, aliceExpectsCommitSig = true, bobExpectsCommitSig = true)
379379
}
380380

381-
test("recv INPUT_DISCONNECTED (commit_sig not received, next_commitment_number = 0)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
381+
test("recv INPUT_DISCONNECTED (commit_sig received by Alice)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
382382
import f._
383383

384384
val fundingTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED].signingSession.fundingTx.txId
385+
bob2alice.expectMsgType[CommitSig]
386+
bob2alice.forward(alice)
385387
alice2bob.expectMsgType[CommitSig] // Bob doesn't receive Alice's commit_sig
386-
bob2alice.expectMsgType[CommitSig] // Alice doesn't receive Bob's commit_sig
387388
awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
388389
awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
389390

@@ -392,10 +393,10 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
392393
bob ! INPUT_DISCONNECTED
393394
awaitCond(bob.stateName == OFFLINE)
394395

395-
reconnect(f, fundingTxId, aliceCommitmentNumber = 0, bobCommitmentNumber = 0)
396+
reconnect(f, fundingTxId, aliceExpectsCommitSig = false, bobExpectsCommitSig = true)
396397
}
397398

398-
test("recv INPUT_DISCONNECTED (commit_sig partially received)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
399+
test("recv INPUT_DISCONNECTED (commit_sig received by Bob)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
399400
import f._
400401

401402
val fundingTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED].signingSession.fundingTx.txId
@@ -411,26 +412,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
411412
bob ! INPUT_DISCONNECTED
412413
awaitCond(bob.stateName == OFFLINE)
413414

414-
reconnect(f, fundingTxId)
415-
}
416-
417-
test("recv INPUT_DISCONNECTED (commit_sig partially received, next_commitment_number = 0)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
418-
import f._
419-
420-
val fundingTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED].signingSession.fundingTx.txId
421-
alice2bob.expectMsgType[CommitSig]
422-
alice2bob.forward(bob)
423-
bob2alice.expectMsgType[CommitSig] // Alice doesn't receive Bob's commit_sig
424-
bob2alice.expectMsgType[TxSignatures] // Alice doesn't receive Bob's tx_signatures
425-
awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
426-
awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_CONFIRMED)
427-
428-
alice ! INPUT_DISCONNECTED
429-
awaitCond(alice.stateName == OFFLINE)
430-
bob ! INPUT_DISCONNECTED
431-
awaitCond(bob.stateName == OFFLINE)
432-
433-
reconnect(f, fundingTxId, aliceCommitmentNumber = 0)
415+
reconnect(f, fundingTxId, aliceExpectsCommitSig = true, bobExpectsCommitSig = false)
434416
}
435417

436418
test("recv INPUT_DISCONNECTED (commit_sig received)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
@@ -450,7 +432,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
450432
bob ! INPUT_DISCONNECTED
451433
awaitCond(bob.stateName == OFFLINE)
452434

453-
reconnect(f, fundingTxId)
435+
reconnect(f, fundingTxId, aliceExpectsCommitSig = false, bobExpectsCommitSig = false)
454436
}
455437

456438
test("recv INPUT_DISCONNECTED (tx_signatures received)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
@@ -490,7 +472,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
490472
assert(listener.expectMsgType[TransactionPublished].tx.txid == fundingTxId)
491473
}
492474

493-
private def reconnect(f: FixtureParam, fundingTxId: TxId, aliceCommitmentNumber: Long = 1, bobCommitmentNumber: Long = 1): Unit = {
475+
private def reconnect(f: FixtureParam, fundingTxId: TxId, aliceExpectsCommitSig: Boolean, bobExpectsCommitSig: Boolean): Unit = {
494476
import f._
495477

496478
val listener = TestProbe()
@@ -501,17 +483,24 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
501483
alice ! INPUT_RECONNECTED(bob, aliceInit, bobInit)
502484
bob ! INPUT_RECONNECTED(alice, bobInit, aliceInit)
503485
val channelReestablishAlice = alice2bob.expectMsgType[ChannelReestablish]
486+
val nextLocalCommitmentNumberAlice = if (aliceExpectsCommitSig) 0 else 1
504487
assert(channelReestablishAlice.nextFundingTxId_opt.contains(fundingTxId))
505-
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
506-
alice2bob.forward(bob, channelReestablishAlice.copy(nextLocalCommitmentNumber = aliceCommitmentNumber))
488+
assert(channelReestablishAlice.nextLocalCommitmentNumber == nextLocalCommitmentNumberAlice)
489+
alice2bob.forward(bob, channelReestablishAlice)
507490
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
491+
val nextLocalCommitmentNumberBob = if (bobExpectsCommitSig) 0 else 1
508492
assert(channelReestablishBob.nextFundingTxId_opt.contains(fundingTxId))
509-
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
510-
bob2alice.forward(alice, channelReestablishBob.copy(nextLocalCommitmentNumber = bobCommitmentNumber))
511-
bob2alice.expectMsgType[CommitSig]
512-
bob2alice.forward(alice)
513-
alice2bob.expectMsgType[CommitSig]
514-
alice2bob.forward(bob)
493+
assert(channelReestablishBob.nextLocalCommitmentNumber == nextLocalCommitmentNumberBob)
494+
bob2alice.forward(alice, channelReestablishBob)
495+
496+
if (aliceExpectsCommitSig) {
497+
bob2alice.expectMsgType[CommitSig]
498+
bob2alice.forward(alice)
499+
}
500+
if (bobExpectsCommitSig) {
501+
alice2bob.expectMsgType[CommitSig]
502+
alice2bob.forward(bob)
503+
}
515504
bob2alice.expectMsgType[TxSignatures]
516505
bob2alice.forward(alice)
517506
alice2bob.expectMsgType[TxSignatures]

0 commit comments

Comments
 (0)