Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send channel_announcement for splice transactions on public channels #2968

Merged
merged 4 commits into from
Jan 30, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Expose lastAnnouncedFundingTxId_opt
And make past codecs decode-only.
t-bast committed Jan 30, 2025
commit fde052db50d55607b8fbbd4bcaf97c2a3edfc358
Original file line number Diff line number Diff line change
@@ -632,6 +632,7 @@ final case class DATA_NORMAL(commitments: Commitments,
closingFeerates: Option[ClosingFeerates],
spliceStatus: SpliceStatus) extends ChannelDataWithCommitments {
val lastAnnouncedCommitment_opt: Option[AnnouncedCommitment] = lastAnnouncement_opt.flatMap(ann => commitments.resolveCommitment(ann.shortChannelId).map(c => AnnouncedCommitment(c, ann)))
val lastAnnouncedFundingTxId_opt: Option[TxId] = lastAnnouncedCommitment_opt.map(_.fundingTxId)
val isNegotiatingQuiescence: Boolean = spliceStatus.isNegotiatingQuiescence
val isQuiescent: Boolean = spliceStatus.isQuiescent
}
Original file line number Diff line number Diff line change
@@ -1225,32 +1225,32 @@ case class Commitments(params: ChannelParams,
* @param updateMethod This method is tricky: it passes the fundingTxIndex of the commitment corresponding to the
* fundingTxId, because in the remote case we may update several commitments.
*/
private def updateFundingStatus(fundingTxId: TxId, lastAnnounced_opt: Option[AnnouncedCommitment], updateMethod: Long => PartialFunction[Commitment, Commitment])(implicit log: LoggingAdapter): Either[Commitments, (Commitments, Commitment)] = {
private def updateFundingStatus(fundingTxId: TxId, lastAnnouncedFundingTxId_opt: Option[TxId], updateMethod: Long => PartialFunction[Commitment, Commitment])(implicit log: LoggingAdapter): Either[Commitments, (Commitments, Commitment)] = {
all.find(_.fundingTxId == fundingTxId) match {
case Some(commitment) =>
val commitments1 = copy(
active = active.map(updateMethod(commitment.fundingTxIndex)),
inactive = inactive.map(updateMethod(commitment.fundingTxIndex))
)
val commitment1 = commitments1.all.find(_.fundingTxId == fundingTxId).get // NB: this commitment might be pruned at the next line
val commitments2 = commitments1.deactivateCommitments().pruneCommitments(lastAnnounced_opt)
val commitments2 = commitments1.deactivateCommitments().pruneCommitments(lastAnnouncedFundingTxId_opt)
Right(commitments2, commitment1)
case None =>
log.warning(s"fundingTxId=$fundingTxId doesn't match any of our funding txs")
Left(this)
}
}

def updateLocalFundingStatus(fundingTxId: TxId, status: LocalFundingStatus, lastAnnounced_opt: Option[AnnouncedCommitment])(implicit log: LoggingAdapter): Either[Commitments, (Commitments, Commitment)] =
updateFundingStatus(fundingTxId, lastAnnounced_opt, _ => {
def updateLocalFundingStatus(fundingTxId: TxId, status: LocalFundingStatus, lastAnnouncedFundingTxId_opt: Option[TxId])(implicit log: LoggingAdapter): Either[Commitments, (Commitments, Commitment)] =
updateFundingStatus(fundingTxId, lastAnnouncedFundingTxId_opt, _ => {
case c if c.fundingTxId == fundingTxId =>
log.info(s"setting localFundingStatus=${status.getClass.getSimpleName} for fundingTxId=${c.fundingTxId} fundingTxIndex=${c.fundingTxIndex}")
c.copy(localFundingStatus = status)
case c => c
})

def updateRemoteFundingStatus(fundingTxId: TxId, lastAnnounced_opt: Option[AnnouncedCommitment])(implicit log: LoggingAdapter): Either[Commitments, (Commitments, Commitment)] =
updateFundingStatus(fundingTxId, lastAnnounced_opt, fundingTxIndex => {
def updateRemoteFundingStatus(fundingTxId: TxId, lastAnnouncedFundingTxId_opt: Option[TxId])(implicit log: LoggingAdapter): Either[Commitments, (Commitments, Commitment)] =
updateFundingStatus(fundingTxId, lastAnnouncedFundingTxId_opt, fundingTxIndex => {
// all funding older than this one are considered locked
case c if c.fundingTxId == fundingTxId || c.fundingTxIndex < fundingTxIndex =>
log.info(s"setting remoteFundingStatus=${RemoteFundingStatus.Locked.getClass.getSimpleName} for fundingTxId=${c.fundingTxId} fundingTxIndex=${c.fundingTxIndex}")
@@ -1300,12 +1300,12 @@ case class Commitments(params: ChannelParams,
* We can prune commitments in two cases:
* - their funding tx has been permanently double-spent by the funding tx of a concurrent commitment (happens when using RBF)
* - their funding tx has been permanently spent by a splice tx
*
*
* But we need to keep our last announced commitment if the channel is public, even if it has been permanently spent
* by a newer splice tx that hasn't been announced yet, otherwise we won't know which short_channel_id to use when
* creating channel_updates.
*/
private def pruneCommitments(lastAnnounced_opt: Option[AnnouncedCommitment])(implicit log: LoggingAdapter): Commitments = {
private def pruneCommitments(lastAnnouncedFundingTxId_opt: Option[TxId])(implicit log: LoggingAdapter): Commitments = {
all
.filter(_.localFundingStatus.isInstanceOf[LocalFundingStatus.ConfirmedFundingTx])
.sortBy(_.fundingTxIndex)
@@ -1317,7 +1317,8 @@ case class Commitments(params: ChannelParams,
// If the most recently confirmed commitment isn't announced yet, we cannot prune the last commitment we
// announced, because our channel updates are based on its announcement (and its short_channel_id).
// If we never announced the channel, we don't need to announce old commitments, we will directly announce the last one.
val pruningIndex = lastAnnounced_opt.map(_.fundingTxIndex).getOrElse(lastConfirmed.fundingTxIndex)
val lastAnnouncedFundingTxIndex_opt = lastAnnouncedFundingTxId_opt.flatMap(txId => all.find(_.fundingTxId == txId).map(_.fundingTxIndex))
val pruningIndex = lastAnnouncedFundingTxIndex_opt.getOrElse(lastConfirmed.fundingTxIndex)
// We can prune all RBF candidates, and commitments that came before the last announced one.
inactive.filter(c => c.fundingTxIndex < pruningIndex || (c.fundingTxIndex == lastConfirmed.fundingTxIndex && c.fundingTxId != lastConfirmed.fundingTxId))
} else {
Original file line number Diff line number Diff line change
@@ -1297,7 +1297,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
stay() sending Error(d.channelId, InvalidFundingSignature(d.channelId, Some(fundingTx.txId)).getMessage)
case Right(fundingTx) =>
val dfu1 = dfu.copy(sharedTx = fundingTx)
d.commitments.updateLocalFundingStatus(msg.txId, dfu1, d.lastAnnouncedCommitment_opt) match {
d.commitments.updateLocalFundingStatus(msg.txId, dfu1, d.lastAnnouncedFundingTxId_opt) match {
case Right((commitments1, _)) =>
log.info("publishing funding tx for channelId={} fundingTxId={}", d.channelId, fundingTx.signedTx.txid)
Metrics.recordSplice(dfu.fundingParams, fundingTx.tx)
@@ -1332,7 +1332,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with

case Event(w: WatchPublishedTriggered, d: DATA_NORMAL) =>
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid))
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, d.lastAnnouncedCommitment_opt) match {
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, d.lastAnnouncedFundingTxId_opt) match {
case Right((commitments1, _)) =>
watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthFunding), delay_opt = None)
maybeEmitEventsPostSplice(d.aliases, d.commitments, commitments1, d.lastAnnouncement_opt)
@@ -1368,7 +1368,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
}

case Event(msg: SpliceLocked, d: DATA_NORMAL) =>
d.commitments.updateRemoteFundingStatus(msg.fundingTxId, d.lastAnnouncedCommitment_opt) match {
d.commitments.updateRemoteFundingStatus(msg.fundingTxId, d.lastAnnouncedFundingTxId_opt) match {
case Right((commitments1, commitment)) =>
// If the commitment is confirmed, we were waiting to receive the remote splice_locked before sending our announcement_signatures.
val localAnnSigs_opt = if (d.commitments.announceChannel) commitment.signAnnouncement(nodeParams, commitments1.params) else None
@@ -2537,11 +2537,11 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
stay()
} else {
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid))
val lastAnnouncedCommitment_opt = d match {
case d: DATA_NORMAL => d.lastAnnouncedCommitment_opt
val lastAnnouncedFundingTxId_opt = d match {
case d: DATA_NORMAL => d.lastAnnouncedFundingTxId_opt
case _ => None
}
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, lastAnnouncedCommitment_opt) match {
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, lastAnnouncedFundingTxId_opt) match {
case Right((commitments1, _)) =>
log.info("zero-conf funding txid={} has been published", w.tx.txid)
watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthFunding), delay_opt = None)
Original file line number Diff line number Diff line change
@@ -724,7 +724,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
case Event(w: WatchPublishedTriggered, d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED) =>
log.info("funding txid={} was successfully published for zero-conf channelId={}", w.tx.txid, d.channelId)
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid))
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, lastAnnounced_opt = None) match {
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, lastAnnouncedFundingTxId_opt = None) match {
case Right((commitments1, _)) =>
// we still watch the funding tx for confirmation even if we can use the zero-conf channel right away
watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthFunding), delay_opt = None)
Original file line number Diff line number Diff line change
@@ -392,7 +392,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {

case Event(w: WatchPublishedTriggered, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) =>
val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, None, None)
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, lastAnnounced_opt = None) match {
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, lastAnnouncedFundingTxId_opt = None) match {
case Right((commitments1, _)) =>
log.info("funding txid={} was successfully published for zero-conf channelId={}", w.tx.txid, d.channelId)
// we still watch the funding tx for confirmation even if we can use the zero-conf channel right away
Original file line number Diff line number Diff line change
@@ -92,11 +92,11 @@ trait CommonFundingHandlers extends CommonHandlers {
// previous funding transaction. Our peer cannot publish the corresponding revoked commitments anymore, so we can
// clean-up the htlc data that we were storing for the matching penalty transactions.
context.system.eventStream.publish(RevokedHtlcInfoCleaner.ForgetHtlcInfos(d.channelId, beforeCommitIndex = c.firstRemoteCommitIndex))
val lastAnnouncedCommitment_opt = d match {
case d: DATA_NORMAL => d.lastAnnouncedCommitment_opt
val lastAnnouncedFundingTxId_opt = d match {
case d: DATA_NORMAL => d.lastAnnouncedFundingTxId_opt
case _ => None
}
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, lastAnnouncedCommitment_opt).map {
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus, lastAnnouncedFundingTxId_opt).map {
case (commitments1, commitment) =>
// First of all, we watch the funding tx that is now confirmed.
// Children splice transactions may already spend that confirmed funding transaction.
Original file line number Diff line number Diff line change
@@ -652,7 +652,7 @@ private[channel] object ChannelCodecs4 {
("real_opt" | optional(bool8, realshortchannelid)) ::
("localAlias" | discriminated[Alias].by(uint16).typecase(1, alias)) ::
("remoteAlias_opt" | optional(bool8, alias))
).as[ChannelTypes4.ShortIds]
).as[ChannelTypes4.ShortIds].decodeOnly

val DATA_WAIT_FOR_FUNDING_CONFIRMED_00_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = (
("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) ::
@@ -668,11 +668,11 @@ private[channel] object ChannelCodecs4 {

val DATA_WAIT_FOR_CHANNEL_READY_01_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = (
("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) ::
("shortIds" | shortids)).as[ChannelTypes4.DATA_WAIT_FOR_CHANNEL_READY_0b].xmap(d => d.migrate(), d => ChannelTypes4.DATA_WAIT_FOR_CHANNEL_READY_0b.from(d))
("shortIds" | shortids)).as[ChannelTypes4.DATA_WAIT_FOR_CHANNEL_READY_0b].map(_.migrate()).decodeOnly

val DATA_WAIT_FOR_CHANNEL_READY_0b_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = (
("commitments" | versionedCommitmentsCodec) ::
("shortIds" | shortids)).as[ChannelTypes4.DATA_WAIT_FOR_CHANNEL_READY_0b].xmap(d => d.migrate(), d => ChannelTypes4.DATA_WAIT_FOR_CHANNEL_READY_0b.from(d))
("shortIds" | shortids)).as[ChannelTypes4.DATA_WAIT_FOR_CHANNEL_READY_0b].map(_.migrate()).decodeOnly

val DATA_WAIT_FOR_CHANNEL_READY_15_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = (
("commitments" | versionedCommitmentsCodec) ::
@@ -714,11 +714,11 @@ private[channel] object ChannelCodecs4 {

val DATA_WAIT_FOR_DUAL_FUNDING_READY_03_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_READY] = (
("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) ::
("shortIds" | shortids)).as[ChannelTypes4.DATA_WAIT_FOR_DUAL_FUNDING_READY_0d].xmap(d => d.migrate(), d => ChannelTypes4.DATA_WAIT_FOR_DUAL_FUNDING_READY_0d.from(d))
("shortIds" | shortids)).as[ChannelTypes4.DATA_WAIT_FOR_DUAL_FUNDING_READY_0d].map(_.migrate()).decodeOnly

val DATA_WAIT_FOR_DUAL_FUNDING_READY_0d_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_READY] = (
("commitments" | versionedCommitmentsCodec) ::
("shortIds" | shortids)).as[ChannelTypes4.DATA_WAIT_FOR_DUAL_FUNDING_READY_0d].xmap(d => d.migrate(), d => ChannelTypes4.DATA_WAIT_FOR_DUAL_FUNDING_READY_0d.from(d))
("shortIds" | shortids)).as[ChannelTypes4.DATA_WAIT_FOR_DUAL_FUNDING_READY_0d].map(_.migrate()).decodeOnly

val DATA_WAIT_FOR_DUAL_FUNDING_READY_16_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_READY] = (
("commitments" | versionedCommitmentsCodec) ::
@@ -732,7 +732,7 @@ private[channel] object ChannelCodecs4 {
("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
("closingFeerates" | optional(bool8, closingFeeratesCodec)) ::
("spliceStatus" | spliceStatusCodec)).as[ChannelTypes4.DATA_NORMAL_0e].xmap(d => d.migrate(), d => ChannelTypes4.DATA_NORMAL_0e.from(d))
("spliceStatus" | spliceStatusCodec)).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly

val DATA_NORMAL_0e_Codec: Codec[DATA_NORMAL] = (
("commitments" | versionedCommitmentsCodec) ::
@@ -742,7 +742,7 @@ private[channel] object ChannelCodecs4 {
("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) ::
("closingFeerates" | optional(bool8, closingFeeratesCodec)) ::
("spliceStatus" | spliceStatusCodec)).as[ChannelTypes4.DATA_NORMAL_0e].xmap(d => d.migrate(), d => ChannelTypes4.DATA_NORMAL_0e.from(d))
("spliceStatus" | spliceStatusCodec)).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly

val DATA_NORMAL_14_Codec: Codec[DATA_NORMAL] = (
("commitments" | versionedCommitmentsCodec) ::
Original file line number Diff line number Diff line change
@@ -45,14 +45,6 @@ private[channel] object ChannelTypes4 {
}
}

object DATA_NORMAL_0e {
// shouldn't be used since we only decode old data and never encode it
def from(d: DATA_NORMAL): DATA_NORMAL_0e = {
val shortIds = ShortIds(d.lastAnnouncement_opt.map(_.shortChannelId), d.aliases.localAlias, d.aliases.remoteAlias_opt)
DATA_NORMAL_0e(d.commitments, shortIds, d.lastAnnouncement_opt, d.channelUpdate, d.localShutdown, d.remoteShutdown, d.closingFeerates, d.spliceStatus)
}
}

case class DATA_WAIT_FOR_CHANNEL_READY_0b(commitments: Commitments, shortIds: ShortIds) {
def migrate(): DATA_WAIT_FOR_CHANNEL_READY = {
val commitments1 = commitments.copy(
@@ -64,14 +56,6 @@ private[channel] object ChannelTypes4 {
}
}

object DATA_WAIT_FOR_CHANNEL_READY_0b {
// shouldn't be used since we only decode old data and never encode it
def from(d: DATA_WAIT_FOR_CHANNEL_READY): DATA_WAIT_FOR_CHANNEL_READY_0b = {
val shortIds = ShortIds(None, d.aliases.localAlias, d.aliases.remoteAlias_opt)
DATA_WAIT_FOR_CHANNEL_READY_0b(d.commitments, shortIds)
}
}

case class DATA_WAIT_FOR_DUAL_FUNDING_READY_0d(commitments: Commitments, shortIds: ShortIds) {
def migrate(): DATA_WAIT_FOR_DUAL_FUNDING_READY = {
val commitments1 = commitments.copy(
@@ -83,14 +67,6 @@ private[channel] object ChannelTypes4 {
}
}

object DATA_WAIT_FOR_DUAL_FUNDING_READY_0d {
// shouldn't be used since we only decode old data and never encode it
def from(d: DATA_WAIT_FOR_DUAL_FUNDING_READY): DATA_WAIT_FOR_DUAL_FUNDING_READY_0d = {
val shortIds = ShortIds(None, d.aliases.localAlias, d.aliases.remoteAlias_opt)
DATA_WAIT_FOR_DUAL_FUNDING_READY_0d(d.commitments, shortIds)
}
}

private def setScidIfMatches(c: Commitment, shortIds: ShortIds): Commitment = {
c.localFundingStatus match {
// We didn't support splicing on public channels in this version: the scid (if available) is for the initial