Skip to content

Commit 4e59f25

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 06eb44a commit 4e59f25

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
@@ -2108,7 +2108,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
21082108
val nextFundingTlv: Set[ChannelReestablishTlv] = Set(ChannelReestablishTlv.NextFundingTlv(d.signingSession.fundingTx.txId))
21092109
val channelReestablish = ChannelReestablish(
21102110
channelId = d.channelId,
2111-
nextLocalCommitmentNumber = 1,
2111+
nextLocalCommitmentNumber = d.signingSession.reconnectNextLocalCommitmentNumber,
21122112
nextRemoteRevocationNumber = 0,
21132113
yourLastPerCommitmentSecret = PrivateKey(ByteVector32.Zeroes),
21142114
myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint,
@@ -2123,6 +2123,19 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
21232123
val yourLastPerCommitmentSecret = remotePerCommitmentSecrets.lastIndex.flatMap(remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
21242124
val channelKeyPath = keyManager.keyPath(d.commitments.params.localParams, d.commitments.params.channelConfig)
21252125
val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommitIndex)
2126+
// If we disconnected while signing a funding transaction, we may need our peer to retransmit their commit_sig.
2127+
val nextLocalCommitmentNumber = d match {
2128+
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
2129+
case DualFundingStatus.RbfWaitingForSigs(status) => status.reconnectNextLocalCommitmentNumber
2130+
case _ => d.commitments.localCommitIndex + 1
2131+
}
2132+
case d: DATA_NORMAL => d.spliceStatus match {
2133+
case SpliceStatus.SpliceWaitingForSigs(status) => status.reconnectNextLocalCommitmentNumber
2134+
case _ => d.commitments.localCommitIndex + 1
2135+
}
2136+
case _ => d.commitments.localCommitIndex + 1
2137+
}
2138+
// If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures.
21262139
val rbfTlv: Set[ChannelReestablishTlv] = d match {
21272140
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
21282141
case DualFundingStatus.RbfWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId))
@@ -2142,7 +2155,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
21422155
}
21432156
val channelReestablish = ChannelReestablish(
21442157
channelId = d.channelId,
2145-
nextLocalCommitmentNumber = d.commitments.localCommitIndex + 1,
2158+
nextLocalCommitmentNumber = nextLocalCommitmentNumber,
21462159
nextRemoteRevocationNumber = d.commitments.remoteCommitIndex,
21472160
yourLastPerCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret),
21482161
myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint,
@@ -2183,8 +2196,9 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
21832196

21842197
case Event(channelReestablish: ChannelReestablish, d: DATA_WAIT_FOR_DUAL_FUNDING_SIGNED) =>
21852198
channelReestablish.nextFundingTxId_opt match {
2186-
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId =>
2187-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2199+
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.nextLocalCommitmentNumber == 0 =>
2200+
// They haven't received our commit_sig: we retransmit it, and will send our tx_signatures once we've received
2201+
// their commit_sig or their tx_signatures (depending on who must send tx_signatures first).
21882202
val commitSig = d.signingSession.remoteCommit.sign(keyManager, d.channelParams, d.signingSession.fundingTxIndex, d.signingSession.fundingParams.remoteFundingPubKey, d.signingSession.commitInput)
21892203
goto(WAIT_FOR_DUAL_FUNDING_SIGNED) sending commitSig
21902204
case _ => goto(WAIT_FOR_DUAL_FUNDING_SIGNED)
@@ -2195,20 +2209,25 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
21952209
case Some(fundingTxId) =>
21962210
d.status match {
21972211
case DualFundingStatus.RbfWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
2198-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2199-
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2200-
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig
2212+
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2213+
// They haven't received our commit_sig: we retransmit it.
2214+
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
2215+
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2216+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig
2217+
} else {
2218+
// They have already received our commit_sig, but we were waiting for them to send either commit_sig or
2219+
// tx_signatures first. We wait for their message before sending our tx_signatures.
2220+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED)
2221+
}
22012222
case _ if d.latestFundingTx.sharedTx.txId == fundingTxId =>
2202-
val toSend = d.latestFundingTx.sharedTx match {
2203-
case fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction =>
2204-
// We have not received their tx_signatures: we retransmit our commit_sig because we don't know if they received it.
2205-
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2206-
Seq(commitSig, fundingTx.localSigs)
2207-
case fundingTx: InteractiveTxBuilder.FullySignedSharedTransaction =>
2208-
// 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.
2209-
Seq(fundingTx.localSigs)
2223+
// We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures
2224+
// and our commit_sig if they haven't received it already.
2225+
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2226+
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2227+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending Seq(commitSig, d.latestFundingTx.sharedTx.localSigs)
2228+
} else {
2229+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending d.latestFundingTx.sharedTx.localSigs
22102230
}
2211-
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending toSend
22122231
case _ =>
22132232
// The fundingTxId must be for an RBF attempt that we didn't store (we got disconnected before receiving
22142233
// their tx_complete): we tell them to abort that RBF attempt.
@@ -2250,23 +2269,26 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22502269
case Some(fundingTxId) =>
22512270
d.spliceStatus match {
22522271
case SpliceStatus.SpliceWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
2253-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2254-
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
2255-
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2256-
sendQueue = sendQueue :+ commitSig
2272+
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
2273+
// They haven't received our commit_sig: we retransmit it.
2274+
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
2275+
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
2276+
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2277+
sendQueue = sendQueue :+ commitSig
2278+
}
22572279
d.spliceStatus
22582280
case _ if d.commitments.latest.fundingTxId == fundingTxId =>
22592281
d.commitments.latest.localFundingStatus match {
22602282
case dfu: LocalFundingStatus.DualFundedUnconfirmedFundingTx =>
2261-
dfu.sharedTx match {
2262-
case fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction =>
2263-
// 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
2264-
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2265-
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2266-
sendQueue = sendQueue :+ commitSig :+ fundingTx.localSigs
2267-
case fundingTx: InteractiveTxBuilder.FullySignedSharedTransaction =>
2268-
log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2269-
sendQueue = sendQueue :+ fundingTx.localSigs
2283+
// We've already received their commit_sig and sent our tx_signatures. We retransmit our
2284+
// tx_signatures and our commit_sig if they haven't received it already.
2285+
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
2286+
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2287+
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2288+
sendQueue = sendQueue :+ commitSig :+ dfu.sharedTx.localSigs
2289+
} else {
2290+
log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2291+
sendQueue = sendQueue :+ dfu.sharedTx.localSigs
22702292
}
22712293
case fundingStatus =>
22722294
// 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)