Skip to content

Commit a1c6988

Browse files
authored
Stricter batching of commit_sig messages on the wire (#3083)
We introduce a `CommitSigBatch` class to group `commit_sig` messages when splice transactions are pending. We use this class to ensure that all the `commit_sig` messages in the batch are sent together to our peer, without any other messages in-between. We move the incoming `commit_sig` batching logic outside of the channel and into the `PeerConnection` instead. This slightly simplifies the channel FSM and its tests, since the `PeerConnection` actor is simpler. We unfortunately cannot easily do this in the `TransportHandler` because of our buffered read of the encrypted messages, which may split batches and make it more complex to correctly group messages.
1 parent dd622ad commit a1c6988

File tree

10 files changed

+395
-334
lines changed

10 files changed

+395
-334
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,7 @@ case class Commitments(params: ChannelParams,
10121012
}
10131013
}
10141014

1015-
def sendCommit(channelKeys: ChannelKeys)(implicit log: LoggingAdapter): Either[ChannelException, (Commitments, Seq[CommitSig])] = {
1015+
def sendCommit(channelKeys: ChannelKeys)(implicit log: LoggingAdapter): Either[ChannelException, (Commitments, CommitSigs)] = {
10161016
remoteNextCommitInfo match {
10171017
case Right(_) if !changes.localHasChanges => Left(CannotSignWithoutChanges(channelId))
10181018
case Right(remoteNextPerCommitmentPoint) =>
@@ -1026,21 +1026,24 @@ case class Commitments(params: ChannelParams,
10261026
active = active1,
10271027
remoteNextCommitInfo = Left(WaitForRev(localCommitIndex))
10281028
)
1029-
Right(commitments1, sigs)
1029+
Right(commitments1, CommitSigs(sigs))
10301030
case Left(_) => Left(CannotSignBeforeRevocation(channelId))
10311031
}
10321032
}
10331033

1034-
def receiveCommit(commits: Seq[CommitSig], channelKeys: ChannelKeys)(implicit log: LoggingAdapter): Either[ChannelException, (Commitments, RevokeAndAck)] = {
1034+
def receiveCommit(commitSigs: CommitSigs, channelKeys: ChannelKeys)(implicit log: LoggingAdapter): Either[ChannelException, (Commitments, RevokeAndAck)] = {
10351035
// We may receive more commit_sig than the number of active commitments, because there can be a race where we send
10361036
// splice_locked while our peer is sending us a batch of commit_sig. When that happens, we simply need to discard
10371037
// the commit_sig that belong to commitments we deactivated.
1038-
if (commits.size < active.size) {
1039-
return Left(CommitSigCountMismatch(channelId, active.size, commits.size))
1038+
val sigs = commitSigs match {
1039+
case batch: CommitSigBatch if batch.batchSize < active.size => return Left(CommitSigCountMismatch(channelId, active.size, batch.batchSize))
1040+
case batch: CommitSigBatch => batch.messages
1041+
case _: CommitSig if active.size > 1 => return Left(CommitSigCountMismatch(channelId, active.size, 1))
1042+
case commitSig: CommitSig => Seq(commitSig)
10401043
}
10411044
val commitKeys = LocalCommitmentKeys(params, channelKeys, localCommitIndex + 1)
10421045
// Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments.
1043-
val active1 = active.zip(commits).map { case (commitment, commit) =>
1046+
val active1 = active.zip(sigs).map { case (commitment, commit) =>
10441047
commitment.receiveCommit(params, channelKeys, commitKeys, changes, commit) match {
10451048
case Left(f) => return Left(f)
10461049
case Right(commitment1) => commitment1
@@ -1171,7 +1174,7 @@ case class Commitments(params: ChannelParams,
11711174
case ChannelSpendSignature.IndividualSignature(latestRemoteSig) => latestRemoteSig == commitSig.signature
11721175
case ChannelSpendSignature.PartialSignatureWithNonce(_, _) => ???
11731176
}
1174-
params.channelFeatures.hasFeature(Features.DualFunding) && commitSig.batchSize == 1 && isLatestSig
1177+
params.channelFeatures.hasFeature(Features.DualFunding) && isLatestSig
11751178
}
11761179

11771180
def localFundingSigs(fundingTxId: TxId): Option[TxSignatures] = {

eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -479,14 +479,14 @@ object Helpers {
479479
// we just sent a new commit_sig but they didn't receive it
480480
// we resend the same updates and the same sig, and preserve the same ordering
481481
val signedUpdates = commitments.changes.localChanges.signed
482-
val commitSigs = commitments.active.flatMap(_.nextRemoteCommit_opt).map(_.sig)
482+
val commitSigs = CommitSigs(commitments.active.flatMap(_.nextRemoteCommit_opt).map(_.sig))
483483
retransmitRevocation_opt match {
484484
case None =>
485-
SyncResult.Success(retransmit = signedUpdates ++ commitSigs)
485+
SyncResult.Success(retransmit = signedUpdates :+ commitSigs)
486486
case Some(revocation) if commitments.localCommitIndex > waitingForRevocation.sentAfterLocalCommitIndex =>
487-
SyncResult.Success(retransmit = signedUpdates ++ commitSigs ++ Seq(revocation))
487+
SyncResult.Success(retransmit = signedUpdates :+ commitSigs :+ revocation)
488488
case Some(revocation) =>
489-
SyncResult.Success(retransmit = Seq(revocation) ++ signedUpdates ++ commitSigs)
489+
SyncResult.Success(retransmit = revocation +: signedUpdates :+ commitSigs)
490490
}
491491
case Left(_) if remoteChannelReestablish.nextLocalCommitmentNumber == (commitments.nextRemoteCommitIndex + 1) =>
492492
// we just sent a new commit_sig, they have received it but we haven't received their revocation

0 commit comments

Comments
 (0)