Skip to content

Commit a8a77af

Browse files
committed
Add retransmit flags to next_funding TLV
Instead of using the `next_commitment_number` to let our peer know that we haven't received their `commit_sig` for a partially signed funding transaction, we add retransmit flags to the `next_funding` TLV in the `channel_reestablish` message. This is similar to what was done in the previous commit for announcement signatures with the `my_current_funding_locked` TLV.
1 parent c42d6c1 commit a8a77af

File tree

8 files changed

+79
-63
lines changed

8 files changed

+79
-63
lines changed

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

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
651651
case (s: SpliceStatus.SpliceInProgress, sig: CommitSig) =>
652652
log.debug("received their commit_sig, deferring message")
653653
stay() using d.copy(spliceStatus = s.copy(remoteCommitSig = Some(sig)))
654-
case (SpliceStatus.SpliceAborted, sig: CommitSig) =>
654+
case (SpliceStatus.SpliceAborted, _: CommitSig) =>
655655
log.warning("received commit_sig after sending tx_abort, they probably sent it before receiving our tx_abort, ignoring...")
656656
stay()
657657
case (SpliceStatus.SpliceWaitingForSigs(signingSession), sig: CommitSig) =>
@@ -1456,7 +1456,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
14561456
case Event(w: WatchPublishedTriggered, d: DATA_NORMAL) =>
14571457
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid))
14581458
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, d.lastAnnouncedFundingTxId_opt) match {
1459-
case Right((commitments1, commitment)) =>
1459+
case Right((commitments1, _)) =>
14601460
watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepth), delay_opt = None)
14611461
maybeEmitEventsPostSplice(d.aliases, d.commitments, commitments1, d.lastAnnouncement_opt)
14621462
maybeUpdateMaxHtlcAmount(d.channelUpdate.htlcMaximumMsat, commitments1)
@@ -2370,7 +2370,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
23702370
case Event(INPUT_RECONNECTED(r, localInit, remoteInit), d: DATA_WAIT_FOR_DUAL_FUNDING_SIGNED) =>
23712371
activeConnection = r
23722372
val myFirstPerCommitmentPoint = channelKeys.commitmentPoint(0)
2373-
val nextFundingTlv: Set[ChannelReestablishTlv] = Set(ChannelReestablishTlv.NextFundingTlv(d.signingSession.fundingTxId))
2373+
val nextFundingTlv = Set[ChannelReestablishTlv](ChannelReestablishTlv.NextFundingTlv(d.signingSession.fundingTxId, d.signingSession.retransmitRemoteCommitSig))
23742374
val nonceTlvs = d.signingSession.fundingParams.commitmentFormat match {
23752375
case _: SegwitV0CommitmentFormat => Set.empty
23762376
case _: SimpleTaprootChannelCommitmentFormat =>
@@ -2388,7 +2388,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
23882388
}
23892389
val channelReestablish = ChannelReestablish(
23902390
channelId = d.channelId,
2391-
nextLocalCommitmentNumber = d.signingSession.nextLocalCommitmentNumber,
2391+
nextLocalCommitmentNumber = 1,
23922392
nextRemoteRevocationNumber = 0,
23932393
yourLastPerCommitmentSecret = PrivateKey(ByteVector32.Zeroes),
23942394
myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint,
@@ -2402,31 +2402,19 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
24022402
val remotePerCommitmentSecrets = d.commitments.remotePerCommitmentSecrets
24032403
val yourLastPerCommitmentSecret = remotePerCommitmentSecrets.lastIndex.flatMap(remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
24042404
val myCurrentPerCommitmentPoint = channelKeys.commitmentPoint(d.commitments.localCommitIndex)
2405-
// If we disconnected while signing a funding transaction, we may need our peer to retransmit their commit_sig.
2406-
val nextLocalCommitmentNumber = d match {
2407-
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
2408-
case DualFundingStatus.RbfWaitingForSigs(status) => status.nextLocalCommitmentNumber
2409-
case _ => d.commitments.localCommitIndex + 1
2410-
}
2411-
case d: DATA_NORMAL => d.spliceStatus match {
2412-
case SpliceStatus.SpliceWaitingForSigs(status) => status.nextLocalCommitmentNumber
2413-
case _ => d.commitments.localCommitIndex + 1
2414-
}
2415-
case _ => d.commitments.localCommitIndex + 1
2416-
}
2417-
// If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures.
2405+
// If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures and commit_sig.
24182406
val rbfTlv: Set[ChannelReestablishTlv] = d match {
24192407
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
2420-
case DualFundingStatus.RbfWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId))
2408+
case DualFundingStatus.RbfWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId, status.retransmitRemoteCommitSig))
24212409
case _ => d.latestFundingTx.sharedTx match {
2422-
case _: InteractiveTxBuilder.PartiallySignedSharedTransaction => Set(ChannelReestablishTlv.NextFundingTlv(d.latestFundingTx.sharedTx.txId))
2410+
case _: InteractiveTxBuilder.PartiallySignedSharedTransaction => Set(ChannelReestablishTlv.NextFundingTlv(d.latestFundingTx.sharedTx.txId, retransmitCommitSig = false))
24232411
case _: InteractiveTxBuilder.FullySignedSharedTransaction => Set.empty
24242412
}
24252413
}
24262414
case d: DATA_NORMAL => d.spliceStatus match {
2427-
case SpliceStatus.SpliceWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId))
2415+
case SpliceStatus.SpliceWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId, status.retransmitRemoteCommitSig))
24282416
case _ => d.commitments.latest.localFundingStatus match {
2429-
case LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _, _) => Set(ChannelReestablishTlv.NextFundingTlv(fundingTx.txId))
2417+
case LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _, _) => Set(ChannelReestablishTlv.NextFundingTlv(fundingTx.txId, retransmitCommitSig = false))
24302418
case _ => Set.empty
24312419
}
24322420
}
@@ -2476,7 +2464,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
24762464

