diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/Features.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/Features.kt index 50b239b74..d576187e6 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/Features.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/Features.kt @@ -161,6 +161,13 @@ sealed class Feature { override val scopes: Set get() = setOf(FeatureScope.Init, FeatureScope.Node) } + @Serializable + object Splicing : Feature() { + override val rfcName get() = "option_splice" + override val mandatory get() = 62 + override val scopes: Set get() = setOf(FeatureScope.Init, FeatureScope.Node) + } + // The following features have not been standardised, hence the high feature bits to avoid conflicts. /** This feature bit should be activated when a node accepts having their channel reserve set to 0. */ @@ -196,13 +203,6 @@ sealed class Feature { override val scopes: Set get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice) } - @Serializable - object ExperimentalSplice : Feature() { - override val rfcName get() = "splice_experimental" - override val mandatory get() = 154 - override val scopes: Set get() = setOf(FeatureScope.Init) - } - @Serializable object OnTheFlyFunding : Feature() { override val rfcName get() = "on_the_fly_funding" @@ -295,11 +295,11 @@ data class Features(val activated: Map, val unknown: Se Feature.ChannelType, Feature.PaymentMetadata, Feature.SimpleClose, + Feature.Splicing, Feature.ExperimentalTrampolinePayment, Feature.ZeroReserveChannels, Feature.WakeUpNotificationClient, Feature.WakeUpNotificationProvider, - Feature.ExperimentalSplice, Feature.OnTheFlyFunding, Feature.FundingFeeCredit, Feature.SimpleTaprootChannels @@ -335,7 +335,6 @@ data class Features(val activated: Map, val unknown: Se Feature.AnchorOutputs to listOf(Feature.StaticRemoteKey), Feature.SimpleClose to listOf(Feature.ShutdownAnySegwit), Feature.ExperimentalTrampolinePayment to listOf(Feature.PaymentSecret), - Feature.OnTheFlyFunding to listOf(Feature.ExperimentalSplice), Feature.FundingFeeCredit to listOf(Feature.OnTheFlyFunding) ) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt index 4daae7404..68a8909d3 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt @@ -172,6 +172,7 @@ data class NodeParams( require(features.hasFeature(Feature.PaymentSecret, FeatureSupport.Mandatory)) { "${Feature.PaymentSecret.rfcName} should be mandatory" } require(features.hasFeature(Feature.ChannelType, FeatureSupport.Mandatory)) { "${Feature.ChannelType.rfcName} should be mandatory" } require(features.hasFeature(Feature.DualFunding, FeatureSupport.Mandatory)) { "${Feature.DualFunding.rfcName} should be mandatory" } + require(features.hasFeature(Feature.Splicing, FeatureSupport.Mandatory)) { "${Feature.Splicing.rfcName} should be mandatory" } require(features.hasFeature(Feature.RouteBlinding)) { "${Feature.RouteBlinding.rfcName} should be supported" } require(features.hasFeature(Feature.ShutdownAnySegwit, FeatureSupport.Mandatory)) { "${Feature.ShutdownAnySegwit.rfcName} should be mandatory" } require(features.hasFeature(Feature.SimpleClose, FeatureSupport.Mandatory)) { "${Feature.SimpleClose.rfcName} should be mandatory" } @@ -203,10 +204,10 @@ data class NodeParams( Feature.ChannelType to FeatureSupport.Mandatory, Feature.PaymentMetadata to FeatureSupport.Optional, Feature.SimpleClose to FeatureSupport.Mandatory, + Feature.Splicing to FeatureSupport.Mandatory, Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional, Feature.ZeroReserveChannels to FeatureSupport.Optional, Feature.WakeUpNotificationClient to FeatureSupport.Optional, - Feature.ExperimentalSplice to FeatureSupport.Optional, Feature.OnTheFlyFunding to FeatureSupport.Optional, Feature.FundingFeeCredit to FeatureSupport.Optional, ), diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Commitments.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Commitments.kt index 43fbcfde9..1ffe356e5 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Commitments.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Commitments.kt @@ -167,7 +167,6 @@ data class RemoteCommit(val index: Long, val spec: CommitmentSpec, val txid: TxI remoteFundingPubKey: PublicKey, commitInput: Transactions.InputInfo, commitmentFormat: Transactions.CommitmentFormat, - batchSize: Int, remoteNonce: IndividualNonce?, logger: MDCLogger ): Either { @@ -194,7 +193,7 @@ data class RemoteCommit(val index: Long, val spec: CommitmentSpec, val txid: TxI return when (commitmentFormat) { Transactions.CommitmentFormat.AnchorOutputs -> { val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubKey) - Either.Right(CommitSig(channelParams.channelId, sig, htlcSigs, batchSize)) + Either.Right(CommitSig(channelParams.channelId, commitInput.outPoint.txid, sig, htlcSigs)) } Transactions.CommitmentFormat.SimpleTaprootChannels -> when (remoteNonce) { null -> Either.Left(MissingCommitNonce(channelParams.channelId, commitInput.outPoint.txid, index)) @@ -202,7 +201,7 @@ data class RemoteCommit(val index: Long, val spec: CommitmentSpec, val txid: TxI val localNonce = NonceGenerator.signingNonce(fundingKey.publicKey(), remoteFundingPubKey, commitInput.outPoint.txid) when (val psig = remoteCommitTx.partialSign(fundingKey, remoteFundingPubKey, mapOf(), localNonce, listOf(localNonce.publicNonce, remoteNonce))) { is Either.Left -> Either.Left(InvalidCommitNonce(channelParams.channelId, commitInput.outPoint.txid, index)) - is Either.Right -> Either.Right(CommitSig(channelParams.channelId, psig.value, htlcSigs, batchSize)) + is Either.Right -> Either.Right(CommitSig(channelParams.channelId, commitInput.outPoint.txid, psig.value, htlcSigs)) } } } @@ -218,7 +217,6 @@ data class RemoteCommit(val index: Long, val spec: CommitmentSpec, val txid: TxI signingSession.fundingParams.remoteFundingPubkey, signingSession.commitInput(channelKeys), signingSession.fundingParams.commitmentFormat, - batchSize = 1, remoteNonce, logger ) @@ -563,7 +561,6 @@ data class Commitment( commitKeys: RemoteCommitmentKeys, changes: CommitmentChanges, remoteNextPerCommitmentPoint: PublicKey, - batchSize: Int, nextRemoteNonce: IndividualNonce?, logger: MDCLogger ): Either> { @@ -601,7 +598,7 @@ data class Commitment( val htlcsOut = spec.htlcs.incomings().map { it.id }.joinToString(",") "built remote commit number=${remoteCommit.index + 1} toLocalMsat=${spec.toLocal.toLong()} toRemoteMsat=${spec.toRemote.toLong()} htlc_in=$htlcsIn htlc_out=$htlcsOut feeratePerKw=${spec.feerate} txId=${remoteCommitTx.tx.txid} fundingTxId=$fundingTxId" } - val commitSig = CommitSig(params.channelId, sig, htlcSigs.toList(), batchSize) + val commitSig = CommitSig(params.channelId, fundingTxId, sig, htlcSigs.toList()) val commitment1 = copy(nextRemoteCommit = RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint)) return Either.Right(Pair(commitment1, commitSig)) } @@ -693,6 +690,8 @@ data class Commitments( // We always use the last commitment that was created, to make sure we never go back in time. val latest = FullCommitment(channelParams, changes, active.first()) + fun lastLocalLocked(zeroConf: Boolean): Commitment? = active.find { zeroConf || it.localFundingStatus is LocalFundingStatus.ConfirmedFundingTx } + val all = buildList { addAll(active) addAll(inactive) @@ -868,7 +867,7 @@ data class Commitments( val (active1, sigs) = active.map { c -> val commitKeys = channelKeys.remoteCommitmentKeys(channelParams, remoteNextPerCommitmentPoint) val remoteNonce = remoteCommitNonces[c.fundingTxId] - when (val res = c.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size, remoteNonce, logger)) { + when (val res = c.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, remoteNonce, logger)) { is Either.Left -> return Either.Left(res.left) is Either.Right -> res.value } @@ -896,9 +895,12 @@ data class Commitments( return Either.Left(CommitSigCountMismatch(channelId, active.size, sigs.size)) } val commitKeys = channelKeys.localCommitmentKeys(channelParams, localCommitIndex + 1) - // Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments. - val active1 = active.zip(sigs).map { - when (val commitment1 = it.first.receiveCommit(channelParams, channelKeys, commitKeys, changes, it.second, logger)) { + val active1 = active.withIndex().map { (i, c) -> + // If the funding_txid isn't provided, we assume that signatures are sent in order (most recent first). + // This ensures that the case where we have a batch of a single element (no pending splice) works correctly. + // This also ensures that we get a signature failure when our set of funding txs doesn't match with our peer. + val commit = sigs.find { it.fundingTxId == c.fundingTxId } ?: sigs[i] + when (val commitment1 = c.receiveCommit(channelParams, channelKeys, commitKeys, changes, commit, logger)) { is Either.Left -> return Either.Left(commitment1.value) is Either.Right -> commitment1.value } @@ -1067,7 +1069,7 @@ data class Commitments( // This ensures that we only have to send splice_locked for the latest commitment instead of sending it for every commitment. // A side-effect is that previous commitments that are implicitly locked don't necessarily have their status correctly set. // That's why we look at locked commitments separately and then select the one with the oldest fundingTxIndex. - val lastLocalLocked = active.find { staticParams.useZeroConf || it.localFundingStatus is LocalFundingStatus.ConfirmedFundingTx } + val lastLocal = lastLocalLocked(staticParams.useZeroConf) val lastRemoteLocked = active.find { it.remoteFundingStatus == RemoteFundingStatus.Locked } return when { // We select the locked commitment with the smaller value for fundingTxIndex, but both have to be defined. @@ -1076,9 +1078,9 @@ data class Commitments( // - transactions with the same fundingTxIndex double-spend each other, so only one of them can confirm // - we don't allow creating a splice on top of an unconfirmed transaction that has RBF attempts (because it // would become invalid if another of the RBF attempts end up being confirmed) - lastLocalLocked != null && lastRemoteLocked != null -> listOf(lastLocalLocked, lastRemoteLocked).minByOrNull { it.fundingTxIndex } + lastLocal != null && lastRemoteLocked != null -> listOf(lastLocal, lastRemoteLocked).minByOrNull { it.fundingTxIndex } // Special case for the initial funding tx, we only require a local lock because channel_ready doesn't explicitly reference a funding tx. - lastLocalLocked != null && lastLocalLocked.fundingTxIndex == 0L -> lastLocalLocked + lastLocal != null && lastLocal.fundingTxIndex == 0L -> lastLocal else -> null } } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt index 4209d59a7..17b1dad34 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt @@ -731,7 +731,7 @@ data class InteractiveTxSession( fundingParams, localCommitIndex, SharedFundingInputBalances(previousLocalBalance, previousRemoteBalance, localHtlcs.map { it.add.amountMsat }.sum()), - fundingContributions.inputs.map { i -> Either.Left(i) } + fundingContributions.outputs.map { o -> Either.Right(o) }, + fundingContributions.inputs.map { i -> Either.Left(i) } + fundingContributions.outputs.map { o -> Either.Right(o) }, previousTxs, localHtlcs, localFundingNonce = fundingParams.sharedInput?.let { @@ -1106,11 +1106,8 @@ data class InteractiveTxSigningSession( // +-------+ +-------+ val fundingTxId: TxId = fundingTx.txId val localCommitIndex = localCommit.fold({ it.index }, { it.index }) - // This value tells our peer whether we need them to retransmit their commit_sig on reconnection or not. - val nextLocalCommitmentNumber = when (localCommit) { - is Either.Left -> localCommit.value.index - is Either.Right -> localCommit.value.index + 1 - } + // If we haven't received the remote commit_sig, we will request a retransmission on reconnection. + val retransmitRemoteCommitSig: Boolean = localCommit.isLeft fun localFundingKey(channelKeys: ChannelKeys): PrivateKey = fundingParams.fundingKey(channelKeys) @@ -1273,7 +1270,7 @@ data class InteractiveTxSigningSession( val htlcsOut = spec.htlcs.incomings().map { it.id }.joinToString(",") "built remote commit number=$remoteCommitmentIndex toLocalMsat=${spec.toLocal.toLong()} toRemoteMsat=${spec.toRemote.toLong()} htlc_in=$htlcsIn htlc_out=$htlcsOut feeratePerKw=${spec.feerate} txId=${firstCommitTx.remoteCommitTx.tx.txid} fundingTxId=${unsignedTx.txid}" } - val commitSig = CommitSig(channelParams.channelId, localSigOfRemoteCommitTx, localSigsOfRemoteHtlcTxs, batchSize = 1) + val commitSig = CommitSig(channelParams.channelId, unsignedTx.txid, localSigOfRemoteCommitTx, localSigsOfRemoteHtlcTxs) // We haven't received the remote commit_sig: we don't have local htlc txs yet. val unsignedLocalCommit = UnsignedLocalCommit(localCommitmentIndex, firstCommitTx.localSpec, firstCommitTx.localCommitTx.tx.txid) val remoteCommit = RemoteCommit(remoteCommitmentIndex, firstCommitTx.remoteSpec, firstCommitTx.remoteCommitTx.tx.txid, remotePerCommitmentPoint) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Channel.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Channel.kt index 5fb297996..4a2a55c90 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Channel.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Channel.kt @@ -317,12 +317,13 @@ sealed class PersistedChannelState : ChannelState() { } ChannelReestablish( channelId = channelId, - nextLocalCommitmentNumber = state.signingSession.nextLocalCommitmentNumber, + nextLocalCommitmentNumber = 1, nextRemoteRevocationNumber = 0, yourLastCommitmentSecret = PrivateKey(ByteVector32.Zeroes), myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint, nextCommitNonces = nextCommitNonce?.let { listOf(nextFundingTxId to it) } ?: listOf(), nextFundingTxId = nextFundingTxId, + retransmitCommitSig = state.signingSession.retransmitRemoteCommitSig, currentCommitNonce = currentCommitNonce ) } @@ -330,24 +331,13 @@ sealed class PersistedChannelState : ChannelState() { val channelKeys = channelKeys() val yourLastPerCommitmentSecret = state.commitments.remotePerCommitmentSecrets.lastIndex?.let { state.commitments.remotePerCommitmentSecrets.getHash(it) } ?: ByteVector32.Zeroes val myCurrentPerCommitmentPoint = channelKeys.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.nextLocalCommitmentNumber - else -> state.commitments.localCommitIndex + 1 - } - is Normal -> when (state.spliceStatus) { - is SpliceStatus.WaitingForSigs -> state.spliceStatus.session.nextLocalCommitmentNumber - 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) { + // If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures and commit_sig. + val (unsignedFundingTxId, retransmitCommitSig) = when (state) { is WaitForFundingConfirmed -> state.getUnsignedFundingTxId() is Normal -> state.getUnsignedFundingTxId() - else -> null + else -> Pair(null, false) } + val lastFundingLocked = state.commitments.lastLocalLocked(staticParams.useZeroConf) // We send our verification nonces for all active commitments. val nextCommitNonces = state.commitments.active.mapNotNull { c -> when (c.commitmentFormat) { @@ -378,12 +368,14 @@ sealed class PersistedChannelState : ChannelState() { } ChannelReestablish( channelId = channelId, - nextLocalCommitmentNumber = nextLocalCommitmentNumber, + nextLocalCommitmentNumber = state.commitments.localCommitIndex + 1, nextRemoteRevocationNumber = state.commitments.remoteCommitIndex, yourLastCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret), myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint, nextCommitNonces = nextCommitNonces + listOfNotNull(interactiveTxNextCommitNonce), nextFundingTxId = unsignedFundingTxId, + retransmitCommitSig = retransmitCommitSig, + currentFundingLocked = lastFundingLocked?.fundingTxId, currentCommitNonce = interactiveTxCurrentCommitNonce, ) } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt index 2094b31f6..de24dbd96 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt @@ -935,11 +935,14 @@ data class Normal( return Pair(nextState, actions) } - /** If we haven't completed the signing steps of an interactive-tx session, we will ask our peer to retransmit signatures for the corresponding transaction. */ - fun getUnsignedFundingTxId(): TxId? = when { - spliceStatus is SpliceStatus.WaitingForSigs -> spliceStatus.session.fundingTx.txId - commitments.latest.localFundingStatus is LocalFundingStatus.UnconfirmedFundingTx && commitments.latest.localFundingStatus.sharedTx is PartiallySignedSharedTransaction -> commitments.latest.localFundingStatus.txId - else -> null + /** + * If we haven't completed the signing steps of an interactive-tx session, we will ask our peer to retransmit signatures for the corresponding transaction. + * The second parameter should be set to true when commit_sig also needs to be retransmitted. + */ + fun getUnsignedFundingTxId(): Pair = when { + spliceStatus is SpliceStatus.WaitingForSigs -> Pair(spliceStatus.session.fundingTx.txId, spliceStatus.session.retransmitRemoteCommitSig) + commitments.latest.localFundingStatus is LocalFundingStatus.UnconfirmedFundingTx && commitments.latest.localFundingStatus.sharedTx is PartiallySignedSharedTransaction -> Pair(commitments.latest.localFundingStatus.txId, false) + else -> Pair(null, false) } /** Returns true if the [shortChannelId] matches one of our commitments or our alias. */ diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Syncing.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Syncing.kt index 454bbfd72..ff5107e72 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Syncing.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Syncing.kt @@ -25,7 +25,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: val (nextState, actions) = when (state) { is WaitForFundingSigned -> { val actions = buildList { - if (cmd.message.nextFundingTxId == state.signingSession.fundingTx.txId && cmd.message.nextLocalCommitmentNumber == 0L) { + if (cmd.message.nextFundingTxId == state.signingSession.fundingTx.txId && cmd.message.retransmitInteractiveTxCommitSig) { // They haven't received our commit_sig: we retransmit it, and will send our tx_signatures once we've received // their commit_sig or their tx_signatures (depending on who must send tx_signatures first). logger.info { "re-sending commit_sig for channel creation with fundingTxId=${state.signingSession.fundingTx.txId}" } @@ -44,7 +44,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: else -> { if (state.rbfStatus is RbfStatus.WaitingForSigs && state.rbfStatus.session.fundingTx.txId == cmd.message.nextFundingTxId) { val actions = buildList { - if (cmd.message.nextLocalCommitmentNumber == 0L) { + if (cmd.message.retransmitInteractiveTxCommitSig) { // They haven't received our commit_sig: we retransmit it. // We're waiting for signatures from them, and will send our tx_signatures once we receive them. logger.info { "re-sending commit_sig for rbf attempt with fundingTxId=${cmd.message.nextFundingTxId}" } @@ -59,7 +59,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: // We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures // and our commit_sig if they haven't received it already. val actions = buildList { - if (cmd.message.nextLocalCommitmentNumber == 0L) { + if (cmd.message.retransmitInteractiveTxCommitSig) { logger.info { "re-sending commit_sig for fundingTxId=${cmd.message.nextFundingTxId}" } when (val commitSig = state.commitments.latest.remoteCommit.sign( state.commitments.channelParams, @@ -69,7 +69,6 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: state.commitments.latest.remoteFundingPubkey, state.commitments.latest.commitInput(channelKeys), state.commitments.latest.commitmentFormat, - batchSize = 1, remoteNonce = cmd.message.currentCommitNonce, logger )) { @@ -92,11 +91,10 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: } is WaitForChannelReady -> { val actions = ArrayList() - // We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures - // and our commit_sig if they haven't received it already. + // If they haven't received our signatures for the channel funding transaction, we retransmit them. if (state.commitments.latest.fundingTxId == cmd.message.nextFundingTxId) { if (state.commitments.latest.localFundingStatus is LocalFundingStatus.UnconfirmedFundingTx) { - if (cmd.message.nextLocalCommitmentNumber == 0L) { + if (cmd.message.retransmitInteractiveTxCommitSig) { logger.info { "re-sending commit_sig for fundingTxId=${state.commitments.latest.fundingTxId}" } when (val commitSig = state.commitments.latest.remoteCommit.sign( state.commitments.channelParams, @@ -106,7 +104,6 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: state.commitments.latest.remoteFundingPubkey, state.commitments.latest.commitInput(channelKeys), state.commitments.latest.commitmentFormat, - batchSize = 1, remoteNonce = cmd.message.currentCommitNonce, logger )) { @@ -145,7 +142,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: // resume splice signing session if any val spliceStatus1 = if (state.spliceStatus is SpliceStatus.WaitingForSigs && state.spliceStatus.session.fundingTx.txId == cmd.message.nextFundingTxId) { - if (cmd.message.nextLocalCommitmentNumber == state.commitments.remoteCommitIndex) { + if (cmd.message.retransmitInteractiveTxCommitSig) { // They haven't received our commit_sig: we retransmit it. // We're waiting for signatures from them, and will send our tx_signatures once we receive them. logger.info { "re-sending commit_sig for splice attempt with fundingTxIndex=${state.spliceStatus.session.fundingParams.fundingTxIndex} fundingTxId=${state.spliceStatus.session.fundingTx.txId}" } @@ -160,7 +157,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: is LocalFundingStatus.UnconfirmedFundingTx -> { // We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures // and our commit_sig if they haven't received it already. - if (cmd.message.nextLocalCommitmentNumber == state.commitments.remoteCommitIndex) { + if (cmd.message.retransmitInteractiveTxCommitSig) { logger.info { "re-sending commit_sig for fundingTxIndex=${state.commitments.latest.fundingTxIndex} fundingTxId=${state.commitments.latest.fundingTxId}" } when (val commitSig = state.commitments.latest.remoteCommit.sign( state.commitments.channelParams, @@ -170,7 +167,6 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: state.commitments.latest.remoteFundingPubkey, state.commitments.latest.commitInput(channelKeys), state.commitments.latest.commitmentFormat, - batchSize = 1, remoteNonce = cmd.message.currentCommitNonce, logger )) { @@ -180,6 +176,8 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: } logger.info { "re-sending tx_signatures for fundingTxId=${cmd.message.nextFundingTxId}" } actions.add(ChannelAction.Message.Send(localFundingStatus.sharedTx.localSigs)) + // If we're using 0-conf, we also retransmit our splice_locked. + if (staticParams.useZeroConf) actions.add(ChannelAction.Message.Send(SpliceLocked(channelId, cmd.message.nextFundingTxId))) } is LocalFundingStatus.ConfirmedFundingTx -> { // The funding tx is confirmed, and they have not received our tx_signatures, but they must have received our commit_sig, otherwise they @@ -198,26 +196,27 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: state.spliceStatus } - // Re-send splice_locked (must come *after* potentially retransmitting tx_signatures). - // NB: there is a key difference between channel_ready and splice_locked: - // - channel_ready: a non-zero commitment index implies that both sides have seen the channel_ready - // - splice_locked: the commitment index can be updated as long as it is compatible with all splices, so - // we must keep sending our most recent splice_locked at each reconnection - state.commitments.active - .filter { it.fundingTxIndex > 0L } // only consider splice txs - .firstOrNull { staticParams.useZeroConf || it.localFundingStatus is LocalFundingStatus.ConfirmedFundingTx } - ?.let { - logger.debug { "re-sending splice_locked for fundingTxId=${it.fundingTxId}" } - val spliceLocked = SpliceLocked(channelId, it.fundingTxId) - actions.add(ChannelAction.Message.Send(spliceLocked)) + // Prune previous funding transactions and RBF attempts if we already sent splice_locked for the last funding + // transaction that is also locked by our counterparty; we either missed their splice_locked or it confirmed + // while disconnected. + val commitments1 = run { + val withRemoteLocked = when (val remoteFundingTxLocked = cmd.message.myCurrentFundingLocked) { + null -> state.commitments + else -> when (val commitments1 = state.commitments.run { updateRemoteFundingStatus(remoteFundingTxLocked) }) { + is Either.Left -> state.commitments + is Either.Right -> { + state.run { newlyLocked(state.commitments, commitments1.value.first) }.forEach { actions.add(ChannelAction.Storage.SetLocked(it.fundingTxId)) } + commitments1.value.first + } + } } + // Then we clean up unsigned updates. + discardUnsignedUpdates(withRemoteLocked) + } // we may need to retransmit updates and/or commit_sig and/or revocation actions.addAll(syncResult.retransmit.map { ChannelAction.Message.Send(it) }) - // then we clean up unsigned updates - val commitments1 = discardUnsignedUpdates(state.commitments) - if (commitments1.changes.localHasChanges()) { actions.add(ChannelAction.Message.SendToSelf(ChannelCommand.Commitment.Sign)) } @@ -435,14 +434,13 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: // We just sent a new commit_sig but they didn't receive it: we resend the same updates and sign them again, // and preserve the same ordering of messages. val signedUpdates = commitments.changes.localChanges.signed - val batchSize = commitments.active.size val commitSigs = CommitSigs.fromSigs(commitments.active.mapNotNull { c -> val commitInput = c.commitInput(channelKeys) val remoteNonce = remoteChannelReestablish.nextCommitNonces[c.fundingTxId] // Note that we ignore errors and simply skip failures to sign: we've already signed those updates before // the disconnection, so we don't expect any error here unless our peer sends an invalid nonce. In that // case, we simply won't send back our commit_sig until they fix their node. - c.nextRemoteCommit?.sign(commitments.channelParams, c.remoteCommitParams, channelKeys, c.fundingTxIndex, c.remoteFundingPubkey, commitInput, c.commitmentFormat, batchSize, remoteNonce, logger)?.right + c.nextRemoteCommit?.sign(commitments.channelParams, c.remoteCommitParams, channelKeys, c.fundingTxIndex, c.remoteFundingPubkey, commitInput, c.commitmentFormat, remoteNonce, logger)?.right }) val retransmit = when (retransmitRevocation) { null -> buildList { diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmed.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmed.kt index 574a156ae..3afb01be4 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmed.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmed.kt @@ -352,13 +352,16 @@ data class WaitForFundingConfirmed( return Pair(nextState, actions) } - /** If we haven't completed the signing steps of an interactive-tx session, we will ask our peer to retransmit signatures for the corresponding transaction. */ - fun getUnsignedFundingTxId(): TxId? { + /** + * If we haven't completed the signing steps of an interactive-tx session, we will ask our peer to retransmit signatures for the corresponding transaction. + * The second parameter should be set to true when commit_sig also needs to be retransmitted. + */ + fun getUnsignedFundingTxId(): Pair { return when (rbfStatus) { - is RbfStatus.WaitingForSigs -> rbfStatus.session.fundingTx.txId + is RbfStatus.WaitingForSigs -> Pair(rbfStatus.session.fundingTx.txId, rbfStatus.session.retransmitRemoteCommitSig) else -> when (latestFundingTx.sharedTx) { - is PartiallySignedSharedTransaction -> latestFundingTx.txId - is FullySignedSharedTransaction -> null + is PartiallySignedSharedTransaction -> Pair(latestFundingTx.txId, false) + is FullySignedSharedTransaction -> Pair(null, false) } } } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt index d98ac1d04..fc1c8a40c 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt @@ -115,7 +115,10 @@ data class PeerConnection(val id: Long, val output: Channel, v fun send(msg: LightningMessage) { when (msg) { - is CommitSigBatch -> msg.messages.map { sendInternal(it) } + is CommitSigBatch -> { + sendInternal(StartBatch(msg.channelId, msg.batchSize)) + msg.messages.map { sendInternal(it) } + } else -> sendInternal(msg) } } @@ -488,14 +491,14 @@ class Peer( try { while (isActive) { - val msg = when (val msg = receiveMessage()) { - is CommitSig -> { - val others = (1 until msg.batchSize).mapNotNull { receiveMessage() as CommitSig } - CommitSigs.fromSigs(listOf(msg) + others) + when (val msg = receiveMessage()) { + is StartBatch -> { + val sigs = (0 until msg.batchSize).mapNotNull { receiveMessage() as CommitSig } + input.send(MessageReceived(peerConnection.id, CommitSigs.fromSigs(sigs))) } - else -> msg + is LightningMessage -> input.send(MessageReceived(peerConnection.id, msg)) + else -> {} } - msg?.let { input.send(MessageReceived(peerConnection.id, it)) } } closeSocket(null) } catch (ex: Throwable) { diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/json/JsonSerializers.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/json/JsonSerializers.kt index 6ffb6d22a..a0f7eebd8 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/json/JsonSerializers.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/json/JsonSerializers.kt @@ -95,10 +95,11 @@ JsonSerializers.ShutdownTlvShutdownNonceSerializer::class, JsonSerializers.ClosingCompleteTlvSerializer::class, JsonSerializers.ClosingSigTlvSerializer::class, + JsonSerializers.MyCurrentFundingLockedTlvSerializer::class, JsonSerializers.ChannelReestablishTlvSerializer::class, JsonSerializers.ChannelReestablishTlvNextLocalNoncesSerializer::class, JsonSerializers.ChannelReadyTlvSerializer::class, - JsonSerializers.CommitSigTlvBatchSerializer::class, + JsonSerializers.CommitSigTlvFundingTxSerializer::class, JsonSerializers.CommitSigTlvPartialSignatureWithNonceSerializer::class, JsonSerializers.CommitSigTlvSerializer::class, JsonSerializers.UUIDSerializer::class, @@ -210,9 +211,10 @@ object JsonSerializers { subclass(UpdateFee::class, UpdateFeeSerializer) } polymorphic(Tlv::class) { + subclass(ChannelReestablishTlv.MyCurrentFundingLocked::class, MyCurrentFundingLockedTlvSerializer) subclass(ChannelReadyTlv.ShortChannelIdTlv::class, ChannelReadyTlvShortChannelIdTlvSerializer) subclass(ChannelReadyTlv.NextLocalNonce::class, ChannelReadyTlvNextLocalNonceSerializer) - subclass(CommitSigTlv.Batch::class, CommitSigTlvBatchSerializer) + subclass(CommitSigTlv.FundingTx::class, CommitSigTlvFundingTxSerializer) subclass(CommitSigTlv.PartialSignatureWithNonce::class, CommitSigTlvPartialSignatureWithNonceSerializer) subclass(UpdateAddHtlcTlv.PathKey::class, UpdateAddHtlcTlvPathKeySerializer) subclass(ShutdownTlv.ShutdownNonce::class, ShutdownTlvShutdownNonceSerializer) @@ -563,8 +565,8 @@ object JsonSerializers { @Serializer(forClass = ShutdownTlv.ShutdownNonce::class) object ShutdownTlvShutdownNonceSerializer - @Serializer(forClass = CommitSigTlv.Batch::class) - object CommitSigTlvBatchSerializer + @Serializer(forClass = CommitSigTlv.FundingTx::class) + object CommitSigTlvFundingTxSerializer @Serializer(forClass = CommitSigTlv.PartialSignatureWithNonce::class) object CommitSigTlvPartialSignatureWithNonceSerializer @@ -581,6 +583,9 @@ object JsonSerializers { @Serializer(forClass = ChannelReadyTlv::class) object ChannelReadyTlvSerializer + @Serializer(forClass = ChannelReestablishTlv.MyCurrentFundingLocked::class) + object MyCurrentFundingLockedTlvSerializer + @Serializer(forClass = ChannelReestablishTlv::class) object ChannelReestablishTlvSerializer diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/ChannelTlv.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/ChannelTlv.kt index 64e31ec49..819f03079 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/ChannelTlv.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/ChannelTlv.kt @@ -153,7 +153,40 @@ sealed class ChannelReadyTlv : Tlv { } } +sealed class StartBatchTlv : Tlv { + /** Type of [LightningMessage] that is included in the batch, when batching a single message type. */ + data class MessageType(val msg: Int) : StartBatchTlv() { + override val tag: Long get() = MessageType.tag + override fun write(out: Output) { + LightningCodecs.writeU16(msg, out) + } + + companion object : TlvValueReader { + const val tag: Long = 1 + override fun read(input: Input): MessageType = MessageType(msg = LightningCodecs.u16(input)) + } + } +} + sealed class CommitSigTlv : Tlv { + /** + * While a splice is ongoing and not locked, we have multiple valid commitments. + * We send one [CommitSig] message for each valid commitment. + * + * @param txId the funding transaction spent by this commitment. + */ + data class FundingTx(val txId: TxId) : CommitSigTlv() { + override val tag: Long get() = FundingTx.tag + override fun write(out: Output) { + LightningCodecs.writeTxHash(TxHash(txId), out) + } + + companion object : TlvValueReader { + const val tag: Long = 1 + override fun read(input: Input): FundingTx = FundingTx(txId = TxId(LightningCodecs.txHash(input))) + } + } + /** Partial signature along with the signer's nonce, which is usually randomly created at signing time (when using taproot channels). */ data class PartialSignatureWithNonce(val psig: ChannelSpendSignature.PartialSignatureWithNonce) : CommitSigTlv() { override val tag: Long get() = PartialSignatureWithNonce.tag @@ -175,16 +208,6 @@ sealed class CommitSigTlv : Tlv { } } } - - data class Batch(val size: Int) : CommitSigTlv() { - override val tag: Long get() = Batch.tag - override fun write(out: Output) = LightningCodecs.writeTU16(size, out) - - companion object : TlvValueReader { - const val tag: Long = 0x47010005 - override fun read(input: Input): Batch = Batch(size = LightningCodecs.tu16(input)) - } - } } sealed class RevokeAndAckTlv : Tlv { @@ -212,13 +235,46 @@ sealed class RevokeAndAckTlv : Tlv { } sealed class ChannelReestablishTlv : Tlv { - data class NextFunding(val txId: TxId) : ChannelReestablishTlv() { + /** + * When disconnected in the middle of an interactive-tx session, this field is used to request a retransmission of + * [TxSignatures] for the given [txId]. + * + * @param txId the txId of the partially signed funding transaction. + * @param retransmitCommitSig true if [CommitSig] must be retransmitted before [TxSignatures]. + */ + data class NextFunding(val txId: TxId, val retransmitCommitSig: Boolean) : ChannelReestablishTlv() { override val tag: Long get() = NextFunding.tag - override fun write(out: Output) = LightningCodecs.writeTxHash(TxHash(txId), out) + override fun write(out: Output) { + LightningCodecs.writeTxHash(TxHash(txId), out) + LightningCodecs.writeByte(if (retransmitCommitSig) 1 else 0, out) + } companion object : TlvValueReader { - const val tag: Long = 0 - override fun read(input: Input): NextFunding = NextFunding(TxId(LightningCodecs.txHash(input))) + const val tag: Long = 1 + override fun read(input: Input): NextFunding = NextFunding( + txId = TxId(LightningCodecs.txHash(input)), + retransmitCommitSig = (LightningCodecs.byte(input) % 2) == 1, + ) + } + } + + /** + * @param txId the txId of our latest outgoing [ChannelReady] or [SpliceLocked] for this channel. + * @param retransmitAnnSigs true if [AnnouncementSignatures] must be retransmitted. + */ + data class MyCurrentFundingLocked(val txId: TxId, val retransmitAnnSigs: Boolean) : ChannelReestablishTlv() { + override val tag: Long get() = MyCurrentFundingLocked.tag + override fun write(out: Output) { + LightningCodecs.writeTxHash(TxHash(txId), out) + LightningCodecs.writeByte(if (retransmitAnnSigs) 1 else 0, out) + } + + companion object : TlvValueReader { + const val tag: Long = 5 + override fun read(input: Input): MyCurrentFundingLocked = MyCurrentFundingLocked( + txId = TxId(LightningCodecs.txHash(input)), + retransmitAnnSigs = (LightningCodecs.byte(input) % 2) == 1, + ) } } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt index dd82ce9bd..60e51df17 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt @@ -18,7 +18,7 @@ sealed class TxAddInputTlv : Tlv { override fun write(out: Output) = LightningCodecs.writeTxHash(TxHash(txId), out) companion object : TlvValueReader { - const val tag: Long = 1105 + const val tag: Long = 0 override fun read(input: Input): SharedInputTxId = SharedInputTxId(TxId(LightningCodecs.txHash(input))) } } @@ -136,7 +136,7 @@ sealed class TxSignaturesTlv : Tlv { override fun write(out: Output) = LightningCodecs.writeBytes(sig.toByteArray(), out) companion object : TlvValueReader { - const val tag: Long = 601 + const val tag: Long = 0 override fun read(input: Input): PreviousFundingTxSig = PreviousFundingTxSig(LightningCodecs.bytes(input, 64).toByteVector64()) } } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/LightningMessages.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/LightningMessages.kt index 0f25f6cf5..e8f581613 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/LightningMessages.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/LightningMessages.kt @@ -68,6 +68,7 @@ interface LightningMessage { TxAbort.type -> TxAbort.read(stream) CommitSig.type -> CommitSig.read(stream) RevokeAndAck.type -> RevokeAndAck.read(stream) + StartBatch.type -> StartBatch.read(stream) UpdateAddHtlc.type -> UpdateAddHtlc.read(stream) UpdateFailHtlc.type -> UpdateFailHtlc.read(stream) UpdateFailMalformedHtlc.type -> UpdateFailMalformedHtlc.read(stream) @@ -1003,7 +1004,7 @@ data class SpliceInit( } companion object : LightningMessageReader { - const val type: Long = 37000 + const val type: Long = 80 @Suppress("UNCHECKED_CAST") private val readers = mapOf( @@ -1055,7 +1056,7 @@ data class SpliceAck( } companion object : LightningMessageReader { - const val type: Long = 37002 + const val type: Long = 81 @Suppress("UNCHECKED_CAST") private val readers = mapOf( @@ -1088,7 +1089,7 @@ data class SpliceLocked( } companion object : LightningMessageReader { - const val type: Long = 37004 + const val type: Long = 77 private val readers = emptyMap>() @@ -1100,6 +1101,35 @@ data class SpliceLocked( } } +/** This message is used to indicate that the next [batchSize] messages form a single logical message. */ +data class StartBatch(override val channelId: ByteVector32, val batchSize: Int, val tlvStream: TlvStream) : ChannelMessage, HasChannelId { + // We only support batches of [CommitSig] messages. + constructor(channelId: ByteVector32, batchSize: Int) : this(channelId, batchSize, TlvStream(StartBatchTlv.MessageType(132))) + + override val type: Long get() = StartBatch.type + + override fun write(out: Output) { + LightningCodecs.writeBytes(channelId, out) + LightningCodecs.writeU16(batchSize, out) + TlvStreamSerializer(false, readers).write(tlvStream, out) + } + + companion object : LightningMessageReader { + const val type: Long = 127 + + @Suppress("UNCHECKED_CAST") + val readers: Map> = mapOf( + StartBatchTlv.MessageType.tag to StartBatchTlv.MessageType as TlvValueReader + ) + + override fun read(input: Input): StartBatch = StartBatch( + channelId = ByteVector32(LightningCodecs.bytes(input, 32)), + batchSize = LightningCodecs.u16(input), + tlvStream = TlvStreamSerializer(false, readers).read(input) + ) + } +} + data class UpdateAddHtlc( override val channelId: ByteVector32, val id: Long, @@ -1263,7 +1293,7 @@ data class CommitSig( val tlvStream: TlvStream = TlvStream.empty() ) : CommitSigs() { - constructor(channelId: ByteVector32, signature: ChannelSpendSignature, htlcSignatures: List, batchSize: Int) : this( + constructor(channelId: ByteVector32, fundingTxId: TxId, signature: ChannelSpendSignature, htlcSignatures: List) : this( channelId, when (signature) { is ChannelSpendSignature.IndividualSignature -> signature @@ -1272,7 +1302,7 @@ data class CommitSig( htlcSignatures, TlvStream( setOfNotNull( - if (batchSize > 1) CommitSigTlv.Batch(batchSize) else null, + CommitSigTlv.FundingTx(fundingTxId), when (signature) { is ChannelSpendSignature.PartialSignatureWithNonce -> CommitSigTlv.PartialSignatureWithNonce(signature) is ChannelSpendSignature.IndividualSignature -> null @@ -1285,7 +1315,7 @@ data class CommitSig( val partialSignature: ChannelSpendSignature.PartialSignatureWithNonce? = tlvStream.get()?.psig val sigOrPartialSig: ChannelSpendSignature = partialSignature ?: signature - val batchSize: Int = tlvStream.get()?.size ?: 1 + val fundingTxId: TxId? = tlvStream.get()?.txId override fun write(out: Output) { LightningCodecs.writeBytes(channelId, out) @@ -1300,8 +1330,8 @@ data class CommitSig( @Suppress("UNCHECKED_CAST") val readers = mapOf( + CommitSigTlv.FundingTx.tag to CommitSigTlv.FundingTx.Companion as TlvValueReader, CommitSigTlv.PartialSignatureWithNonce.tag to CommitSigTlv.PartialSignatureWithNonce.Companion as TlvValueReader, - CommitSigTlv.Batch.tag to CommitSigTlv.Batch.Companion as TlvValueReader, ) override fun read(input: Input): CommitSig { @@ -1420,6 +1450,8 @@ data class ChannelReestablish( myCurrentPerCommitmentPoint: PublicKey, nextCommitNonces: List>, nextFundingTxId: TxId? = null, + retransmitCommitSig: Boolean = false, + currentFundingLocked: TxId? = null, currentCommitNonce: IndividualNonce? = null ) : this( channelId = channelId, @@ -1429,8 +1461,9 @@ data class ChannelReestablish( myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint, tlvStream = TlvStream( setOfNotNull( + nextFundingTxId?.let { ChannelReestablishTlv.NextFunding(it, retransmitCommitSig) }, + currentFundingLocked?.let { ChannelReestablishTlv.MyCurrentFundingLocked(it, retransmitAnnSigs = false) }, if (nextCommitNonces.isNotEmpty()) ChannelReestablishTlv.NextLocalNonces(nextCommitNonces) else null, - nextFundingTxId?.let { ChannelReestablishTlv.NextFunding(it) }, currentCommitNonce?.let { ChannelReestablishTlv.CurrentCommitNonce(it) }, ) ) @@ -1439,6 +1472,8 @@ data class ChannelReestablish( override val type: Long get() = ChannelReestablish.type val nextFundingTxId: TxId? = tlvStream.get()?.txId + val retransmitInteractiveTxCommitSig: Boolean = tlvStream.get()?.retransmitCommitSig ?: false + val myCurrentFundingLocked: TxId? = tlvStream.get()?.txId val nextCommitNonces: Map = tlvStream.get()?.nonces?.toMap() ?: mapOf() val currentCommitNonce: IndividualNonce? = tlvStream.get()?.nonce @@ -1457,6 +1492,7 @@ data class ChannelReestablish( @Suppress("UNCHECKED_CAST") val readers = mapOf( ChannelReestablishTlv.NextFunding.tag to ChannelReestablishTlv.NextFunding.Companion as TlvValueReader, + ChannelReestablishTlv.MyCurrentFundingLocked.tag to ChannelReestablishTlv.MyCurrentFundingLocked.Companion as TlvValueReader, ChannelReestablishTlv.NextLocalNonces.tag to ChannelReestablishTlv.NextLocalNonces.Companion as TlvValueReader, ChannelReestablishTlv.CurrentCommitNonce.tag to ChannelReestablishTlv.CurrentCommitNonce.Companion as TlvValueReader, ) @@ -1961,7 +1997,7 @@ data class WillFailMalformedHtlc(val id: ByteVector32, val paymentHash: ByteVect } /** - * This message is sent in response to an [OpenDualFundedChannel] or [SpliceInit] message containing an invalid [LiquidityAds.RequestFunds]. + * This message is sent in response to an [OpenDualFundedChannel] or [SpliceInit] message containing an invalid [LiquidityAds.RequestFunding]. * The receiver must consider the funding attempt failed when receiving this message. */ data class CancelOnTheFlyFunding(override val channelId: ByteVector32, val paymentHashes: List, val reason: ByteVector) : OnTheFlyFundingMessage, HasChannelId { diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/OfflineTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/OfflineTestsCommon.kt index 8f63c9bc8..6aa9ecc78 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/OfflineTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/OfflineTestsCommon.kt @@ -65,28 +65,31 @@ class OfflineTestsCommon : LightningTestSuite() { assertIs(bob2.state) val channelReestablishB = actions1.findOutgoingMessage() + val fundingTxId = bob.commitments.latest.fundingTxId val bobCommitments = bob.commitments val aliceCommitments = alice.commitments val bobCurrentPerCommitmentPoint = bob.channelKeys.commitmentPoint(bobCommitments.localCommitIndex) val aliceCurrentPerCommitmentPoint = alice.channelKeys.commitmentPoint(aliceCommitments.localCommitIndex) // alice didn't receive any update or sig - assertEquals( - ChannelReestablish(alice.channelId, 1, 0, PrivateKey(ByteVector32.Zeroes), aliceCurrentPerCommitmentPoint), - channelReestablishA.copy(tlvStream = TlvStream.empty()) - ) - assertEquals( - ChannelReestablish(bob.channelId, 1, 0, PrivateKey(ByteVector32.Zeroes), bobCurrentPerCommitmentPoint), - channelReestablishB.copy(tlvStream = TlvStream.empty()) - ) + assertEquals(1, channelReestablishA.nextLocalCommitmentNumber) + assertEquals(0, channelReestablishA.nextRemoteRevocationNumber) + assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishA.yourLastCommitmentSecret) + assertEquals(aliceCurrentPerCommitmentPoint, channelReestablishA.myCurrentPerCommitmentPoint) + assertEquals(fundingTxId, channelReestablishA.myCurrentFundingLocked) + assertEquals(1, channelReestablishB.nextLocalCommitmentNumber) + assertEquals(0, channelReestablishB.nextRemoteRevocationNumber) + assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishB.yourLastCommitmentSecret) + assertEquals(bobCurrentPerCommitmentPoint, channelReestablishB.myCurrentPerCommitmentPoint) + assertEquals(fundingTxId, channelReestablishB.myCurrentFundingLocked) val (alice3, actions2) = alice2.process(ChannelCommand.MessageReceived(channelReestablishB)) - assertEquals(alice, alice3) + assertIs>(alice3) assertEquals(1, actions2.size) actions2.hasOutgoingMessage() val (bob3, actions3) = bob2.process(ChannelCommand.MessageReceived(channelReestablishA)) - assertEquals(bob, bob3) + assertIs>(bob3) assertEquals(1, actions3.size) actions3.hasOutgoingMessage() } @@ -118,6 +121,7 @@ class OfflineTestsCommon : LightningTestSuite() { assertIs(bob2.state) val channelReestablishB = actionsBob2.findOutgoingMessage() + val fundingTxId = bob0.commitments.latest.fundingTxId val bobCommitments = bob0.commitments val aliceCommitments = alice0.commitments val bobCurrentPerCommitmentPoint = bob0.channelKeys.commitmentPoint(bobCommitments.localCommitIndex) @@ -128,11 +132,17 @@ class OfflineTestsCommon : LightningTestSuite() { assertEquals(0, channelReestablishA.nextRemoteRevocationNumber) assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishA.yourLastCommitmentSecret) assertEquals(aliceCurrentPerCommitmentPoint, channelReestablishA.myCurrentPerCommitmentPoint) + assertEquals(fundingTxId, channelReestablishA.myCurrentFundingLocked) + assertNull(channelReestablishA.nextFundingTxId) + assertFalse(channelReestablishA.retransmitInteractiveTxCommitSig) // bob did not receive alice's sig assertEquals(1, channelReestablishB.nextLocalCommitmentNumber) assertEquals(0, channelReestablishB.nextRemoteRevocationNumber) assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishB.yourLastCommitmentSecret) assertEquals(bobCurrentPerCommitmentPoint, channelReestablishB.myCurrentPerCommitmentPoint) + assertEquals(fundingTxId, channelReestablishB.myCurrentFundingLocked) + assertNull(channelReestablishB.nextFundingTxId) + assertFalse(channelReestablishB.retransmitInteractiveTxCommitSig) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishB)) // alice sends ChannelReady again @@ -197,6 +207,7 @@ class OfflineTestsCommon : LightningTestSuite() { assertIs(bob2.state) val channelReestablishB = actionsBob2.findOutgoingMessage() + val fundingTxId = bob0.commitments.latest.fundingTxId val bobCommitments = bob0.commitments val aliceCommitments = alice0.commitments val bobCurrentPerCommitmentPoint = bob0.channelKeys.commitmentPoint(bobCommitments.localCommitIndex) @@ -207,11 +218,15 @@ class OfflineTestsCommon : LightningTestSuite() { assertEquals(0, channelReestablishA.nextRemoteRevocationNumber) assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishA.yourLastCommitmentSecret) assertEquals(aliceCurrentPerCommitmentPoint, channelReestablishA.myCurrentPerCommitmentPoint) + assertEquals(fundingTxId, channelReestablishA.myCurrentFundingLocked) + assertNull(channelReestablishA.nextFundingTxId) // bob did receive alice's sig assertEquals(2, channelReestablishB.nextLocalCommitmentNumber) assertEquals(0, channelReestablishB.nextRemoteRevocationNumber) assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishB.yourLastCommitmentSecret) assertEquals(bobCurrentPerCommitmentPoint, channelReestablishB.myCurrentPerCommitmentPoint) + assertEquals(fundingTxId, channelReestablishB.myCurrentFundingLocked) + assertNull(channelReestablishB.nextFundingTxId) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishB)) // alice does not re-send messages bob already received diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt index 90818800e..b0009d767 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt @@ -830,6 +830,7 @@ class SpliceTestsCommon : LightningTestSuite() { val (alice4, actionsAlice4) = alice3.process(ChannelCommand.Commitment.Sign) val commitSigsAlice = actionsAlice4.findOutgoingMessage() assertEquals(commitSigsAlice.batchSize, 3) + assertEquals(setOf(alice.commitments.latest.fundingTxId, alice1.commitments.latest.fundingTxId, spliceTx.txid), commitSigsAlice.messages.mapNotNull { it.fundingTxId }.toSet()) val (alice5, _) = alice4.process(ChannelCommand.MessageReceived(spliceLocked)) assertEquals(alice5.commitments.active.size, 1) assertEquals(alice5.commitments.inactive.size, 2) @@ -841,7 +842,7 @@ class SpliceTestsCommon : LightningTestSuite() { val (bob6, actionsBob6) = bob5.process(ChannelCommand.Commitment.Sign) assertEquals(actionsBob6.size, 3) val commitSigBob = actionsBob6.findOutgoingMessage() - assertEquals(commitSigBob.batchSize, 1) + assertEquals(spliceTx.txid, commitSigBob.fundingTxId) actionsBob6.has() actionsBob6.has() val (alice6, actionsAlice6) = alice5.process(ChannelCommand.MessageReceived(revokeAndAckBob)) @@ -882,7 +883,8 @@ class SpliceTestsCommon : LightningTestSuite() { assertNotNull(channelReestablishAlice.currentCommitNonce) assertContains(channelReestablishAlice.nextCommitNonces, alice.commitments.latest.fundingTxId) assertContains(channelReestablishAlice.nextCommitNonces, spliceStatus.session.fundingTx.txId) - assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, aliceCommitIndex) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, aliceCommitIndex + 1) + assertTrue(channelReestablishAlice.retransmitInteractiveTxCommitSig) val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertIs>(bob3) assertEquals(actionsBob3.size, 4) @@ -893,7 +895,8 @@ class SpliceTestsCommon : LightningTestSuite() { val commitSigBob = actionsBob3.findOutgoingMessage() assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob3.filterIsInstance().map { it.add }.toSet()) assertEquals(channelReestablishBob.nextFundingTxId, spliceStatus.session.fundingTx.txId) - assertEquals(channelReestablishBob.nextLocalCommitmentNumber, bobCommitIndex) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, bobCommitIndex + 1) + assertTrue(channelReestablishBob.retransmitInteractiveTxCommitSig) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishBob)) assertIs>(alice3) assertEquals(actionsAlice3.size, 3) @@ -919,6 +922,7 @@ class SpliceTestsCommon : LightningTestSuite() { val (alice4, bob3, channelReestablishAlice) = disconnect(alice3, bob2) assertEquals(channelReestablishAlice.nextFundingTxId, spliceTxId) + assertFalse(channelReestablishAlice.retransmitInteractiveTxCommitSig) assertNull(channelReestablishAlice.currentCommitNonce) assertContains(channelReestablishAlice.nextCommitNonces, alice.commitments.latest.fundingTxId) assertContains(channelReestablishAlice.nextCommitNonces, spliceTxId) @@ -932,7 +936,8 @@ class SpliceTestsCommon : LightningTestSuite() { assertNull(actionsBob4.findOutgoingMessageOpt()) assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob4.filterIsInstance().map { it.add }.toSet()) assertEquals(channelReestablishBob.nextFundingTxId, spliceTxId) - assertEquals(channelReestablishBob.nextLocalCommitmentNumber, bobCommitIndex) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, bobCommitIndex + 1) + assertTrue(channelReestablishBob.retransmitInteractiveTxCommitSig) val (alice5, actionsAlice5) = alice4.process(ChannelCommand.MessageReceived(channelReestablishBob)) assertEquals(actionsAlice5.size, 3) val commitSigAlice = actionsAlice5.findOutgoingMessage() @@ -978,10 +983,11 @@ class SpliceTestsCommon : LightningTestSuite() { val (alice2, bob3, channelReestablishAlice) = disconnect(alice1, bob2) assertEquals(channelReestablishAlice.nextFundingTxId, spliceTxId) + assertTrue(channelReestablishAlice.retransmitInteractiveTxCommitSig) assertNotNull(channelReestablishAlice.currentCommitNonce) assertContains(channelReestablishAlice.nextCommitNonces, alice.commitments.latest.fundingTxId) assertContains(channelReestablishAlice.nextCommitNonces, spliceTxId) - assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, aliceCommitIndex) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, aliceCommitIndex + 1) val (bob4, actionsBob4) = bob3.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertEquals(actionsBob4.size, 5) val channelReestablishBob = actionsBob4.findOutgoingMessage() @@ -992,6 +998,7 @@ class SpliceTestsCommon : LightningTestSuite() { assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob4.filterIsInstance().map { it.add }.toSet()) val txSigsBob = actionsBob4.findOutgoingMessage() assertEquals(channelReestablishBob.nextFundingTxId, spliceTxId) + assertFalse(channelReestablishBob.retransmitInteractiveTxCommitSig) assertEquals(channelReestablishBob.nextLocalCommitmentNumber, bobCommitIndex + 1) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishBob)) assertEquals(actionsAlice3.size, 2) @@ -1035,24 +1042,26 @@ class SpliceTestsCommon : LightningTestSuite() { val (alice2, bob3, channelReestablishAlice) = disconnect(alice1, bob2) assertEquals(channelReestablishAlice.nextFundingTxId, spliceTxId) + assertTrue(channelReestablishAlice.retransmitInteractiveTxCommitSig) assertNotNull(channelReestablishAlice.currentCommitNonce) assertContains(channelReestablishAlice.nextCommitNonces, alice.commitments.latest.fundingTxId) assertContains(channelReestablishAlice.nextCommitNonces, spliceTxId) val (bob4, actionsBob4) = bob3.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertEquals(actionsBob4.size, 6) val channelReestablishBob = actionsBob4.findOutgoingMessage() + assertEquals(channelReestablishBob.nextFundingTxId, spliceTxId) + assertFalse(channelReestablishBob.retransmitInteractiveTxCommitSig) assertNull(channelReestablishBob.currentCommitNonce) assertContains(channelReestablishBob.nextCommitNonces, bob.commitments.latest.fundingTxId) assertContains(channelReestablishBob.nextCommitNonces, spliceTxId) val commitSigBob = actionsBob4.findOutgoingMessage() val txSigsBob = actionsBob4.findOutgoingMessage() - // splice_locked must always be sent *after* tx_signatures - assertIs(actionsBob4.filterIsInstance().last().message) val spliceLockedBob = actionsBob4.findOutgoingMessage() assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob4.filterIsInstance().map { it.add }.toSet()) - assertEquals(channelReestablishBob.nextFundingTxId, spliceTxId) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishBob)) + assertIs>(alice3) assertEquals(actionsAlice3.size, 2) + assertEquals(alice3.state.commitments.active.size, 1) assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice3.filterIsInstance().map { it.add }.toSet()) assertNull(actionsAlice3.findOutgoingMessageOpt()) @@ -1073,9 +1082,9 @@ class SpliceTestsCommon : LightningTestSuite() { val (alice6, actionsAlice6) = alice5.process(ChannelCommand.MessageReceived(spliceLockedBob)) assertIs>(alice6) assertEquals(alice6.state.commitments.active.size, 1) - assertEquals(actionsAlice6.size, 2) - actionsAlice6.find().also { assertEquals(it.txId, spliceTxId) } + assertEquals(2, actionsAlice6.size) actionsAlice6.has() + actionsAlice6.find().also { assertEquals(it.txId, spliceTxId) } val (bob5, actionsBob5) = bob4.process(ChannelCommand.MessageReceived(txSigsAlice)) assertIs>(bob5) @@ -1137,9 +1146,9 @@ class SpliceTestsCommon : LightningTestSuite() { val (alice6, actionsAlice6) = alice5.process(ChannelCommand.MessageReceived(channelReestablishBob)) assertIs>(alice6) assertEquals(alice6.state.spliceStatus, SpliceStatus.None) - assertEquals(4, actionsAlice6.size) + assertEquals(3, actionsAlice6.size) val txSigsAlice = actionsAlice6.hasOutgoingMessage() - actionsAlice6.hasOutgoingMessage() + assertNull(actionsAlice6.findOutgoingMessageOpt()) assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice6.filterIsInstance().map { it.add }.toSet()) // Bob receives tx_signatures, which completes the splice. @@ -1222,6 +1231,7 @@ class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- new changes before splice_locked -- partially locked`() { val (alice, bob) = reachNormalWithConfirmedFundingTx() + val fundingTxId = alice.commitments.latest.fundingTxId val (alice1, bob1) = spliceOut(alice, bob, 70_000.sat) val spliceTx = alice1.commitments.latest.localFundingStatus.signedTx!! @@ -1237,7 +1247,7 @@ class SpliceTestsCommon : LightningTestSuite() { val (alice3, actionsAlice3) = nodes3.first.process(ChannelCommand.Commitment.Sign) actionsAlice3.hasOutgoingMessage().also { batch -> assertEquals(2, batch.batchSize) - batch.messages.forEach { sig -> assertEquals(2, sig.batchSize) } + assertEquals(setOf(fundingTxId, spliceTx.txid), batch.messages.mapNotNull { it.fundingTxId }.toSet()) } // At the same time, the splice confirms on Bob's side, who now expects a single commit_sig message. @@ -1256,7 +1266,7 @@ class SpliceTestsCommon : LightningTestSuite() { actionsAlice6.hasOutgoingMessage().also { assertEquals(htlc, it) } assertEquals(1, actionsAlice6.findOutgoingMessages().size) val commitSigAlice = actionsAlice6.hasOutgoingMessage() - assertEquals(1, commitSigAlice.batchSize) + assertEquals(spliceTx.txid, commitSigAlice.fundingTxId) val (bob6, _) = bob5.process(ChannelCommand.MessageReceived(htlc)) val (bob7, actionsBob7) = bob6.process(ChannelCommand.MessageReceived(commitSigAlice)) assertIs>(bob7) @@ -1280,29 +1290,27 @@ class SpliceTestsCommon : LightningTestSuite() { // Alice disconnects before receiving Bob's splice_locked. val (alice3, bob4, channelReestablishAlice) = disconnect(alice2, bob3) + assertEquals(spliceTx.txid, channelReestablishAlice.myCurrentFundingLocked) + assertNull(channelReestablishAlice.nextFundingTxId) val (bob5, actionsBob5) = bob4.process(ChannelCommand.MessageReceived(channelReestablishAlice)) - assertEquals(actionsBob5.size, 4) + assertIs>(bob5) + assertEquals(actionsBob5.size, 3) + assertEquals(bob5.state.commitments.active.size, 1) val channelReestablishBob = actionsBob5.findOutgoingMessage() - val spliceLockedBob = actionsBob5.findOutgoingMessage() + assertEquals(spliceTx.txid, channelReestablishBob.myCurrentFundingLocked) + assertNull(channelReestablishBob.nextFundingTxId) + assertNull(actionsBob5.findOutgoingMessageOpt()) assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob5.filterIsInstance().map { it.add }.toSet()) + // Alice can lock the latest commitment as soon as she receives channel_reestablish. val (alice4, actionsAlice4) = alice3.process(ChannelCommand.MessageReceived(channelReestablishBob)) + assertIs>(alice4) + assertEquals(alice4.state.commitments.active.size, 1) assertEquals(actionsAlice4.size, 3) - val spliceLockedAlice2 = actionsAlice4.hasOutgoingMessage() + assertNull(actionsAlice4.findOutgoingMessageOpt()) + actionsAlice4.has() assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice4.filterIsInstance().map { it.add }.toSet()) - val (alice5, actionsAlice5) = alice4.process(ChannelCommand.MessageReceived(spliceLockedBob)) - assertIs>(alice5) - assertEquals(alice5.state.commitments.active.size, 1) - assertEquals(2, actionsAlice5.size) - actionsAlice5.has() - actionsAlice5.has() - - val (bob6, actionsBob6) = bob5.process(ChannelCommand.MessageReceived(spliceLockedAlice2)) - assertIs>(bob6) - assertEquals(bob6.state.commitments.active.size, 1) - assertEquals(actionsBob6.size, 1) - actionsBob6.has() - resolveHtlcs(alice5, bob6, htlcs, commitmentsCount = 1) + resolveHtlcs(alice4, bob5, htlcs, commitmentsCount = 1) } @Test @@ -1314,42 +1322,42 @@ class SpliceTestsCommon : LightningTestSuite() { // Alice and Bob have not received any remote splice_locked yet. assertEquals(alice2.commitments.active.size, 3) - alice2.commitments.active.forEach { assertEquals(it.remoteFundingStatus, RemoteFundingStatus.NotLocked) } + alice2.commitments.active.filter { it.fundingTxIndex > 0 }.forEach { assertEquals(it.remoteFundingStatus, RemoteFundingStatus.NotLocked) } assertEquals(bob2.commitments.active.size, 3) - bob2.commitments.active.forEach { assertEquals(it.remoteFundingStatus, RemoteFundingStatus.NotLocked) } + bob2.commitments.active.filter { it.fundingTxIndex > 0 }.forEach { assertEquals(it.remoteFundingStatus, RemoteFundingStatus.NotLocked) } - // On reconnection, Alice and Bob only send splice_locked for the latest commitment. + // On reconnection, Alice and Bob advertise that they want to lock the latest commitment. val (alice3, bob3, channelReestablishAlice) = disconnect(alice2, bob2) + assertEquals(alice2.commitments.active.first().fundingTxId, channelReestablishAlice.myCurrentFundingLocked) + assertNull(channelReestablishAlice.nextFundingTxId) + assertFalse(channelReestablishAlice.retransmitInteractiveTxCommitSig) val (bob4, actionsBob4) = bob3.process(ChannelCommand.MessageReceived(channelReestablishAlice)) - assertEquals(actionsBob4.size, 4) + assertIs>(bob4) + assertEquals(actionsBob4.size, 5) + // Bob immediately applies Alice's my_current_funding_locked, even though he hasn't received splice_locked yet. + assertEquals(bob4.commitments.active.size, 1) val channelReestablishBob = actionsBob4.findOutgoingMessage() - val spliceLockedBob = actionsBob4.findOutgoingMessage() + assertEquals(bob2.commitments.active.first().fundingTxId, channelReestablishBob.myCurrentFundingLocked) + assertNull(channelReestablishBob.nextFundingTxId) + assertFalse(channelReestablishBob.retransmitInteractiveTxCommitSig) + assertNull(actionsBob4.findOutgoingMessageOpt()) assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob4.filterIsInstance().map { it.add }.toSet()) - assertEquals(spliceLockedBob.fundingTxId, bob2.commitments.latest.fundingTxId) + assertContains(actionsBob4, ChannelAction.Storage.SetLocked(bob1.commitments.latest.fundingTxId)) + assertContains(actionsBob4, ChannelAction.Storage.SetLocked(bob2.commitments.latest.fundingTxId)) val (alice4, actionsAlice4) = alice3.process(ChannelCommand.MessageReceived(channelReestablishBob)) - assertEquals(actionsAlice4.size, 3) - val spliceLockedAlice = actionsAlice4.hasOutgoingMessage() + assertIs>(alice4) + assertEquals(actionsAlice4.size, 4) + // Alice immediately applies Bob's my_current_funding_locked, even though she hasn't received splice_locked yet. + assertEquals(alice4.commitments.active.size, 1) + assertEquals(alice4.commitments.latest.fundingTxId, channelReestablishAlice.myCurrentFundingLocked) + assertNull(actionsAlice4.findOutgoingMessageOpt()) assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice4.filterIsInstance().map { it.add }.toSet()) - assertEquals(spliceLockedAlice.fundingTxId, spliceLockedBob.fundingTxId) - val (alice5, actionsAlice5) = alice4.process(ChannelCommand.MessageReceived(spliceLockedBob)) - assertEquals(actionsAlice5.size, 3) - assertEquals(alice5.commitments.active.size, 1) - assertEquals(alice5.commitments.latest.fundingTxId, spliceLockedBob.fundingTxId) - actionsAlice5.has() - assertContains(actionsAlice5, ChannelAction.Storage.SetLocked(alice1.commitments.latest.fundingTxId)) - assertContains(actionsAlice5, ChannelAction.Storage.SetLocked(alice2.commitments.latest.fundingTxId)) + assertContains(actionsAlice4, ChannelAction.Storage.SetLocked(alice1.commitments.latest.fundingTxId)) + assertContains(actionsAlice4, ChannelAction.Storage.SetLocked(alice2.commitments.latest.fundingTxId)) - val (bob5, actionsBob5) = bob4.process(ChannelCommand.MessageReceived(spliceLockedAlice)) - assertEquals(actionsBob5.size, 3) - assertEquals(bob5.commitments.active.size, 1) - assertEquals(bob5.commitments.latest.fundingTxId, spliceLockedAlice.fundingTxId) - actionsBob5.has() - assertContains(actionsBob5, ChannelAction.Storage.SetLocked(bob1.commitments.latest.fundingTxId)) - assertContains(actionsBob5, ChannelAction.Storage.SetLocked(bob2.commitments.latest.fundingTxId)) - assertIs>(alice5) - assertIs>(bob5) - resolveHtlcs(alice5, bob5, htlcs, commitmentsCount = 1) + // Alice and Bob can now resolve HTLCs. + resolveHtlcs(alice4, bob4, htlcs, commitmentsCount = 1) } @Test @@ -1364,9 +1372,9 @@ class SpliceTestsCommon : LightningTestSuite() { // Alice and Bob have not received any remote splice_locked yet. assertEquals(alice2.commitments.active.size, 3) - alice2.commitments.active.forEach { assertEquals(it.remoteFundingStatus, RemoteFundingStatus.NotLocked) } + alice2.commitments.active.filter { it.fundingTxIndex > 0 }.forEach { assertEquals(it.remoteFundingStatus, RemoteFundingStatus.NotLocked) } assertEquals(bob2.commitments.active.size, 3) - bob2.commitments.active.forEach { assertEquals(it.remoteFundingStatus, RemoteFundingStatus.NotLocked) } + bob2.commitments.active.filter { it.fundingTxIndex > 0 }.forEach { assertEquals(it.remoteFundingStatus, RemoteFundingStatus.NotLocked) } // Alice locks the last commitment. val (alice3, actionsAlice3) = alice2.process(ChannelCommand.WatchReceived(WatchConfirmedTriggered(alice.channelId, WatchConfirmed.ChannelFundingDepthOk, 100, 0, spliceTx2))) @@ -1387,32 +1395,29 @@ class SpliceTestsCommon : LightningTestSuite() { // Alice and Bob disconnect before receiving each other's splice_locked. // On reconnection, the latest commitment is still unlocked by Bob so they have two active commitments. val (alice4, bob4, channelReestablishAlice) = disconnect(alice3, bob3) + assertEquals(spliceTx2.txid, channelReestablishAlice.myCurrentFundingLocked) + assertNull(channelReestablishAlice.nextFundingTxId) val (bob5, actionsBob5) = bob4.process(ChannelCommand.MessageReceived(channelReestablishAlice)) + assertIs>(bob5) assertEquals(actionsBob5.size, 4) + assertEquals(bob5.commitments.active.map { it.fundingTxId }, listOf(spliceTx2.txid, spliceTx1.txid)) val channelReestablishBob = actionsBob5.findOutgoingMessage() - val spliceLockedBob = actionsBob5.findOutgoingMessage() + assertEquals(spliceTx1.txid, channelReestablishBob.myCurrentFundingLocked) + assertNull(channelReestablishBob.nextFundingTxId) + assertNull(actionsBob5.findOutgoingMessageOpt()) assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob5.filterIsInstance().map { it.add }.toSet()) - assertEquals(spliceLockedBob.fundingTxId, spliceTx1.txid) + assertContains(actionsBob5, ChannelAction.Storage.SetLocked(spliceTx1.txid)) val (alice5, actionsAlice5) = alice4.process(ChannelCommand.MessageReceived(channelReestablishBob)) + assertIs>(alice5) assertEquals(actionsAlice5.size, 3) - val spliceLockedAlice = actionsAlice5.hasOutgoingMessage() + assertEquals(alice5.commitments.active.map { it.fundingTxId }, listOf(spliceTx2.txid, spliceTx1.txid)) + assertNull(actionsAlice5.findOutgoingMessageOpt()) assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice5.filterIsInstance().map { it.add }.toSet()) - assertEquals(spliceLockedAlice.fundingTxId, spliceTx2.txid) - val (alice6, actionsAlice6) = alice5.process(ChannelCommand.MessageReceived(spliceLockedBob)) - assertEquals(actionsAlice6.size, 2) - assertEquals(alice6.commitments.active.map { it.fundingTxId }, listOf(spliceTx2.txid, spliceTx1.txid)) - actionsAlice6.has() - actionsAlice6.contains(ChannelAction.Storage.SetLocked(spliceTx1.txid)) + actionsAlice5.contains(ChannelAction.Storage.SetLocked(spliceTx1.txid)) - val (bob6, actionsBob6) = bob5.process(ChannelCommand.MessageReceived(spliceLockedAlice)) - assertEquals(actionsBob6.size, 2) - assertEquals(bob6.commitments.active.map { it.fundingTxId }, listOf(spliceTx2.txid, spliceTx1.txid)) - actionsBob6.has() - actionsBob6.contains(ChannelAction.Storage.SetLocked(spliceTx1.txid)) - assertIs>(alice6) - assertIs>(bob6) - resolveHtlcs(alice6, bob6, htlcs, commitmentsCount = 2) + // Alice and Bob can now resolve HTLCs. + resolveHtlcs(alice5, bob5, htlcs, commitmentsCount = 2) } @Test diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SyncingTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SyncingTestsCommon.kt index c61872fe9..05b188c94 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SyncingTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SyncingTestsCommon.kt @@ -95,9 +95,11 @@ class SyncingTestsCommon : LightningTestSuite() { val (alice1, bob1, channelReestablishAlice, channelReestablishBob) = disconnectWithBackup(alice, bob) assertNotNull(channelReestablishBob) assertEquals(channelReestablishAlice.nextFundingTxId, fundingTxId) - assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 0) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 1) + assertTrue(channelReestablishAlice.retransmitInteractiveTxCommitSig) assertEquals(channelReestablishBob.nextFundingTxId, fundingTxId) - assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 0) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 1) + assertTrue(channelReestablishBob.retransmitInteractiveTxCommitSig) val (bob2, actionsBob2) = bob1.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertEquals(actionsBob2.size, 1) @@ -132,8 +134,10 @@ class SyncingTestsCommon : LightningTestSuite() { assertNotNull(channelReestablishBob) assertEquals(channelReestablishAlice.nextFundingTxId, fundingTxId) assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 1) + assertFalse(channelReestablishAlice.retransmitInteractiveTxCommitSig) assertEquals(channelReestablishBob.nextFundingTxId, fundingTxId) - assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 0) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 1) + assertTrue(channelReestablishBob.retransmitInteractiveTxCommitSig) val (bob2, actionsBob2) = bob1.process(ChannelCommand.MessageReceived(channelReestablishAlice)) // Bob is waiting for Alice's commit_sig before sending his tx_signatures. @@ -169,12 +173,14 @@ class SyncingTestsCommon : LightningTestSuite() { val (alice1, bob2, channelReestablishAlice, channelReestablishBob0) = disconnectWithBackup(alice, bob1) assertNull(channelReestablishBob0) assertEquals(channelReestablishAlice.nextFundingTxId, fundingTxId) - assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 0) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 1) + assertTrue(channelReestablishAlice.retransmitInteractiveTxCommitSig) val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(channelReestablishAlice)) val channelReestablishBob = actionsBob3.hasOutgoingMessage() assertEquals(channelReestablishBob.nextFundingTxId, fundingTxId) assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 1) + assertFalse(channelReestablishBob.retransmitInteractiveTxCommitSig) val commitSigBob = actionsBob3.hasOutgoingMessage() val txSigsBob = actionsBob3.hasOutgoingMessage() @@ -276,13 +282,15 @@ class SyncingTestsCommon : LightningTestSuite() { val (alice1, bob1, channelReestablishAlice, channelReestablishBob0) = disconnectWithBackup(alice, bob) assertNull(channelReestablishBob0) assertEquals(channelReestablishAlice.nextFundingTxId, rbfFundingTxId) - assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 0) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 1) + assertTrue(channelReestablishAlice.retransmitInteractiveTxCommitSig) val (bob2, actionsBob2) = bob1.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertEquals(actionsBob2.size, 2) val channelReestablishBob = actionsBob2.hasOutgoingMessage() assertEquals(channelReestablishBob.nextFundingTxId, rbfFundingTxId) - assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 0) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 1) + assertTrue(channelReestablishBob.retransmitInteractiveTxCommitSig) val commitSigBob = actionsBob2.hasOutgoingMessage() val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(channelReestablishBob)) diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/io/peer/PeerTest.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/io/peer/PeerTest.kt index c67cb978b..057c14a95 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/io/peer/PeerTest.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/io/peer/PeerTest.kt @@ -361,10 +361,11 @@ class PeerTest : LightningTestSuite() { // Simulate a reconnection with Alice. peer.send(MessageReceived(connectionId = 0, Init(features = alice0.staticParams.nodeParams.features))) peer.send(MessageReceived(connectionId = 0, PeerStorageRetrieval(backup))) + // We remove TLVs which may affect channel_ready / splice_locked behavior. val aliceReestablish = alice1.state.run { alice1.ctx.createChannelReestablish() } - peer.send(MessageReceived(connectionId = 0, aliceReestablish)) + peer.send(MessageReceived(connectionId = 0, aliceReestablish.copy(tlvStream = TlvStream(aliceReestablish.tlvStream.records.filterNot { it is ChannelReestablishTlv.MyCurrentFundingLocked }.toSet())))) - // Wait until the channels are Syncing + // Wait until the channels are Syncing. val restoredChannel = peer.channelsFlow .first { it.size == 1 } .values @@ -393,8 +394,9 @@ class PeerTest : LightningTestSuite() { // Simulate a reconnection with Alice. peer.send(MessageReceived(connectionId = 0, Init(features = alice0.staticParams.nodeParams.features))) peer.send(MessageReceived(connectionId = 0, PeerStorageRetrieval(backup))) + // We remove TLVs which may affect channel_ready / splice_locked behavior. val aliceReestablish = alice1.state.run { alice1.ctx.createChannelReestablish() } - peer.send(MessageReceived(connectionId = 0, aliceReestablish)) + peer.send(MessageReceived(connectionId = 0, aliceReestablish.copy(tlvStream = TlvStream(aliceReestablish.tlvStream.records.filterNot { it is ChannelReestablishTlv.MyCurrentFundingLocked }.toSet())))) // Wait until the channels are Syncing val restoredChannel = peer.channelsFlow diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt index 7df3e7ca0..c803823ef 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt @@ -1898,7 +1898,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val defaultPreimage = randomBytes32() val defaultPaymentHash = Crypto.sha256(defaultPreimage).toByteVector32() val defaultAmount = 150_000_000.msat - val feeCreditFeatures = Features(Feature.ExperimentalSplice to FeatureSupport.Optional, Feature.OnTheFlyFunding to FeatureSupport.Optional, Feature.FundingFeeCredit to FeatureSupport.Optional) + val feeCreditFeatures = Features(Feature.OnTheFlyFunding to FeatureSupport.Optional, Feature.FundingFeeCredit to FeatureSupport.Optional) fun LightningIncomingPayment.Part.resetTimestamp() = when (this) { is LightningIncomingPayment.Part.Htlc -> copy(receivedAt = 0) diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt index bef1d779f..ca082512d 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt @@ -58,6 +58,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { Feature.RouteBlinding to FeatureSupport.Optional, Feature.ShutdownAnySegwit to FeatureSupport.Mandatory, Feature.SimpleClose to FeatureSupport.Mandatory, + Feature.Splicing to FeatureSupport.Mandatory, ) // The following invoice requires payment_metadata. val invoice1 = run { diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt index 8843e78a6..c394eef7a 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt @@ -82,10 +82,10 @@ object TestConstants { Feature.ChannelType to FeatureSupport.Mandatory, Feature.PaymentMetadata to FeatureSupport.Optional, Feature.SimpleClose to FeatureSupport.Mandatory, + Feature.Splicing to FeatureSupport.Mandatory, Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional, Feature.WakeUpNotificationProvider to FeatureSupport.Optional, Feature.ProvideStorage to FeatureSupport.Optional, - Feature.ExperimentalSplice to FeatureSupport.Optional, Feature.OnTheFlyFunding to FeatureSupport.Optional, ), dustLimit = 1_100.sat, diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt index 8c556524c..88f926122 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt @@ -450,6 +450,7 @@ class LightningCodecsTestsCommon : LightningTestSuite() { @Test fun `encode - decode commit_sig`() { val channelId = ByteVector32.fromValidHex("2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db25") + val fundingTxId = TxId("d7279c1cc0192b73a7e80d89ad045b57806a5ab1443c815207fa944ffa5718cb") val signature = ChannelSpendSignature.IndividualSignature(ByteVector64.fromValidHex("05e06d9a8fdfbb3625051ff2e3cdf82679cc2268beee6905941d6dd8a067cd62711e04b119a836aa0eebe07545172cefb228860fea6c797178453a319169bed7")) val partialSig = ChannelSpendSignature.PartialSignatureWithNonce( ByteVector32("034ad8ca7bed68a934b633c4beeb7dc493cb0ff70e7aa9c86b895bbf3a3b5f82"), @@ -458,7 +459,8 @@ class LightningCodecsTestsCommon : LightningTestSuite() { val testCases = listOf( // @formatter:off CommitSig(channelId, signature, listOf()) to "0084 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db25 05e06d9a8fdfbb3625051ff2e3cdf82679cc2268beee6905941d6dd8a067cd62711e04b119a836aa0eebe07545172cefb228860fea6c797178453a319169bed7 0000", - CommitSig(channelId, partialSig, listOf(), batchSize = 1) to "0084 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db25 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000 0262034ad8ca7bed68a934b633c4beeb7dc493cb0ff70e7aa9c86b895bbf3a3b5f82a49ff67b08c720b993c946556cde1be1c3b664bc847c4792135dfd6ef0986e00e9871808c6620b0420567dad525b27431453d4434fd326f8ac56496639b72326eb5d", + CommitSig(channelId, fundingTxId, signature, listOf()) to "0084 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db25 05e06d9a8fdfbb3625051ff2e3cdf82679cc2268beee6905941d6dd8a067cd62711e04b119a836aa0eebe07545172cefb228860fea6c797178453a319169bed7 0000 0120cb1857fa4f94fa0752813c44b15a6a80575b04ad890de8a7732b19c01c9c27d7", + CommitSig(channelId, fundingTxId, partialSig, listOf()) to "0084 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db25 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000 0120cb1857fa4f94fa0752813c44b15a6a80575b04ad890de8a7732b19c01c9c27d7 0262034ad8ca7bed68a934b633c4beeb7dc493cb0ff70e7aa9c86b895bbf3a3b5f82a49ff67b08c720b993c946556cde1be1c3b664bc847c4792135dfd6ef0986e00e9871808c6620b0420567dad525b27431453d4434fd326f8ac56496639b72326eb5d", // @formatter:on ) testCases.forEach { (commitSig, bin) -> @@ -505,7 +507,7 @@ class LightningCodecsTestsCommon : LightningTestSuite() { TxAddInput(channelId1, 561, tx1, 1, 5u) to ByteVector("0042 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000231 00f7 020000000001014ade359c5deb7c1cde2e94f401854658f97d7fa31c17ce9a831db253120a0a410100000017160014eb9a5bd79194a23d19d6ec473c768fb74f9ed32cffffffff021ca408000000000017a914946118f24bb7b37d5e9e39579e4a411e70f5b6a08763e703000000000017a9143638b2602d11f934c04abc6adb1494f69d1f14af8702473044022059ddd943b399211e4266a349f26b3289979e29f9b067792c6cfa8cc5ae25f44602204d627a5a5b603d0562e7969011fb3d64908af90a3ec7c876eaa9baf61e1958af012102f5188df1da92ed818581c29778047800ed6635788aa09d9469f7d17628f7323300000000 00000001 00000005"), TxAddInput(channelId2, 0, tx2, 2, 0u) to ByteVector("0042 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0000000000000000 0100 0200000000010142180a8812fc79a3da7fb2471eff3e22d7faee990604c2ba7f2fc8dfb15b550a0200000000feffffff030f241800000000001976a9146774040642a78ca3b8b395e70f8391b21ec026fc88ac4a155801000000001600148d2e0b57adcb8869e603fd35b5179caf053361253b1d010000000000160014e032f4f4b9f8611df0d30a20648c190c263bbc33024730440220506005aa347f5b698542cafcb4f1a10250aeb52a609d6fd67ef68f9c1a5d954302206b9bb844343f4012bccd9d08a0f5430afb9549555a3252e499be7df97aae477a012103976d6b3eea3de4b056cd88cdfd50a22daf121e0fb5c6e45ba0f40e1effbd275a00000000 00000002 00000000"), TxAddInput(channelId1, 561, tx1, 0, 0xfffffffdu) to ByteVector("0042 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000231 00f7 020000000001014ade359c5deb7c1cde2e94f401854658f97d7fa31c17ce9a831db253120a0a410100000017160014eb9a5bd79194a23d19d6ec473c768fb74f9ed32cffffffff021ca408000000000017a914946118f24bb7b37d5e9e39579e4a411e70f5b6a08763e703000000000017a9143638b2602d11f934c04abc6adb1494f69d1f14af8702473044022059ddd943b399211e4266a349f26b3289979e29f9b067792c6cfa8cc5ae25f44602204d627a5a5b603d0562e7969011fb3d64908af90a3ec7c876eaa9baf61e1958af012102f5188df1da92ed818581c29778047800ed6635788aa09d9469f7d17628f7323300000000 00000000 fffffffd"), - TxAddInput(channelId1, 561, OutPoint(tx1, 1), 5u) to ByteVector("0042 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000231 0000 00000001 00000005 fd0451201f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106"), + TxAddInput(channelId1, 561, OutPoint(tx1, 1), 5u) to ByteVector("0042 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000231 0000 00000001 00000005 00201f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106"), TxAddInput(channelId1, 561, tx1, 1, 5u, TlvStream(TxAddInputTlv.SwapInParamsLegacy(swapInUserKey, swapInServerKey, swapInRefundDelay))) to ByteVector("0042 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000231 00f7 020000000001014ade359c5deb7c1cde2e94f401854658f97d7fa31c17ce9a831db253120a0a410100000017160014eb9a5bd79194a23d19d6ec473c768fb74f9ed32cffffffff021ca408000000000017a914946118f24bb7b37d5e9e39579e4a411e70f5b6a08763e703000000000017a9143638b2602d11f934c04abc6adb1494f69d1f14af8702473044022059ddd943b399211e4266a349f26b3289979e29f9b067792c6cfa8cc5ae25f44602204d627a5a5b603d0562e7969011fb3d64908af90a3ec7c876eaa9baf61e1958af012102f5188df1da92ed818581c29778047800ed6635788aa09d9469f7d17628f7323300000000 00000001 00000005 fd04534603462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f00000090"), TxAddInput(channelId1, 561, tx1, 1, 5u, TlvStream(TxAddInputTlv.SwapInParams(swapInUserKey, swapInServerKey, swapInUserRefundKey, swapInRefundDelay))) to ByteVector("0042 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000231 00f7 020000000001014ade359c5deb7c1cde2e94f401854658f97d7fa31c17ce9a831db253120a0a410100000017160014eb9a5bd79194a23d19d6ec473c768fb74f9ed32cffffffff021ca408000000000017a914946118f24bb7b37d5e9e39579e4a411e70f5b6a08763e703000000000017a9143638b2602d11f934c04abc6adb1494f69d1f14af8702473044022059ddd943b399211e4266a349f26b3289979e29f9b067792c6cfa8cc5ae25f44602204d627a5a5b603d0562e7969011fb3d64908af90a3ec7c876eaa9baf61e1958af012102f5188df1da92ed818581c29778047800ed6635788aa09d9469f7d17628f7323300000000 00000001 00000005 fd04556703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f033a47288cdae4b25818d0d82802bc114c6f7184f2e071602fa4e3d69881ae2cce00000090"), TxAddOutput(channelId1, 1105, 2047.sat, ByteVector("00149357014afd0ccd265658c9ae81efa995e771f472")) to ByteVector("0043 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000451 00000000000007ff 0016 00149357014afd0ccd265658c9ae81efa995e771f472"), @@ -519,14 +521,14 @@ class LightningCodecsTestsCommon : LightningTestSuite() { TxSignatures(channelId2, tx1, listOf(), null, legacySwapInSignatures, listOf(), listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd025b 80 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5"), TxSignatures(channelId2, tx1, listOf(), null, listOf(), legacySwapInSignatures, listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd025d 80 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5"), TxSignatures(channelId2, tx1, listOf(), null, legacySwapInSignatures.take(1), legacySwapInSignatures.drop(1), listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd025b 40 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 fd025d 40 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5"), - TxSignatures(channelId2, tx1, listOf(), signature, listOf(), listOf(), listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd0259 40 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), - TxSignatures(channelId2, tx1, listOf(), signature, legacySwapInSignatures, listOf(), listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd0259 40 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025b 80 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5"), - TxSignatures(channelId2, tx1, listOf(), signature, listOf(), legacySwapInSignatures, listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd0259 40 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025d 80 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5"), - TxSignatures(channelId2, tx1, listOf(), signature, legacySwapInSignatures.take(1), legacySwapInSignatures.drop(1), listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd0259 40 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025b 40 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 fd025d 40 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5"), - TxSignatures(channelId2, tx1, listOf(), signature, listOf(), listOf(), swapInPartialSignatures, listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd0259 40 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025f fd0148 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc03097c9a5c786c4638d9f9f3460e8bebdfd4b5df4028942f89356a530316491d3003c522e17501cdbe722ac83b2187495c6c35d9cedae48bbb59433727e4f5c610d7031eef07e08298e3fb0332f97cd7139c18a364d88b2b4fa46c78fed0a5b86e4bcb03602f97bbde47fe4618e58d3b8ffaabd5f959477df870aed6d0075d1b5d464e04dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd02d73ec0b15bae2f8a6331bdc5620f8eb2d50e5511470a5a9912172cc3651048f7024a4148b89e0500f55197f38823aec5d0ddf600437a3ab257469aca957e94137a036b678ad3a55192180adbedf8fc9178df1cecf19281386710e7c21da44349c8b602219efb684532a7cb40dbee62c87e3e6dca4658c9d80f6a7608d4c1e8c9d581a3"), - TxSignatures(channelId2, tx1, listOf(), signature, listOf(), listOf(), listOf(), swapInPartialSignatures) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd0259 40 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd0261 fd0148 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc03097c9a5c786c4638d9f9f3460e8bebdfd4b5df4028942f89356a530316491d3003c522e17501cdbe722ac83b2187495c6c35d9cedae48bbb59433727e4f5c610d7031eef07e08298e3fb0332f97cd7139c18a364d88b2b4fa46c78fed0a5b86e4bcb03602f97bbde47fe4618e58d3b8ffaabd5f959477df870aed6d0075d1b5d464e04dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd02d73ec0b15bae2f8a6331bdc5620f8eb2d50e5511470a5a9912172cc3651048f7024a4148b89e0500f55197f38823aec5d0ddf600437a3ab257469aca957e94137a036b678ad3a55192180adbedf8fc9178df1cecf19281386710e7c21da44349c8b602219efb684532a7cb40dbee62c87e3e6dca4658c9d80f6a7608d4c1e8c9d581a3"), - TxSignatures(channelId2, tx1, listOf(), signature, listOf(), listOf(), swapInPartialSignatures.take(1), swapInPartialSignatures.drop(1)) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd0259 40 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025f a4 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc03097c9a5c786c4638d9f9f3460e8bebdfd4b5df4028942f89356a530316491d3003c522e17501cdbe722ac83b2187495c6c35d9cedae48bbb59433727e4f5c610d7031eef07e08298e3fb0332f97cd7139c18a364d88b2b4fa46c78fed0a5b86e4bcb03602f97bbde47fe4618e58d3b8ffaabd5f959477df870aed6d0075d1b5d464e04 fd0261 a4 dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd02d73ec0b15bae2f8a6331bdc5620f8eb2d50e5511470a5a9912172cc3651048f7024a4148b89e0500f55197f38823aec5d0ddf600437a3ab257469aca957e94137a036b678ad3a55192180adbedf8fc9178df1cecf19281386710e7c21da44349c8b602219efb684532a7cb40dbee62c87e3e6dca4658c9d80f6a7608d4c1e8c9d581a3"), - TxSignatures(channelId2, tx1, listOf(), signature, legacySwapInSignatures.take(1), legacySwapInSignatures.drop(1), swapInPartialSignatures.take(1), swapInPartialSignatures.drop(1)) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 fd0259 40 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025b 40 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 fd025d 40 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5 fd025f a4 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc03097c9a5c786c4638d9f9f3460e8bebdfd4b5df4028942f89356a530316491d3003c522e17501cdbe722ac83b2187495c6c35d9cedae48bbb59433727e4f5c610d7031eef07e08298e3fb0332f97cd7139c18a364d88b2b4fa46c78fed0a5b86e4bcb03602f97bbde47fe4618e58d3b8ffaabd5f959477df870aed6d0075d1b5d464e04 fd0261 a4 dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd02d73ec0b15bae2f8a6331bdc5620f8eb2d50e5511470a5a9912172cc3651048f7024a4148b89e0500f55197f38823aec5d0ddf600437a3ab257469aca957e94137a036b678ad3a55192180adbedf8fc9178df1cecf19281386710e7c21da44349c8b602219efb684532a7cb40dbee62c87e3e6dca4658c9d80f6a7608d4c1e8c9d581a3"), + TxSignatures(channelId2, tx1, listOf(), signature, listOf(), listOf(), listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 0040aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + TxSignatures(channelId2, tx1, listOf(), signature, legacySwapInSignatures, listOf(), listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 0040aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025b 80 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5"), + TxSignatures(channelId2, tx1, listOf(), signature, listOf(), legacySwapInSignatures, listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 0040aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025d 80 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5"), + TxSignatures(channelId2, tx1, listOf(), signature, legacySwapInSignatures.take(1), legacySwapInSignatures.drop(1), listOf(), listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 0040aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025b 40 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 fd025d 40 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5"), + TxSignatures(channelId2, tx1, listOf(), signature, listOf(), listOf(), swapInPartialSignatures, listOf()) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 0040aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025f fd0148 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc03097c9a5c786c4638d9f9f3460e8bebdfd4b5df4028942f89356a530316491d3003c522e17501cdbe722ac83b2187495c6c35d9cedae48bbb59433727e4f5c610d7031eef07e08298e3fb0332f97cd7139c18a364d88b2b4fa46c78fed0a5b86e4bcb03602f97bbde47fe4618e58d3b8ffaabd5f959477df870aed6d0075d1b5d464e04dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd02d73ec0b15bae2f8a6331bdc5620f8eb2d50e5511470a5a9912172cc3651048f7024a4148b89e0500f55197f38823aec5d0ddf600437a3ab257469aca957e94137a036b678ad3a55192180adbedf8fc9178df1cecf19281386710e7c21da44349c8b602219efb684532a7cb40dbee62c87e3e6dca4658c9d80f6a7608d4c1e8c9d581a3"), + TxSignatures(channelId2, tx1, listOf(), signature, listOf(), listOf(), listOf(), swapInPartialSignatures) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 0040aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd0261 fd0148 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc03097c9a5c786c4638d9f9f3460e8bebdfd4b5df4028942f89356a530316491d3003c522e17501cdbe722ac83b2187495c6c35d9cedae48bbb59433727e4f5c610d7031eef07e08298e3fb0332f97cd7139c18a364d88b2b4fa46c78fed0a5b86e4bcb03602f97bbde47fe4618e58d3b8ffaabd5f959477df870aed6d0075d1b5d464e04dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd02d73ec0b15bae2f8a6331bdc5620f8eb2d50e5511470a5a9912172cc3651048f7024a4148b89e0500f55197f38823aec5d0ddf600437a3ab257469aca957e94137a036b678ad3a55192180adbedf8fc9178df1cecf19281386710e7c21da44349c8b602219efb684532a7cb40dbee62c87e3e6dca4658c9d80f6a7608d4c1e8c9d581a3"), + TxSignatures(channelId2, tx1, listOf(), signature, listOf(), listOf(), swapInPartialSignatures.take(1), swapInPartialSignatures.drop(1)) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 0040aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025f a4 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc03097c9a5c786c4638d9f9f3460e8bebdfd4b5df4028942f89356a530316491d3003c522e17501cdbe722ac83b2187495c6c35d9cedae48bbb59433727e4f5c610d7031eef07e08298e3fb0332f97cd7139c18a364d88b2b4fa46c78fed0a5b86e4bcb03602f97bbde47fe4618e58d3b8ffaabd5f959477df870aed6d0075d1b5d464e04 fd0261 a4 dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd02d73ec0b15bae2f8a6331bdc5620f8eb2d50e5511470a5a9912172cc3651048f7024a4148b89e0500f55197f38823aec5d0ddf600437a3ab257469aca957e94137a036b678ad3a55192180adbedf8fc9178df1cecf19281386710e7c21da44349c8b602219efb684532a7cb40dbee62c87e3e6dca4658c9d80f6a7608d4c1e8c9d581a3"), + TxSignatures(channelId2, tx1, listOf(), signature, legacySwapInSignatures.take(1), legacySwapInSignatures.drop(1), swapInPartialSignatures.take(1), swapInPartialSignatures.drop(1)) to ByteVector("0047 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 1f2ec025a33e39ef8e177afcdc1adc855bf128dc906182255aeb64efa825f106 0000 0040aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb fd025b 40 c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3 fd025d 40 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5 fd025f a4 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc03097c9a5c786c4638d9f9f3460e8bebdfd4b5df4028942f89356a530316491d3003c522e17501cdbe722ac83b2187495c6c35d9cedae48bbb59433727e4f5c610d7031eef07e08298e3fb0332f97cd7139c18a364d88b2b4fa46c78fed0a5b86e4bcb03602f97bbde47fe4618e58d3b8ffaabd5f959477df870aed6d0075d1b5d464e04 fd0261 a4 dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd02d73ec0b15bae2f8a6331bdc5620f8eb2d50e5511470a5a9912172cc3651048f7024a4148b89e0500f55197f38823aec5d0ddf600437a3ab257469aca957e94137a036b678ad3a55192180adbedf8fc9178df1cecf19281386710e7c21da44349c8b602219efb684532a7cb40dbee62c87e3e6dca4658c9d80f6a7608d4c1e8c9d581a3"), TxInitRbf(channelId1, 8388607, FeeratePerKw(4000.sat)) to ByteVector("0048 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 007fffff 00000fa0"), TxInitRbf(channelId1, 0, FeeratePerKw(4000.sat), TlvStream(TxInitRbfTlv.SharedOutputContributionTlv(1_500_000.sat), TxInitRbfTlv.RequireConfirmedInputsTlv)) to ByteVector("0048 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000 00000fa0 0008000000000016e360 0200"), TxInitRbf(channelId1, 0, FeeratePerKw(4000.sat), TlvStream(TxInitRbfTlv.SharedOutputContributionTlv(0.sat))) to ByteVector("0048 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000 00000fa0 00080000000000000000"), @@ -558,19 +560,19 @@ class LightningCodecsTestsCommon : LightningTestSuite() { // @formatter:off Stfu(channelId, false) to ByteVector("0002 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00"), Stfu(channelId, true) to ByteVector("0002 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 01"), - SpliceInit(channelId, 100_000.sat, FeeratePerKw(2500.sat), 100, fundingPubkey) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000186a0 000009c4 00000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), - SpliceInit(channelId, 0.sat, FeeratePerKw(500.sat), 0, fundingPubkey) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000000 000001f4 00000000 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), - SpliceInit(channelId, (-50_000).sat, FeeratePerKw(500.sat), 0, fundingPubkey) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ffffffffffff3cb0 000001f4 00000000 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), - SpliceInit(channelId, 100_000.sat, FeeratePerKw(2500.sat), 100, fundingPubkey, LiquidityAds.RequestFunding(100_000.sat, fundingRate, LiquidityAds.PaymentDetails.FromChannelBalance), channelType = null) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000186a0 000009c4 00000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b1e00000000000186a0000186a0000186a00190009600000000000000000000"), - SpliceInit(channelId, 100_000.sat, FeeratePerKw(2500.sat), 100, fundingPubkey, null, ChannelType.SupportedChannelType.SimpleTaprootChannels) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000186a0 000009c400000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fe 47000011 471000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000"), - SpliceAck(channelId, 25_000.sat, fundingPubkey) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), - SpliceAck(channelId, 0.sat, fundingPubkey) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000000 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), - SpliceAck(channelId, (-25_000).sat, fundingPubkey) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ffffffffffff9e58 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), - SpliceAck(channelId, 25_000.sat, fundingPubkey, LiquidityAds.WillFund(fundingRate, ByteVector("deadbeef"), ByteVector64.Zeroes), channelType = null) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b5a000186a0000186a00190009600000000000000000004deadbeef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), - SpliceAck(channelId, 25_000.sat, fundingPubkey, TlvStream(ChannelTlv.FeeCreditUsedTlv(0.msat))) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fda05200"), - SpliceAck(channelId, 25_000.sat, fundingPubkey, TlvStream(ChannelTlv.FeeCreditUsedTlv(1729.msat))) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fda0520206c1"), - SpliceAck(channelId, 25_000.sat, fundingPubkey, null, ChannelType.SupportedChannelType.SimpleTaprootChannels) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fe 47000011 471000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000"), - SpliceLocked(channelId, fundingTxId) to ByteVector("908c aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 24e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566"), + SpliceInit(channelId, 100_000.sat, FeeratePerKw(2500.sat), 100, fundingPubkey) to ByteVector("0050 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000186a0 000009c4 00000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + SpliceInit(channelId, 0.sat, FeeratePerKw(500.sat), 0, fundingPubkey) to ByteVector("0050 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000000 000001f4 00000000 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + SpliceInit(channelId, (-50_000).sat, FeeratePerKw(500.sat), 0, fundingPubkey) to ByteVector("0050 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ffffffffffff3cb0 000001f4 00000000 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + SpliceInit(channelId, 100_000.sat, FeeratePerKw(2500.sat), 100, fundingPubkey, LiquidityAds.RequestFunding(100_000.sat, fundingRate, LiquidityAds.PaymentDetails.FromChannelBalance), channelType = null) to ByteVector("0050 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000186a0 000009c4 00000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b1e00000000000186a0000186a0000186a00190009600000000000000000000"), + SpliceInit(channelId, 100_000.sat, FeeratePerKw(2500.sat), 100, fundingPubkey, null, ChannelType.SupportedChannelType.SimpleTaprootChannels) to ByteVector("0050 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000186a0 000009c400000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fe 47000011 471000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000"), + SpliceAck(channelId, 25_000.sat, fundingPubkey) to ByteVector("0051 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + SpliceAck(channelId, 0.sat, fundingPubkey) to ByteVector("0051 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000000 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + SpliceAck(channelId, (-25_000).sat, fundingPubkey) to ByteVector("0051 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ffffffffffff9e58 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + SpliceAck(channelId, 25_000.sat, fundingPubkey, LiquidityAds.WillFund(fundingRate, ByteVector("deadbeef"), ByteVector64.Zeroes), channelType = null) to ByteVector("0051 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b5a000186a0000186a00190009600000000000000000004deadbeef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + SpliceAck(channelId, 25_000.sat, fundingPubkey, TlvStream(ChannelTlv.FeeCreditUsedTlv(0.msat))) to ByteVector("0051 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fda05200"), + SpliceAck(channelId, 25_000.sat, fundingPubkey, TlvStream(ChannelTlv.FeeCreditUsedTlv(1729.msat))) to ByteVector("0051 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fda0520206c1"), + SpliceAck(channelId, 25_000.sat, fundingPubkey, null, ChannelType.SupportedChannelType.SimpleTaprootChannels) to ByteVector("0051 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fe 47000011 471000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000"), + SpliceLocked(channelId, fundingTxId) to ByteVector("004d aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 24e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566"), // @formatter:on ) testCases.forEach { (message, bin) -> @@ -582,6 +584,24 @@ class LightningCodecsTestsCommon : LightningTestSuite() { } } + @Test + fun `encode - decode start_batch`() { + val channelId = ByteVector32("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + val testCases = listOf( + StartBatch(channelId, 1) to ByteVector("007f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0001 01020084"), + StartBatch(channelId, 7) to ByteVector("007f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0007 01020084"), + StartBatch(channelId, 32000) to ByteVector("007f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 7d00 01020084"), + StartBatch(channelId, 7, TlvStream(StartBatchTlv.MessageType(57331))) to ByteVector("007f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0007 0102dff3"), + ) + testCases.forEach { (message, bin) -> + val decoded = LightningMessage.decode(bin.toByteArray()) + assertNotNull(decoded) + assertEquals(decoded, message) + val encoded = LightningMessage.encode(message) + assertEquals(encoded.byteVector(), bin) + } + } + @Test fun `encode - decode channel_reestablish`() { val channelId = ByteVector32("c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c") @@ -594,9 +614,9 @@ class LightningCodecsTestsCommon : LightningTestSuite() { val testCases = listOf( // @formatter:off ChannelReestablish(channelId, 242842, 42, commitmentSecret, commitmentPoint) to ByteVector("0088 c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c 000000000003b49a 000000000000002a 34f159d37cf7b5de52ec0adc3968886232f90d272e8c82e8b6f7fcb7e57c4b55 02bf050efff417efc09eb211ca9e4e845920e2503740800e88505b25e6f0e1e867"), - ChannelReestablish(channelId, 242842, 42, commitmentSecret, commitmentPoint, nextCommitNonces = listOf(), nextFundingTxId = nextFundingTxId) to ByteVector("0088 c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c 000000000003b49a 000000000000002a 34f159d37cf7b5de52ec0adc3968886232f90d272e8c82e8b6f7fcb7e57c4b55 02bf050efff417efc09eb211ca9e4e845920e2503740800e88505b25e6f0e1e867 00 20 24e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566"), + ChannelReestablish(channelId, 242842, 42, commitmentSecret, commitmentPoint, nextCommitNonces = listOf(), nextFundingTxId = nextFundingTxId) to ByteVector("0088 c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c 000000000003b49a 000000000000002a 34f159d37cf7b5de52ec0adc3968886232f90d272e8c82e8b6f7fcb7e57c4b55 02bf050efff417efc09eb211ca9e4e845920e2503740800e88505b25e6f0e1e867 012124e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f1456600"), ChannelReestablish(channelId, 242842, 42, commitmentSecret, commitmentPoint, listOf(Pair(fundingTxId, commitNonce), Pair(nextFundingTxId, nextCommitNonce))) to ByteVector("0088 c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c 000000000003b49a 000000000000002a 34f159d37cf7b5de52ec0adc3968886232f90d272e8c82e8b6f7fcb7e57c4b55 02bf050efff417efc09eb211ca9e4e845920e2503740800e88505b25e6f0e1e867 16c4422c292416d1151e5bfd210990b2306e5c4d3e2e3f9d53247bbd5557c96fbfa0798cd38fe6ea51c1e8f021007c809f613034f1cb702cc64f4b9370c3bb73a67844ed280d26ae541a9c855a035fe0d87daf6b5de90a2f8785947aa4c10f294a5131f524e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566d8bc06ffaf24f29fb6310290262651a3154928bc8ad0d1546257e1bfe19ae6ac4ec02c15ee3f397f601d2c521ff65c604d04170b42ffee95db597d82df2bfde5b14a"), - ChannelReestablish(channelId, 242842, 42, commitmentSecret, commitmentPoint, listOf(Pair(nextFundingTxId, nextCommitNonce)), nextFundingTxId, commitNonce) to ByteVector("0088 c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c 000000000003b49a 000000000000002a 34f159d37cf7b5de52ec0adc3968886232f90d272e8c82e8b6f7fcb7e57c4b55 02bf050efff417efc09eb211ca9e4e845920e2503740800e88505b25e6f0e1e867 002024e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566 166224e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566d8bc06ffaf24f29fb6310290262651a3154928bc8ad0d1546257e1bfe19ae6ac4ec02c15ee3f397f601d2c521ff65c604d04170b42ffee95db597d82df2bfde5b14a 1842798cd38fe6ea51c1e8f021007c809f613034f1cb702cc64f4b9370c3bb73a67844ed280d26ae541a9c855a035fe0d87daf6b5de90a2f8785947aa4c10f294a5131f5"), + ChannelReestablish(channelId, 242842, 42, commitmentSecret, commitmentPoint, listOf(Pair(nextFundingTxId, nextCommitNonce)), nextFundingTxId, retransmitCommitSig = true, currentFundingLocked = fundingTxId, commitNonce) to ByteVector("0088 c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c 000000000003b49a 000000000000002a 34f159d37cf7b5de52ec0adc3968886232f90d272e8c82e8b6f7fcb7e57c4b55 02bf050efff417efc09eb211ca9e4e845920e2503740800e88505b25e6f0e1e867 012124e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f1456601 0521422c292416d1151e5bfd210990b2306e5c4d3e2e3f9d53247bbd5557c96fbfa000 166224e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566d8bc06ffaf24f29fb6310290262651a3154928bc8ad0d1546257e1bfe19ae6ac4ec02c15ee3f397f601d2c521ff65c604d04170b42ffee95db597d82df2bfde5b14a 1842798cd38fe6ea51c1e8f021007c809f613034f1cb702cc64f4b9370c3bb73a67844ed280d26ae541a9c855a035fe0d87daf6b5de90a2f8785947aa4c10f294a5131f5"), // @formatter:on ) testCases.forEach { (message, bin) -> @@ -771,18 +791,19 @@ class LightningCodecsTestsCommon : LightningTestSuite() { // @formatter:off val refs = mapOf( // channel_reestablish - Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point), - Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() + Hex.decode("01 02 0102") to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream(setOf(), setOf(GenericTlv(1, ByteVector("0102"))))), - Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream()), - Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() + Hex.decode("01 02 0102") to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream(setOf(), setOf(GenericTlv(1, ByteVector("0102"))))), Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream()), - Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() + Hex.decode("01 02 0102") to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream(setOf(), setOf(GenericTlv(1, ByteVector("0102"))))), + Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() + Hex.decode("01 21") + nextTxHash.value.toByteArray() + Hex.decode("00") to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream(ChannelReestablishTlv.NextFunding(TxId(nextTxHash), retransmitCommitSig = false))), + Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() + Hex.decode("01 21") + nextTxHash.value.toByteArray() + Hex.decode("01") to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream(ChannelReestablishTlv.NextFunding(TxId(nextTxHash), retransmitCommitSig = true))), + Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() + Hex.decode("05 21") + txHash.value.toByteArray() + Hex.decode("00") to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream(ChannelReestablishTlv.MyCurrentFundingLocked(TxId(txHash), retransmitAnnSigs = false))), + Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() + Hex.decode("05 21") + txHash.value.toByteArray() + Hex.decode("01") to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream(ChannelReestablishTlv.MyCurrentFundingLocked(TxId(txHash), retransmitAnnSigs = true))), + Hex.decode("0088") + channelId.toByteArray() + Hex.decode("0001020304050607 0809aabbccddeeff") + key.value.toByteArray() + point.value.toByteArray() + Hex.decode("71 02 0102") to ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream(setOf(), setOf(GenericTlv(113, ByteVector("0102"))))), // tx_signatures Hex.decode("0047") + channelId.toByteArray() + txHash.value.toByteArray() + Hex.decode("0000") to TxSignatures(channelId, TxId(txHash), listOf()), Hex.decode("0047") + channelId.toByteArray() + txHash.value.toByteArray() + Hex.decode("0000 2b012a") to TxSignatures(channelId, TxId(txHash), listOf(), TlvStream(setOf(), setOf(GenericTlv(43, ByteVector("2a"))))), // commit_sig Hex.decode("0084") + channelId.toByteArray() + signature.sig.toByteArray() + Hex.decode("0000") to CommitSig(channelId, signature, listOf()), - Hex.decode("0084") + channelId.toByteArray() + signature.sig.toByteArray() + Hex.decode("0000") + Hex.decode("01 02 0102") to CommitSig(channelId, signature, listOf(), TlvStream(setOf(), setOf(GenericTlv(1, ByteVector("0102"))))), + Hex.decode("0084") + channelId.toByteArray() + signature.sig.toByteArray() + Hex.decode("0000") + Hex.decode("01 20") + txHash.value.toByteArray() to CommitSig(channelId, signature, listOf(), TlvStream(CommitSigTlv.FundingTx(TxId(txHash)))), + Hex.decode("0084") + channelId.toByteArray() + signature.sig.toByteArray() + Hex.decode("0000") + Hex.decode("fd0231 02 0102") to CommitSig(channelId, signature, listOf(), TlvStream(setOf(), setOf(GenericTlv(561, ByteVector("0102"))))), // revoke_and_ack Hex.decode("0085") + channelId.toByteArray() + key.value.toByteArray() + point.value.toByteArray() to RevokeAndAck(channelId, key, point), Hex.decode("0085") + channelId.toByteArray() + key.value.toByteArray() + point.value.toByteArray() + Hex.decode("01 02 0102") to RevokeAndAck(channelId, key, point, TlvStream(setOf(), setOf(GenericTlv(1, ByteVector("0102"))))), diff --git a/modules/core/src/commonTest/resources/nonreg/v4/Closing_Local_ebb9087c/data.json b/modules/core/src/commonTest/resources/nonreg/v4/Closing_Local_ebb9087c/data.json index 8892be1e1..3c44db65b 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/Closing_Local_ebb9087c/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/Closing_Local_ebb9087c/data.json @@ -60,12 +60,12 @@ "option_channel_type": "Mandatory", "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", - "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional" + "trampoline_payment_experimental": "Optional" }, "unknown": [ 137, - 145 + 145, + 155 ] } }, diff --git a/modules/core/src/commonTest/resources/nonreg/v4/Closing_Mutual_ebb9087c/data.json b/modules/core/src/commonTest/resources/nonreg/v4/Closing_Mutual_ebb9087c/data.json index 37f0781aa..e753d779e 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/Closing_Mutual_ebb9087c/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/Closing_Mutual_ebb9087c/data.json @@ -60,12 +60,12 @@ "option_channel_type": "Mandatory", "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", - "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional" + "trampoline_payment_experimental": "Optional" }, "unknown": [ 137, - 145 + 145, + 155 ] } }, diff --git a/modules/core/src/commonTest/resources/nonreg/v4/Closing_Remote_ebb9087c/data.json b/modules/core/src/commonTest/resources/nonreg/v4/Closing_Remote_ebb9087c/data.json index ab2371186..32692f999 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/Closing_Remote_ebb9087c/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/Closing_Remote_ebb9087c/data.json @@ -60,12 +60,12 @@ "option_channel_type": "Mandatory", "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", - "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional" + "trampoline_payment_experimental": "Optional" }, "unknown": [ 137, - 145 + 145, + 155 ] } }, diff --git a/modules/core/src/commonTest/resources/nonreg/v4/Closing_Revoked_ebb9087c/data.json b/modules/core/src/commonTest/resources/nonreg/v4/Closing_Revoked_ebb9087c/data.json index d78b7bb4e..af9e3f35a 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/Closing_Revoked_ebb9087c/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/Closing_Revoked_ebb9087c/data.json @@ -60,12 +60,12 @@ "option_channel_type": "Mandatory", "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", - "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional" + "trampoline_payment_experimental": "Optional" }, "unknown": [ 137, - 145 + 145, + 155 ] } }, diff --git a/modules/core/src/commonTest/resources/nonreg/v4/Negotiating_fac54067/data.json b/modules/core/src/commonTest/resources/nonreg/v4/Negotiating_fac54067/data.json index 580f04be3..4bafcda15 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/Negotiating_fac54067/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/Negotiating_fac54067/data.json @@ -35,10 +35,11 @@ "option_simple_close": "Mandatory", "wake_up_notification_provider": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "remoteParams": { @@ -65,11 +66,12 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional", "funding_fee_credit": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "channelFlags": { diff --git a/modules/core/src/commonTest/resources/nonreg/v4/Normal_77f198a3/data.json b/modules/core/src/commonTest/resources/nonreg/v4/Normal_77f198a3/data.json index 29b22b63c..970b6f484 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/Normal_77f198a3/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/Normal_77f198a3/data.json @@ -35,11 +35,12 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional", "funding_fee_credit": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "remoteParams": { @@ -66,10 +67,11 @@ "option_simple_close": "Mandatory", "wake_up_notification_provider": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "channelFlags": { diff --git a/modules/core/src/commonTest/resources/nonreg/v4/Normal_ebb9087c/data.json b/modules/core/src/commonTest/resources/nonreg/v4/Normal_ebb9087c/data.json index bc84ae02c..a23fb8361 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/Normal_ebb9087c/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/Normal_ebb9087c/data.json @@ -33,12 +33,12 @@ "option_channel_type": "Mandatory", "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", - "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional" + "trampoline_payment_experimental": "Optional" }, "unknown": [ 137, - 145 + 145, + 155 ] } }, diff --git a/modules/core/src/commonTest/resources/nonreg/v4/Normal_ff34df87/data.json b/modules/core/src/commonTest/resources/nonreg/v4/Normal_ff34df87/data.json index 3a5c81787..1d7419d7b 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/Normal_ff34df87/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/Normal_ff34df87/data.json @@ -35,10 +35,11 @@ "option_simple_close": "Mandatory", "wake_up_notification_provider": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "remoteParams": { @@ -65,11 +66,12 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional", "funding_fee_credit": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "channelFlags": { diff --git a/modules/core/src/commonTest/resources/nonreg/v4/ShuttingDown_fac54067/data.json b/modules/core/src/commonTest/resources/nonreg/v4/ShuttingDown_fac54067/data.json index a82e98d61..51410709c 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/ShuttingDown_fac54067/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/ShuttingDown_fac54067/data.json @@ -35,10 +35,11 @@ "option_simple_close": "Mandatory", "wake_up_notification_provider": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "remoteParams": { @@ -66,11 +67,12 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional", "funding_fee_credit": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "channelFlags": { diff --git a/modules/core/src/commonTest/resources/nonreg/v4/WaitForChannelReady_fac54067/data.json b/modules/core/src/commonTest/resources/nonreg/v4/WaitForChannelReady_fac54067/data.json index b2c81be9f..54c466270 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/WaitForChannelReady_fac54067/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/WaitForChannelReady_fac54067/data.json @@ -35,10 +35,11 @@ "option_simple_close": "Mandatory", "wake_up_notification_provider": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "remoteParams": { @@ -66,11 +67,12 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional", "funding_fee_credit": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "channelFlags": { diff --git a/modules/core/src/commonTest/resources/nonreg/v4/WaitForFundingConfirmed_fac54067/data.json b/modules/core/src/commonTest/resources/nonreg/v4/WaitForFundingConfirmed_fac54067/data.json index 03a1bc23c..e7ed94006 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/WaitForFundingConfirmed_fac54067/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/WaitForFundingConfirmed_fac54067/data.json @@ -35,10 +35,11 @@ "option_simple_close": "Mandatory", "wake_up_notification_provider": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "remoteParams": { @@ -65,11 +66,12 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional", "on_the_fly_funding": "Optional", "funding_fee_credit": "Optional" }, - "unknown": [] + "unknown": [ + 155 + ] } }, "channelFlags": { diff --git a/modules/core/src/commonTest/resources/nonreg/v4/WaitForRemotePublishFutureCommitment_ebb9087c/data.json b/modules/core/src/commonTest/resources/nonreg/v4/WaitForRemotePublishFutureCommitment_ebb9087c/data.json index be7519e9d..2b5384bc2 100644 --- a/modules/core/src/commonTest/resources/nonreg/v4/WaitForRemotePublishFutureCommitment_ebb9087c/data.json +++ b/modules/core/src/commonTest/resources/nonreg/v4/WaitForRemotePublishFutureCommitment_ebb9087c/data.json @@ -60,11 +60,11 @@ "option_channel_type": "Mandatory", "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", - "trampoline_payment_experimental": "Optional", - "splice_experimental": "Optional" + "trampoline_payment_experimental": "Optional" }, "unknown": [ - 137 + 137, + 155 ] } },