Skip to content

Commit 9625a6e

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. We also retransmit `tx_signatures` (and, if requested, `commit_sig`) after sending `channel_ready` in the 0-conf case. This was missing and was a bug.
1 parent 21917f5 commit 9625a6e

File tree

5 files changed

+298
-140
lines changed

5 files changed

+298
-140
lines changed

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

Lines changed: 72 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2242,7 +2242,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22422242
val nextFundingTlv: Set[ChannelReestablishTlv] = Set(ChannelReestablishTlv.NextFundingTlv(d.signingSession.fundingTx.txId))
22432243
val channelReestablish = ChannelReestablish(
22442244
channelId = d.channelId,
2245-
nextLocalCommitmentNumber = 1,
2245+
nextLocalCommitmentNumber = d.signingSession.reconnectNextLocalCommitmentNumber,
22462246
nextRemoteRevocationNumber = 0,
22472247
yourLastPerCommitmentSecret = PrivateKey(ByteVector32.Zeroes),
22482248
myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint,
@@ -2257,6 +2257,19 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22572257
val yourLastPerCommitmentSecret = remotePerCommitmentSecrets.lastIndex.flatMap(remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
22582258
val channelKeyPath = keyManager.keyPath(d.commitments.params.localParams, d.commitments.params.channelConfig)
22592259
val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommitIndex)
2260+
// If we disconnected while signing a funding transaction, we may need our peer to retransmit their commit_sig.
2261+
val nextLocalCommitmentNumber = d match {
2262+
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
2263+
case DualFundingStatus.RbfWaitingForSigs(status) => status.reconnectNextLocalCommitmentNumber
2264+
case _ => d.commitments.localCommitIndex + 1
2265+
}
2266+
case d: DATA_NORMAL => d.spliceStatus match {
2267+
case SpliceStatus.SpliceWaitingForSigs(status) => status.reconnectNextLocalCommitmentNumber
2268+
case _ => d.commitments.localCommitIndex + 1
2269+
}
2270+
case _ => d.commitments.localCommitIndex + 1
2271+
}
2272+
// If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures.
22602273
val rbfTlv: Set[ChannelReestablishTlv] = d match {
22612274
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
22622275
case DualFundingStatus.RbfWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId))
@@ -2280,7 +2293,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22802293

22812294
val channelReestablish = ChannelReestablish(
22822295
channelId = d.channelId,
2283-
nextLocalCommitmentNumber = d.commitments.localCommitIndex + 1,
2296+
nextLocalCommitmentNumber = nextLocalCommitmentNumber,
22842297
nextRemoteRevocationNumber = d.commitments.remoteCommitIndex,
22852298
yourLastPerCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret),
22862299
myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint,
@@ -2321,8 +2334,9 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
23212334

23222335
case Event(channelReestablish: ChannelReestablish, d: DATA_WAIT_FOR_DUAL_FUNDING_SIGNED) =>
23232336
channelReestablish.nextFundingTxId_opt match {
2324-
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId =>
2325-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2337+
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.nextLocalCommitmentNumber == 0 =>
2338+
// They haven't received our commit_sig: we retransmit it, and will send our tx_signatures once we've received
2339+
// their commit_sig or their tx_signatures (depending on who must send tx_signatures first).
23262340
val commitSig = d.signingSession.remoteCommit.sign(keyManager, d.channelParams, d.signingSession.fundingTxIndex, d.signingSession.fundingParams.remoteFundingPubKey, d.signingSession.commitInput)
23272341
goto(WAIT_FOR_DUAL_FUNDING_SIGNED) sending commitSig
23282342
case _ => goto(WAIT_FOR_DUAL_FUNDING_SIGNED)
@@ -2333,20 +2347,25 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
23332347
case Some(fundingTxId) =>
23342348
d.status match {
23352349
case DualFundingStatus.RbfWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
2336-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2337-
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2338-
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig
2350+
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2351+
// They haven't received our commit_sig: we retransmit it.
2352+
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
2353+
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2354+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig
2355+
} else {
2356+
// They have already received our commit_sig, but we were waiting for them to send either commit_sig or
2357+
// tx_signatures first. We wait for their message before sending our tx_signatures.
2358+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED)
2359+
}
23392360
case _ if d.latestFundingTx.sharedTx.txId == fundingTxId =>
2340-
val toSend = d.latestFundingTx.sharedTx match {
2341-
case fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction =>
2342-
// We have not received their tx_signatures: we retransmit our commit_sig because we don't know if they received it.
2343-
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2344-
Seq(commitSig, fundingTx.localSigs)
2345-
case fundingTx: InteractiveTxBuilder.FullySignedSharedTransaction =>
2346-
// 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.
2347-
Seq(fundingTx.localSigs)
2361+
// We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures
2362+
// and our commit_sig if they haven't received it already.
2363+
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2364+
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2365+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending Seq(commitSig, d.latestFundingTx.sharedTx.localSigs)
2366+
} else {
2367+
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending d.latestFundingTx.sharedTx.localSigs
23482368
}
2349-
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending toSend
23502369
case _ =>
23512370
// The fundingTxId must be for an RBF attempt that we didn't store (we got disconnected before receiving
23522371
// their tx_complete): we tell them to abort that RBF attempt.
@@ -2356,14 +2375,31 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
23562375
}
23572376

