Skip to content

Commit

Permalink
Correctly set next_commitment_number during splice reconnect
Browse files Browse the repository at this point in the history
As pointed out in lightning/bolts#1214, when
reconnecting a partially signed `interactive-tx` session, we should
set `next_commitment_number` to the current commitment number if we
haven't received our peer's `commit_sig`, which tells them they need
to retransmit it.

More importantly, if our peer behaves correctly and sends us the
current commitment number, we must not think that they're late and
halt, waiting for them to send `error`. This commit fixes that by
allowing our peers to use the current commitment number when they
set `next_funding_txid`.

Note that we keep retransmitting our `commit_sig` regardless of the
value our peer set in `next_commitment_number`, because we need to
wait for them to have an opportunity to upgrade. In a future commit
we will stop sending spurious `commit_sig`.
  • Loading branch information
t-bast committed Jan 17, 2025
1 parent ba75224 commit 95f6261
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,11 @@ data class InteractiveTxSigningSession(
is Either.Left -> localCommit.value.commitTx.input
is Either.Right -> localCommit.value.publishableTxs.commitTx.input
}
// This value tells our peer whether we need them to retransmit their commit_sig on reconnection or not.
val reconnectNextLocalCommitmentNumber = when (localCommit) {
is Either.Left -> localCommit.value.index
is Either.Right -> localCommit.value.index + 1
}

fun receiveCommitSig(channelKeys: KeyManager.ChannelKeys, channelParams: ChannelParams, remoteCommitSig: CommitSig, currentBlockHeight: Long, logger: MDCLogger): Pair<InteractiveTxSigningSession, InteractiveTxSigningSessionAction> {
return when (localCommit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import fr.acinq.lightning.channel.Helpers.Closing.claimRevokedRemoteCommitTxOutp
import fr.acinq.lightning.channel.Helpers.Closing.getRemotePerCommitmentSecret
import fr.acinq.lightning.crypto.KeyManager
import fr.acinq.lightning.db.ChannelClosingType
import fr.acinq.lightning.logging.*
import fr.acinq.lightning.logging.LoggingContext
import fr.acinq.lightning.logging.MDCLogger
import fr.acinq.lightning.serialization.channel.Encryption.from
import fr.acinq.lightning.transactions.Transactions.TransactionWithInputInfo.ClosingTx
import fr.acinq.lightning.utils.*
import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.utils.sat
import fr.acinq.lightning.wire.*
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
Expand Down Expand Up @@ -306,7 +308,7 @@ sealed class PersistedChannelState : ChannelState() {
val myFirstPerCommitmentPoint = keyManager.channelKeys(state.channelParams.localParams.fundingKeyPath).commitmentPoint(0)
ChannelReestablish(
channelId = channelId,
nextLocalCommitmentNumber = 1,
nextLocalCommitmentNumber = state.signingSession.reconnectNextLocalCommitmentNumber,
nextRemoteRevocationNumber = 0,
yourLastCommitmentSecret = PrivateKey(ByteVector32.Zeroes),
myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint,
Expand All @@ -316,15 +318,28 @@ sealed class PersistedChannelState : ChannelState() {
is ChannelStateWithCommitments -> {
val yourLastPerCommitmentSecret = state.commitments.remotePerCommitmentSecrets.lastIndex?.let { state.commitments.remotePerCommitmentSecrets.getHash(it) } ?: ByteVector32.Zeroes
val myCurrentPerCommitmentPoint = keyManager.channelKeys(state.commitments.params.localParams.fundingKeyPath).commitmentPoint(state.commitments.localCommitIndex)
// If we disconnected while signing a funding transaction, we may need our peer to retransmit their commit_sig.
val nextLocalCommitmentNumber = when (state) {
is WaitForFundingConfirmed -> when (state.rbfStatus) {
is RbfStatus.WaitingForSigs -> state.rbfStatus.session.reconnectNextLocalCommitmentNumber
else -> state.commitments.localCommitIndex + 1
}
is Normal -> when (state.spliceStatus) {
is SpliceStatus.WaitingForSigs -> state.spliceStatus.session.reconnectNextLocalCommitmentNumber
else -> state.commitments.localCommitIndex + 1
}
else -> state.commitments.localCommitIndex + 1
}
// If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures.
val unsignedFundingTxId = when (state) {
is WaitForFundingConfirmed -> state.getUnsignedFundingTxId()
is Normal -> state.getUnsignedFundingTxId() // a splice was in progress, we tell our peer that we are remembering it and are expecting signatures
is Normal -> state.getUnsignedFundingTxId()
else -> null
}
val tlvs: TlvStream<ChannelReestablishTlv> = unsignedFundingTxId?.let { TlvStream(ChannelReestablishTlv.NextFunding(it)) } ?: TlvStream.empty()
ChannelReestablish(
channelId = channelId,
nextLocalCommitmentNumber = state.commitments.localCommitIndex + 1,
nextLocalCommitmentNumber = nextLocalCommitmentNumber,
nextRemoteRevocationNumber = state.commitments.remoteCommitIndex,
yourLastCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret),
myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent:
// we resend the same updates and the same sig, and preserve the same ordering
val signedUpdates = commitments.changes.localChanges.signed
val commitSigs = commitments.active.map { it.nextRemoteCommit }.filterIsInstance<NextRemoteCommit>().map { it.sig }
SyncResult.Success(retransmit = when (retransmitRevocation) {
val retransmit = when (retransmitRevocation) {
null -> buildList {
addAll(signedUpdates)
addAll(commitSigs)
Expand All @@ -424,7 +424,8 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent:
addAll(commitSigs)
}
}
})
}
SyncResult.Success(retransmit)
}
remoteChannelReestablish.nextLocalCommitmentNumber == (commitments.nextRemoteCommitIndex + 1) -> {
// we just sent a new commit_sig, they have received it but we haven't received their revocation
Expand All @@ -449,6 +450,11 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent:
// they have acknowledged the last commit_sig we sent
SyncResult.Success(retransmit = listOfNotNull(retransmitRevocation))
}
remoteChannelReestablish.nextLocalCommitmentNumber == commitments.remoteCommitIndex && remoteChannelReestablish.nextFundingTxId != null -> {
// they haven't received the commit_sig we sent as part of signing a splice transaction
// we will retransmit it before exchanging tx_signatures
SyncResult.Success(retransmit = listOfNotNull(retransmitRevocation))
}
remoteChannelReestablish.nextLocalCommitmentNumber < (commitments.remoteCommitIndex + 1) -> {
// they are behind
SyncResult.Failure.RemoteLate
Expand Down
Loading

0 comments on commit 95f6261

Please sign in to comment.