Skip to content

Commit 31cc8f1

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 95bbf06 commit 31cc8f1

File tree

5 files changed

+296
-138
lines changed

5 files changed

+296
-138
lines changed

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

+70-31
Original file line numberDiff line numberDiff line change
@@ -2243,7 +2243,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22432243
val nextFundingTlv: Set[ChannelReestablishTlv] = Set(ChannelReestablishTlv.NextFundingTlv(d.signingSession.fundingTx.txId))
22442244
val channelReestablish = ChannelReestablish(
22452245
channelId = d.channelId,
2246-
nextLocalCommitmentNumber = 1,
2246+
nextLocalCommitmentNumber = d.signingSession.nextLocalCommitmentNumber,
22472247
nextRemoteRevocationNumber = 0,
22482248
yourLastPerCommitmentSecret = PrivateKey(ByteVector32.Zeroes),
22492249
myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint,
@@ -2258,6 +2258,19 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22582258
val yourLastPerCommitmentSecret = remotePerCommitmentSecrets.lastIndex.flatMap(remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
22592259
val channelKeyPath = keyManager.keyPath(d.commitments.params.localParams, d.commitments.params.channelConfig)
22602260
val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommitIndex)
2261+
// If we disconnected while signing a funding transaction, we may need our peer to retransmit their commit_sig.
2262+
val nextLocalCommitmentNumber = d match {
2263+
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
2264+
case DualFundingStatus.RbfWaitingForSigs(status) => status.nextLocalCommitmentNumber
2265+
case _ => d.commitments.localCommitIndex + 1
2266+
}
2267+
case d: DATA_NORMAL => d.spliceStatus match {
2268+
case SpliceStatus.SpliceWaitingForSigs(status) => status.nextLocalCommitmentNumber
2269+
case _ => d.commitments.localCommitIndex + 1
2270+
}
2271+
case _ => d.commitments.localCommitIndex + 1
2272+
}
2273+
// If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures.
22612274
val rbfTlv: Set[ChannelReestablishTlv] = d match {
22622275
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
22632276
case DualFundingStatus.RbfWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId))
@@ -2281,7 +2294,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
22812294

22822295
val channelReestablish = ChannelReestablish(
22832296
channelId = d.channelId,
2284-
nextLocalCommitmentNumber = d.commitments.localCommitIndex + 1,
2297+
nextLocalCommitmentNumber = nextLocalCommitmentNumber,
22852298
nextRemoteRevocationNumber = d.commitments.remoteCommitIndex,
22862299
yourLastPerCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret),
22872300
myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint,
@@ -2322,8 +2335,9 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
23222335

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

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

23692405
case Event(channelReestablish: ChannelReestablish, d: DATA_NORMAL) =>
23702406
Syncing.checkSync(keyManager, d.commitments, channelReestablish) match {
@@ -2412,23 +2448,26 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
24122448
case Some(fundingTxId) =>
24132449
d.spliceStatus match {
24142450
case SpliceStatus.SpliceWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
2415-
// We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig.
2416-
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
2417-
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2418-
sendQueue = sendQueue :+ commitSig
2451+
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
2452+
// They haven't received our commit_sig: we retransmit it.
2453+
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
2454+
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
2455+
val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput)
2456+
sendQueue = sendQueue :+ commitSig
2457+
}
24192458
d.spliceStatus
24202459
case _ if d.commitments.latest.fundingTxId == fundingTxId =>
24212460
d.commitments.latest.localFundingStatus match {
24222461
case dfu: LocalFundingStatus.DualFundedUnconfirmedFundingTx =>
2423-
dfu.sharedTx match {
2424-
case fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction =>
2425-
// 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
2426-
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2427-
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2428-
sendQueue = sendQueue :+ commitSig :+ fundingTx.localSigs
2429-
case fundingTx: InteractiveTxBuilder.FullySignedSharedTransaction =>
2430-
log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2431-
sendQueue = sendQueue :+ fundingTx.localSigs
2462+
// We've already received their commit_sig and sent our tx_signatures. We retransmit our
2463+
// tx_signatures and our commit_sig if they haven't received it already.
2464+
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
2465+
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2466+
val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput)
2467+
sendQueue = sendQueue :+ commitSig :+ dfu.sharedTx.localSigs
2468+
} else {
2469+
log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
2470+
sendQueue = sendQueue :+ dfu.sharedTx.localSigs
24322471
}
24332472
case fundingStatus =>
24342473
// 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

+5
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 nextLocalCommitmentNumber: Long = localCommit match {
1087+
case Left(unsignedCommit) => unsignedCommit.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)