23582377
case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_CHANNEL_READY) =>
2359-
log.debug("re-sending channelReady")
2378+
log.debug("re-sending channel_ready")
23602379
val channelReady = createChannelReady(d.aliases, d.commitments.params)
23612380
goto(WAIT_FOR_CHANNEL_READY) sending channelReady
23622381

2363-
case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_DUAL_FUNDING_READY) =>
2364-
log.debug("re-sending channelReady")
2382+
case Event(channelReestablish: ChannelReestablish, d: DATA_WAIT_FOR_DUAL_FUNDING_READY) =>
2383+
log.debug("re-sending channel_ready")
23652384
val channelReady = createChannelReady(d.aliases, d.commitments.params)
2366-
goto(WAIT_FOR_DUAL_FUNDING_READY) sending channelReady
2385+
// We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures
2386+
// and our commit_sig if they haven't received it already.
2387+
channelReestablish.nextFundingTxId_opt match {
2388+
case Some(fundingTxId) if fundingTxId == d.commitments.latest.fundingTxId =>
2389+
d.commitments.latest.localFundingStatus.localSigs_opt match {
2390+
case Some(txSigs) if channelReestablish.nextLocalCommitmentNumber == 0 =>
2391+
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2392+
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2393+
goto(WAIT_FOR_DUAL_FUNDING_READY) sending Seq(commitSig, txSigs, channelReady)
2394+
case Some(txSigs) =>
2395+
log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2396+
goto(WAIT_FOR_DUAL_FUNDING_READY) sending Seq(txSigs, channelReady)
2397+
case None =>
2398+
log.warning("cannot retransmit tx_signatures, we don't have them (status={})", d.commitments.latest.localFundingStatus)
2399+
goto(WAIT_FOR_DUAL_FUNDING_READY) sending channelReady
2400+
}
2401+
case _ => goto(WAIT_FOR_DUAL_FUNDING_READY) sending channelReady
2402+
}
23672403

23682404
case Event(channelReestablish: ChannelReestablish, d: DATA_NORMAL) =>
23692405
Syncing.checkSync(keyManager, d.commitments, channelReestablish) match {
@@ -2389,23 +2425,26 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
23892425
case Some(fundingTxId) =>
23902426
d.spliceStatus match {
23912427
case SpliceStatus.SpliceWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
2392-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2393-
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
2394-
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2395-
sendQueue = sendQueue :+ commitSig
2428+
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
2429+
// They haven't received our commit_sig: we retransmit it.
2430+
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
2431+
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
2432+
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2433+
sendQueue = sendQueue :+ commitSig
2434+
}
23962435
d.spliceStatus
23972436
case _ if d.commitments.latest.fundingTxId == fundingTxId =>
23982437
d.commitments.latest.localFundingStatus match {
23992438
case dfu: LocalFundingStatus.DualFundedUnconfirmedFundingTx =>
2400-
dfu.sharedTx match {
2401-
case fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction =>
2402-
// 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
2403-
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2404-
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2405-
sendQueue = sendQueue :+ commitSig :+ fundingTx.localSigs
2406-
case fundingTx: InteractiveTxBuilder.FullySignedSharedTransaction =>
2407-
log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2408-
sendQueue = sendQueue :+ fundingTx.localSigs
2439+
// We've already received their commit_sig and sent our tx_signatures. We retransmit our
2440+
// tx_signatures and our commit_sig if they haven't received it already.
2441+
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
2442+
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2443+
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2444+
sendQueue = sendQueue :+ commitSig :+ dfu.sharedTx.localSigs
2445+
} else {
2446+
log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2447+
sendQueue = sendQueue :+ dfu.sharedTx.localSigs
24092448
}
24102449
case fundingStatus =>
24112450
// 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 {

0 commit comments

Comments
 (0)