24772465
val channelReestablish = ChannelReestablish(
24782466
channelId = d.channelId,
2479-
nextLocalCommitmentNumber = nextLocalCommitmentNumber,
2467+
nextLocalCommitmentNumber = d.commitments.localCommitIndex + 1,
24802468
nextRemoteRevocationNumber = d.commitments.remoteCommitIndex,
24812469
yourLastPerCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret),
24822470
myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint,
@@ -2528,7 +2516,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
25282516
case _ =>
25292517
remoteNextCommitNonces = channelReestablish.nextCommitNonces
25302518
channelReestablish.nextFundingTxId_opt match {
2531-
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.nextLocalCommitmentNumber == 0 =>
2519+
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.retransmitInteractiveTxCommitSig =>
25322520
// They haven't received our commit_sig: we retransmit it, and will send our tx_signatures once we've received
25332521
// their commit_sig or their tx_signatures (depending on who must send tx_signatures first).
25342522
val fundingParams = d.signingSession.fundingParams
@@ -2556,7 +2544,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
25562544
case Some(fundingTxId) =>
25572545
d.status match {
25582546
case DualFundingStatus.RbfWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
2559-
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2547+
if (channelReestablish.retransmitInteractiveTxCommitSig) {
25602548
// They haven't received our commit_sig: we retransmit it.
25612549
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
25622550
val fundingParams = signingSession.fundingParams
@@ -2573,7 +2561,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
25732561
case _ if d.latestFundingTx.sharedTx.txId == fundingTxId =>
25742562
// We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures
25752563
// and our commit_sig if they haven't received it already.
2576-
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2564+
if (channelReestablish.retransmitInteractiveTxCommitSig) {
25772565
val remoteNonce_opt = channelReestablish.currentCommitNonce_opt
25782566
d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, d.commitments.latest.remoteCommitParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys), d.commitments.latest.commitmentFormat, remoteNonce_opt) match {
25792567
case Left(e) => handleLocalError(e, d, Some(channelReestablish))
@@ -2611,7 +2599,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
26112599
channelReestablish.nextFundingTxId_opt match {
26122600
case Some(fundingTxId) if fundingTxId == d.commitments.latest.fundingTxId =>
26132601
d.commitments.latest.localFundingStatus.localSigs_opt match {
2614-
case Some(txSigs) if channelReestablish.nextLocalCommitmentNumber == 0 =>
2602+
case Some(txSigs) if channelReestablish.retransmitInteractiveTxCommitSig =>
26152603
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
26162604
val remoteNonce_opt = channelReestablish.currentCommitNonce_opt
26172605
d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, d.commitments.latest.remoteCommitParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys), d.commitments.latest.commitmentFormat, remoteNonce_opt) match {
@@ -2672,9 +2660,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
26722660
sendQueue = sendQueue ++ syncSuccess.retransmit
26732661

26742662
commitments1.remoteNextCommitInfo match {
2675-
case Left(_) =>
2676-
// we expect them to (re-)send the revocation immediately
2677-
startSingleTimer(RevocationTimeout.toString, RevocationTimeout(commitments1.remoteCommitIndex, peer), nodeParams.channelConf.revocationTimeout)
2663+
// we expect them to (re-)send their revocation immediately
2664+
case Left(_) => startSingleTimer(RevocationTimeout.toString, RevocationTimeout(commitments1.remoteCommitIndex, peer), nodeParams.channelConf.revocationTimeout)
26782665
case _ => ()
26792666
}
26802667

@@ -3423,7 +3410,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
34233410
case Some(fundingTxId) =>
34243411
d.spliceStatus match {
34253412
case SpliceStatus.SpliceWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
3426-
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
3413+
if (channelReestablish.retransmitInteractiveTxCommitSig) {
34273414
// They haven't received our commit_sig: we retransmit it.
34283415
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
34293416
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
@@ -3440,7 +3427,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
34403427
case dfu: LocalFundingStatus.DualFundedUnconfirmedFundingTx =>
34413428
// We've already received their commit_sig and sent our tx_signatures. We retransmit our
34423429
// tx_signatures and our commit_sig if they haven't received it already.
3443-
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
3430+
if (channelReestablish.retransmitInteractiveTxCommitSig) {
34443431
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
34453432
val remoteNonce_opt = channelReestablish.currentCommitNonce_opt
34463433
d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, d.commitments.latest.remoteCommitParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys), d.commitments.latest.commitmentFormat, remoteNonce_opt) match {

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,11 +1187,8 @@ object InteractiveTxSigningSession {
11871187
liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends InteractiveTxSigningSession {
11881188
val fundingTxId: TxId = fundingTx.txId
11891189
val localCommitIndex: Long = localCommit.fold(_.index, _.index)
1190-
// This value tells our peer whether we need them to retransmit their commit_sig on reconnection or not.
1191-
val nextLocalCommitmentNumber: Long = localCommit match {
1192-
case Left(unsignedCommit) => unsignedCommit.index
1193-
case Right(commit) => commit.index + 1
1194-
}
1190+
// If we haven't received the remote commit_sig, we will request a retransmission on reconnection.
1191+
val retransmitRemoteCommitSig: Boolean = localCommit.isLeft
11951192

11961193
def localFundingKey(channelKeys: ChannelKeys): PrivateKey = channelKeys.fundingKey(fundingTxIndex)
11971194

eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,11 @@ object ChannelReestablishTlv {
258258
/**
259259
* When disconnected in the middle of an interactive-tx session, this field is used to request a retransmission of
260260
* [[TxSignatures]] for the given [[txId]].
261+
*
262+
* @param txId the txid of the partially signed funding transaction.
263+
* @param retransmitCommitSig true if [[CommitSig]] must be retransmitted before [[TxSignatures]].
261264
*/
262-
case class NextFundingTlv(txId: TxId) extends ChannelReestablishTlv
265+
case class NextFundingTlv(txId: TxId, retransmitCommitSig: Boolean) extends ChannelReestablishTlv
263266

264267
/**
265268
* @param txId the txid of our latest outgoing [[ChannelReady]] or [[SpliceLocked]] for this channel.
@@ -281,7 +284,7 @@ object ChannelReestablishTlv {
281284
case class NextLocalNoncesTlv(nonces: Seq[(TxId, IndividualNonce)]) extends ChannelReestablishTlv
282285

283286
object NextFundingTlv {
284-
val codec: Codec[NextFundingTlv] = tlvField(txIdAsHash)
287+
val codec: Codec[NextFundingTlv] = tlvField(("next_funding_txid" | txIdAsHash) :: ("retransmit_flags" | (ignore(7) :: bool)))
285288
}
286289

287290
object MyCurrentFundingLockedTlv {
@@ -297,7 +300,7 @@ object ChannelReestablishTlv {
297300
}
298301

299302
val channelReestablishTlvCodec: Codec[TlvStream[ChannelReestablishTlv]] = tlvStream(discriminated[ChannelReestablishTlv].by(varint)
300-
.typecase(UInt64(0), NextFundingTlv.codec)
303+
.typecase(UInt64(1), NextFundingTlv.codec)
301304
.typecase(UInt64(5), MyCurrentFundingLockedTlv.codec)
302305
.typecase(UInt64(22), NextLocalNoncesTlv.codec)
303306
.typecase(UInt64(24), CurrentCommitNonceTlv.codec)

eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ case class ChannelReestablish(channelId: ByteVector32,
204204
myCurrentPerCommitmentPoint: PublicKey,
205205
tlvStream: TlvStream[ChannelReestablishTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId {
206206
val nextFundingTxId_opt: Option[TxId] = tlvStream.get[ChannelReestablishTlv.NextFundingTlv].map(_.txId)
207+
val retransmitInteractiveTxCommitSig: Boolean = tlvStream.get[ChannelReestablishTlv.NextFundingTlv].exists(_.retransmitCommitSig)
207208
val myCurrentFundingLocked_opt: Option[TxId] = tlvStream.get[ChannelReestablishTlv.MyCurrentFundingLockedTlv].map(_.txId)
208209
val retransmitAnnSigs: Boolean = tlvStream.get[ChannelReestablishTlv.MyCurrentFundingLockedTlv].exists(_.retransmitAnnSigs)
209210
val nextCommitNonces: Map[TxId, IndividualNonce] = tlvStream.get[ChannelReestablishTlv.NextLocalNoncesTlv].map(_.nonces.toMap).getOrElse(Map.empty)

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -425,13 +425,15 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
425425

426426
alice ! INPUT_RECONNECTED(bob, aliceInit, bobInit)
427427
val channelReestablishAlice = alice2bob.expectMsgType[ChannelReestablish]
428-
assert(channelReestablishAlice.nextLocalCommitmentNumber == 0)
428+
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
429+
assert(channelReestablishAlice.retransmitInteractiveTxCommitSig)
429430
assert(channelReestablishAlice.currentCommitNonce_opt.nonEmpty)
430431
assert(channelReestablishAlice.nextCommitNonces.contains(fundingTxId))
431432

432433
bob ! INPUT_RECONNECTED(alice, bobInit, aliceInit)
433434
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
434-
assert(channelReestablishBob.nextLocalCommitmentNumber == 0)
435+
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
436+
assert(channelReestablishBob.retransmitInteractiveTxCommitSig)
435437
assert(channelReestablishBob.currentCommitNonce_opt.nonEmpty)
436438
assert(channelReestablishBob.nextCommitNonces.contains(fundingTxId))
437439

@@ -530,12 +532,14 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
530532
assert(channelReestablishAlice.nextCommitNonces.contains(fundingTx.txid))
531533
assert(channelReestablishAlice.nextCommitNonces.get(fundingTx.txid) != channelReestablishAlice.currentCommitNonce_opt)
532534
assert(channelReestablishAlice.nextFundingTxId_opt.contains(fundingTx.txid))
533-
assert(channelReestablishAlice.nextLocalCommitmentNumber == 0)
535+
assert(channelReestablishAlice.retransmitInteractiveTxCommitSig)
536+
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
534537
alice2bob.forward(bob, channelReestablishAlice)
535538
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
536539
assert(channelReestablishBob.currentCommitNonce_opt.isEmpty)
537540
assert(channelReestablishBob.nextCommitNonces.get(fundingTx.txid) == channelReadyB.nextCommitNonce_opt)
538541
assert(channelReestablishBob.nextFundingTxId_opt.isEmpty)
542+
assert(!channelReestablishBob.retransmitInteractiveTxCommitSig)
539543
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
540544
bob2alice.forward(alice, channelReestablishBob)
541545

@@ -673,14 +677,14 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
673677
alice ! INPUT_RECONNECTED(bob, aliceInit, bobInit)
674678
bob ! INPUT_RECONNECTED(alice, bobInit, aliceInit)
675679
val channelReestablishAlice = alice2bob.expectMsgType[ChannelReestablish]
676-
val nextLocalCommitmentNumberAlice = if (aliceExpectsCommitSig) 0 else 1
677680
assert(channelReestablishAlice.nextFundingTxId_opt.contains(fundingTxId))
678-
assert(channelReestablishAlice.nextLocalCommitmentNumber == nextLocalCommitmentNumberAlice)
681+
assert(channelReestablishAlice.retransmitInteractiveTxCommitSig == aliceExpectsCommitSig)
682+
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
679683
alice2bob.forward(bob, channelReestablishAlice)
680684
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
681-
val nextLocalCommitmentNumberBob = if (bobExpectsCommitSig) 0 else 1
682685
assert(channelReestablishBob.nextFundingTxId_opt.contains(fundingTxId))
683-
assert(channelReestablishBob.nextLocalCommitmentNumber == nextLocalCommitmentNumberBob)
686+
assert(channelReestablishBob.retransmitInteractiveTxCommitSig == bobExpectsCommitSig)
687+
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
684688
bob2alice.forward(alice, channelReestablishBob)
685689

686690
// When using taproot, we must provide nonces for the partial signatures.

0 commit comments

Comments
 (0)