Skip to content

Commit 0bbbf3e

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 ff4796b commit 0bbbf3e

8 files changed

Lines changed: 78 additions & 62 deletions

File tree

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

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
@@ -2371,7 +2371,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
23712371
case Event(INPUT_RECONNECTED(r, localInit, remoteInit), d: DATA_WAIT_FOR_DUAL_FUNDING_SIGNED) =>
23722372
activeConnection = r
23732373
val myFirstPerCommitmentPoint = channelKeys.commitmentPoint(0)
2374-
val nextFundingTlv: Set[ChannelReestablishTlv] = Set(ChannelReestablishTlv.NextFundingTlv(d.signingSession.fundingTxId))
2374+
val nextFundingTlv = Set[ChannelReestablishTlv](ChannelReestablishTlv.NextFundingTlv(d.signingSession.fundingTxId, d.signingSession.retransmitRemoteCommitSig))
23752375
val nonceTlvs = d.signingSession.fundingParams.commitmentFormat match {
23762376
case _: SegwitV0CommitmentFormat => Set.empty
23772377
case _: SimpleTaprootChannelCommitmentFormat =>
@@ -2389,7 +2389,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
23892389
}
23902390
val channelReestablish = ChannelReestablish(
23912391
channelId = d.channelId,
2392-
nextLocalCommitmentNumber = d.signingSession.nextLocalCommitmentNumber,
2392+
nextLocalCommitmentNumber = 1,
23932393
nextRemoteRevocationNumber = 0,
23942394
yourLastPerCommitmentSecret = PrivateKey(ByteVector32.Zeroes),
23952395
myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint,
@@ -2403,31 +2403,19 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
24032403
val remotePerCommitmentSecrets = d.commitments.remotePerCommitmentSecrets
24042404
val yourLastPerCommitmentSecret = remotePerCommitmentSecrets.lastIndex.flatMap(remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
24052405
val myCurrentPerCommitmentPoint = channelKeys.commitmentPoint(d.commitments.localCommitIndex)
2406-
// If we disconnected while signing a funding transaction, we may need our peer to retransmit their commit_sig.
2407-
val nextLocalCommitmentNumber = d match {
2408-
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
2409-
case DualFundingStatus.RbfWaitingForSigs(status) => status.nextLocalCommitmentNumber
2410-
case _ => d.commitments.localCommitIndex + 1
2411-
}
2412-
case d: DATA_NORMAL => d.spliceStatus match {
2413-
case SpliceStatus.SpliceWaitingForSigs(status) => status.nextLocalCommitmentNumber
2414-
case _ => d.commitments.localCommitIndex + 1
2415-
}
2416-
case _ => d.commitments.localCommitIndex + 1
2417-
}
2418-
// If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures.
2406+
// If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures and commit_sig.
24192407
val rbfTlv: Set[ChannelReestablishTlv] = d match {
24202408
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.status match {
2421-
case DualFundingStatus.RbfWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId))
2409+
case DualFundingStatus.RbfWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId, status.retransmitRemoteCommitSig))
24222410
case _ => d.latestFundingTx.sharedTx match {
2423-
case _: InteractiveTxBuilder.PartiallySignedSharedTransaction => Set(ChannelReestablishTlv.NextFundingTlv(d.latestFundingTx.sharedTx.txId))
2411+
case _: InteractiveTxBuilder.PartiallySignedSharedTransaction => Set(ChannelReestablishTlv.NextFundingTlv(d.latestFundingTx.sharedTx.txId, retransmitCommitSig = false))
24242412
case _: InteractiveTxBuilder.FullySignedSharedTransaction => Set.empty
24252413
}
24262414
}
24272415
case d: DATA_NORMAL => d.spliceStatus match {
2428-
case SpliceStatus.SpliceWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId))
2416+
case SpliceStatus.SpliceWaitingForSigs(status) => Set(ChannelReestablishTlv.NextFundingTlv(status.fundingTx.txId, status.retransmitRemoteCommitSig))
24292417
case _ => d.commitments.latest.localFundingStatus match {
2430-
case LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _, _) => Set(ChannelReestablishTlv.NextFundingTlv(fundingTx.txId))
2418+
case LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _, _) => Set(ChannelReestablishTlv.NextFundingTlv(fundingTx.txId, retransmitCommitSig = false))
24312419
case _ => Set.empty
24322420
}
24332421
}
@@ -2477,7 +2465,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
24772465

24782466
val channelReestablish = ChannelReestablish(
24792467
channelId = d.channelId,
2480-
nextLocalCommitmentNumber = nextLocalCommitmentNumber,
2468+
nextLocalCommitmentNumber = d.commitments.localCommitIndex + 1,
24812469
nextRemoteRevocationNumber = d.commitments.remoteCommitIndex,
24822470
yourLastPerCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret),
24832471
myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint,
@@ -2529,7 +2517,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
25292517
case _ =>
25302518
remoteNextCommitNonces = channelReestablish.nextCommitNonces
25312519
channelReestablish.nextFundingTxId_opt match {
2532-
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.nextLocalCommitmentNumber == 0 =>
2520+
case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.retransmitInteractiveTxCommitSig =>
25332521
// They haven't received our commit_sig: we retransmit it, and will send our tx_signatures once we've received
25342522
// their commit_sig or their tx_signatures (depending on who must send tx_signatures first).
25352523
val fundingParams = d.signingSession.fundingParams
@@ -2557,7 +2545,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
25572545
case Some(fundingTxId) =>
25582546
d.status match {
25592547
case DualFundingStatus.RbfWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
2560-
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2548+
if (channelReestablish.retransmitInteractiveTxCommitSig) {
25612549
// They haven't received our commit_sig: we retransmit it.
25622550
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
25632551
val fundingParams = signingSession.fundingParams
@@ -2574,7 +2562,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
25742562
case _ if d.latestFundingTx.sharedTx.txId == fundingTxId =>
25752563
// We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures
25762564
// and our commit_sig if they haven't received it already.
2577-
if (channelReestablish.nextLocalCommitmentNumber == 0) {
2565+
if (channelReestablish.retransmitInteractiveTxCommitSig) {
25782566
val remoteNonce_opt = channelReestablish.currentCommitNonce_opt
25792567
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 {
25802568
case Left(e) => handleLocalError(e, d, Some(channelReestablish))
@@ -2612,7 +2600,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
26122600
channelReestablish.nextFundingTxId_opt match {
26132601
case Some(fundingTxId) if fundingTxId == d.commitments.latest.fundingTxId =>
26142602
d.commitments.latest.localFundingStatus.localSigs_opt match {
2615-
case Some(txSigs) if channelReestablish.nextLocalCommitmentNumber == 0 =>
2603+
case Some(txSigs) if channelReestablish.retransmitInteractiveTxCommitSig =>
26162604
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
26172605
val remoteNonce_opt = channelReestablish.currentCommitNonce_opt
26182606
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 {
@@ -2673,9 +2661,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
26732661
sendQueue = sendQueue ++ syncSuccess.retransmit
26742662

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

@@ -3399,7 +3386,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
33993386
case Some(fundingTxId) =>
34003387
d.spliceStatus match {
34013388
case SpliceStatus.SpliceWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId =>
3402-
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
3389+
if (channelReestablish.retransmitInteractiveTxCommitSig) {
34033390
// They haven't received our commit_sig: we retransmit it.
34043391
// We're also waiting for signatures from them, and will send our tx_signatures once we receive them.
34053392
log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId)
@@ -3416,7 +3403,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
34163403
case dfu: LocalFundingStatus.DualFundedUnconfirmedFundingTx =>
34173404
// We've already received their commit_sig and sent our tx_signatures. We retransmit our
34183405
// tx_signatures and our commit_sig if they haven't received it already.
3419-
if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) {
3406+
if (channelReestablish.retransmitInteractiveTxCommitSig) {
34203407
log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId)
34213408
val remoteNonce_opt = channelReestablish.currentCommitNonce_opt
34223409
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
@@ -1189,11 +1189,8 @@ object InteractiveTxSigningSession {
11891189
liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends InteractiveTxSigningSession {
11901190
val fundingTxId: TxId = fundingTx.txId
11911191
val localCommitIndex: Long = localCommit.fold(_.index, _.index)
1192-
// This value tells our peer whether we need them to retransmit their commit_sig on reconnection or not.
1193-
val nextLocalCommitmentNumber: Long = localCommit match {
1194-
case Left(unsignedCommit) => unsignedCommit.index
1195-
case Right(commit) => commit.index + 1
1196-
}
1192+
// If we haven't received the remote commit_sig, we will request a retransmission on reconnection.
1193+
val retransmitRemoteCommitSig: Boolean = localCommit.isLeft
11971194

11981195
def localFundingKey(channelKeys: ChannelKeys): PrivateKey = channelKeys.fundingKey(fundingTxIndex)
11991196

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
@@ -213,6 +213,7 @@ case class ChannelReestablish(channelId: ByteVector32,
213213
myCurrentPerCommitmentPoint: PublicKey,
214214
tlvStream: TlvStream[ChannelReestablishTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId {
215215
val nextFundingTxId_opt: Option[TxId] = tlvStream.get[ChannelReestablishTlv.NextFundingTlv].map(_.txId)
216+
val retransmitInteractiveTxCommitSig: Boolean = tlvStream.get[ChannelReestablishTlv.NextFundingTlv].exists(_.retransmitCommitSig)
216217
val myCurrentFundingLocked_opt: Option[TxId] = tlvStream.get[ChannelReestablishTlv.MyCurrentFundingLockedTlv].map(_.txId)
217218
val retransmitAnnSigs: Boolean = tlvStream.get[ChannelReestablishTlv.MyCurrentFundingLockedTlv].exists(_.retransmitAnnSigs)
218219
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
@@ -452,13 +452,15 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
452452

453453
alice ! INPUT_RECONNECTED(bob, aliceInit, bobInit)
454454
val channelReestablishAlice = alice2bob.expectMsgType[ChannelReestablish]
455-
assert(channelReestablishAlice.nextLocalCommitmentNumber == 0)
455+
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
456+
assert(channelReestablishAlice.retransmitInteractiveTxCommitSig)
456457
assert(channelReestablishAlice.currentCommitNonce_opt.nonEmpty)
457458
assert(channelReestablishAlice.nextCommitNonces.contains(fundingTxId))
458459

459460
bob ! INPUT_RECONNECTED(alice, bobInit, aliceInit)
460461
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
461-
assert(channelReestablishBob.nextLocalCommitmentNumber == 0)
462+
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
463+
assert(channelReestablishBob.retransmitInteractiveTxCommitSig)
462464
assert(channelReestablishBob.currentCommitNonce_opt.nonEmpty)
463465
assert(channelReestablishBob.nextCommitNonces.contains(fundingTxId))
464466

@@ -557,12 +559,14 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
557559
assert(channelReestablishAlice.nextCommitNonces.contains(fundingTx.txid))
558560
assert(channelReestablishAlice.nextCommitNonces.get(fundingTx.txid) != channelReestablishAlice.currentCommitNonce_opt)
559561
assert(channelReestablishAlice.nextFundingTxId_opt.contains(fundingTx.txid))
560-
assert(channelReestablishAlice.nextLocalCommitmentNumber == 0)
562+
assert(channelReestablishAlice.retransmitInteractiveTxCommitSig)
563+
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
561564
alice2bob.forward(bob, channelReestablishAlice)
562565
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
563566
assert(channelReestablishBob.currentCommitNonce_opt.isEmpty)
564567
assert(channelReestablishBob.nextCommitNonces.get(fundingTx.txid) == channelReadyB.nextCommitNonce_opt)
565568
assert(channelReestablishBob.nextFundingTxId_opt.isEmpty)
569+
assert(!channelReestablishBob.retransmitInteractiveTxCommitSig)
566570
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
567571
bob2alice.forward(alice, channelReestablishBob)
568572

@@ -700,14 +704,14 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
700704
alice ! INPUT_RECONNECTED(bob, aliceInit, bobInit)
701705
bob ! INPUT_RECONNECTED(alice, bobInit, aliceInit)
702706
val channelReestablishAlice = alice2bob.expectMsgType[ChannelReestablish]
703-
val nextLocalCommitmentNumberAlice = if (aliceExpectsCommitSig) 0 else 1
704707
assert(channelReestablishAlice.nextFundingTxId_opt.contains(fundingTxId))
705-
assert(channelReestablishAlice.nextLocalCommitmentNumber == nextLocalCommitmentNumberAlice)
708+
assert(channelReestablishAlice.retransmitInteractiveTxCommitSig == aliceExpectsCommitSig)
709+
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
706710
alice2bob.forward(bob, channelReestablishAlice)
707711
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
708-
val nextLocalCommitmentNumberBob = if (bobExpectsCommitSig) 0 else 1
709712
assert(channelReestablishBob.nextFundingTxId_opt.contains(fundingTxId))
710-
assert(channelReestablishBob.nextLocalCommitmentNumber == nextLocalCommitmentNumberBob)
713+
assert(channelReestablishBob.retransmitInteractiveTxCommitSig == bobExpectsCommitSig)
714+
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
711715
bob2alice.forward(alice, channelReestablishBob)
712716

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

0 commit comments

Comments
 (0)