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 e3afb6de5..24d2f1d37 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt @@ -200,6 +200,7 @@ data class NodeParams( Feature.Wumbo to FeatureSupport.Optional, Feature.StaticRemoteKey to FeatureSupport.Mandatory, Feature.AnchorOutputs to FeatureSupport.Optional, // can't set Mandatory because peers prefers AnchorOutputsZeroFeeHtlcTx + Feature.SimpleTaprootChannels to FeatureSupport.Optional, Feature.RouteBlinding to FeatureSupport.Optional, Feature.DualFunding to FeatureSupport.Mandatory, Feature.ShutdownAnySegwit to FeatureSupport.Mandatory, 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 4b49d622c..6160c28c3 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 @@ -4,7 +4,6 @@ import fr.acinq.bitcoin.* import fr.acinq.bitcoin.Crypto.sha256 import fr.acinq.bitcoin.crypto.musig2.IndividualNonce import fr.acinq.bitcoin.utils.Either -import fr.acinq.bitcoin.utils.getOrElse import fr.acinq.lightning.CltvExpiryDelta import fr.acinq.lightning.Feature import fr.acinq.lightning.MilliSatoshi @@ -14,12 +13,7 @@ import fr.acinq.lightning.blockchain.fee.FeeratePerKw import fr.acinq.lightning.blockchain.fee.FeerateTolerance import fr.acinq.lightning.channel.states.Channel import fr.acinq.lightning.channel.states.ChannelContext -import fr.acinq.lightning.crypto.ChannelKeys -import fr.acinq.lightning.crypto.KeyManager -import fr.acinq.lightning.crypto.LocalCommitmentKeys -import fr.acinq.lightning.crypto.NonceGenerator -import fr.acinq.lightning.crypto.RemoteCommitmentKeys -import fr.acinq.lightning.crypto.ShaChain +import fr.acinq.lightning.crypto.* import fr.acinq.lightning.logging.MDCLogger import fr.acinq.lightning.payment.OutgoingPaymentPacket import fr.acinq.lightning.transactions.CommitmentSpec @@ -36,7 +30,6 @@ import fr.acinq.lightning.transactions.incomings import fr.acinq.lightning.transactions.outgoings import fr.acinq.lightning.utils.* import fr.acinq.lightning.wire.* -import kotlinx.serialization.Transient import kotlin.math.min /** Static channel parameters shared by all commitments. */ @@ -134,17 +127,14 @@ data class LocalCommit(val index: Long, val spec: CommitmentSpec, val txId: TxId spec = spec, ) val remoteSigOk = when (commitmentFormat) { - Transactions.CommitmentFormat.SimpleTaprootChannels -> - when (commit.sigOrPartialSig) { - is ChannelSpendSignature.PartialSignatureWithNonce -> { - val localNonce = NonceGenerator.verificationNonce(commitInput.outPoint.txid, fundingKey, remoteFundingPubKey, localCommitIndex) - localCommitTx.checkRemotePartialSignature(fundingKey.publicKey(), remoteFundingPubKey, commit.sigOrPartialSig, localNonce.publicNonce) - } - - is ChannelSpendSignature.IndividualSignature -> false + Transactions.CommitmentFormat.AnchorOutputs -> localCommitTx.checkRemoteSig(fundingKey.publicKey(), remoteFundingPubKey, commit.signature) + Transactions.CommitmentFormat.SimpleTaprootChannels -> when (val remoteSig = commit.sigOrPartialSig) { + is ChannelSpendSignature.IndividualSignature -> false + is ChannelSpendSignature.PartialSignatureWithNonce -> { + val localNonce = NonceGenerator.verificationNonce(commitInput.outPoint.txid, fundingKey, remoteFundingPubKey, localCommitIndex) + localCommitTx.checkRemotePartialSignature(fundingKey.publicKey(), remoteFundingPubKey, remoteSig, localNonce.publicNonce) } - - else -> localCommitTx.checkRemoteSig(fundingKey.publicKey(), remoteFundingPubKey, commit.signature) + } } if (!remoteSigOk) { log.error { "remote signature $commit is invalid" } @@ -178,7 +168,7 @@ data class RemoteCommit(val index: Long, val spec: CommitmentSpec, val txid: TxI ): Either { val fundingKey = channelKeys.fundingKey(fundingTxIndex) val commitKeys = channelKeys.remoteCommitmentKeys(channelParams, remotePerCommitmentPoint) - val (remoteCommitTx, sortedHtlcsTxs) = Commitments.makeRemoteTxs( + val (remoteCommitTx, sortedHtlcTxs) = Commitments.makeRemoteTxs( channelParams = channelParams, commitParams = commitParams, commitKeys = commitKeys, @@ -189,22 +179,22 @@ data class RemoteCommit(val index: Long, val spec: CommitmentSpec, val txid: TxI commitmentFormat = commitmentFormat, spec = spec ) - val sig = when (commitmentFormat) { - is Transactions.CommitmentFormat.SimpleTaprootChannels -> { - if (remoteNonce == null) { - Either.Left(MissingCommitNonce(channelParams.channelId, commitInput.outPoint.txid, index)) - } else { + val htlcSigs = sortedHtlcTxs.map { it.localSig(commitKeys) } + return when (commitmentFormat) { + Transactions.CommitmentFormat.AnchorOutputs -> { + val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubKey) + Either.Right(CommitSig(channelParams.channelId, sig, htlcSigs, batchSize)) + } + Transactions.CommitmentFormat.SimpleTaprootChannels -> when (remoteNonce) { + null -> Either.Left(MissingCommitNonce(channelParams.channelId, commitInput.outPoint.txid, index)) + else -> { val localNonce = NonceGenerator.signingNonce(fundingKey.publicKey(), remoteFundingPubKey, commitInput.outPoint.txid) - remoteCommitTx.partialSign(fundingKey, remoteFundingPubKey, mapOf(), localNonce, listOf(localNonce.publicNonce, remoteNonce)) - .transform({ InvalidCommitNonce(channelParams.channelId, commitInput.outPoint.txid, index) }, { it }) + 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)) + } } } - - else -> Either.Right(remoteCommitTx.sign(fundingKey, remoteFundingPubKey)) - } - return sig.map { - val htlcSigs = sortedHtlcsTxs.map { it.localSig(commitKeys) } - CommitSig(channelParams.channelId, it, htlcSigs.toList(), batchSize) } } @@ -213,7 +203,7 @@ data class RemoteCommit(val index: Long, val spec: CommitmentSpec, val txid: TxI channelParams, signingSession.remoteCommitParams, channelKeys, - signingSession.fundingTxIndex, + signingSession.fundingParams.fundingTxIndex, signingSession.fundingParams.remoteFundingPubkey, signingSession.commitInput(channelKeys), signingSession.fundingParams.commitmentFormat, @@ -580,54 +570,26 @@ data class Commitment( spec = spec ) val sig = when (commitmentFormat) { - Transactions.CommitmentFormat.SimpleTaprootChannels -> ChannelSpendSignature.IndividualSignature(ByteVector64.Zeroes) - else -> remoteCommitTx.sign(fundingKey, remoteFundingPubkey) - } - val partialSig = when (commitmentFormat) { - Transactions.CommitmentFormat.SimpleTaprootChannels -> { - if (nextRemoteNonce == null) return Either.Left(MissingCommitNonce(params.channelId, remoteCommitTx.input.outPoint.txid, remoteCommit.index)) - val localNonce = NonceGenerator.signingNonce(fundingKey.publicKey(), remoteFundingPubkey, remoteCommitTx.input.outPoint.txid) - remoteCommitTx.partialSign(fundingKey, remoteFundingPubkey, mapOf(), localNonce, listOf(localNonce.publicNonce, nextRemoteNonce)) - .getOrElse { return Either.Left(InvalidCommitNonce(params.channelId, remoteCommitTx.input.outPoint.txid, remoteCommit.index)) } + Transactions.CommitmentFormat.AnchorOutputs -> remoteCommitTx.sign(fundingKey, remoteFundingPubkey) + Transactions.CommitmentFormat.SimpleTaprootChannels -> when (nextRemoteNonce) { + null -> return Either.Left(MissingCommitNonce(params.channelId, fundingTxId, remoteCommit.index + 1)) + else -> { + val localNonce = NonceGenerator.signingNonce(fundingKey.publicKey(), remoteFundingPubkey, fundingTxId) + when (val psig = remoteCommitTx.partialSign(fundingKey, remoteFundingPubkey, mapOf(), localNonce, listOf(localNonce.publicNonce, nextRemoteNonce))) { + is Either.Left -> return Either.Left(InvalidCommitNonce(params.channelId, fundingTxId, remoteCommit.index + 1)) + is Either.Right -> psig.value + } + } } - - else -> null } val htlcSigs = sortedHtlcTxs.map { it.localSig(commitKeys) } - // NB: IN/OUT htlcs are inverted because this is the remote commit log.info { val htlcsIn = spec.htlcs.outgoings().map { it.id }.joinToString(",") 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 tlvs = buildSet { - if (spec.htlcs.isEmpty()) { - val alternativeSigs = Commitments.alternativeFeerates.map { feerate -> - val alternativeSpec = spec.copy(feerate = feerate) - val (alternativeRemoteCommitTx, _) = Commitments.makeRemoteTxs( - channelParams = params, - commitParams = remoteCommitParams, - commitKeys = commitKeys, - commitTxNumber = remoteCommit.index + 1, - localFundingKey = fundingKey, - remoteFundingPubKey = remoteFundingPubkey, - commitmentInput = commitInput(fundingKey), - commitmentFormat = commitmentFormat, - spec = alternativeSpec - ) - val alternativeSig = alternativeRemoteCommitTx.sign(fundingKey, remoteFundingPubkey).sig - CommitSigTlv.AlternativeFeerateSig(feerate, alternativeSig) - } - add(CommitSigTlv.AlternativeFeerateSigs(alternativeSigs)) - } - if (batchSize > 1) { - add(CommitSigTlv.Batch(batchSize)) - } - partialSig?.let { add(CommitSigTlv.PartialSignatureWithNonce(it)) } - } - val commitSig = CommitSig(params.channelId, sig, htlcSigs.toList(), TlvStream(tlvs)) + val commitSig = CommitSig(params.channelId, sig, htlcSigs.toList(), batchSize) val commitment1 = copy(nextRemoteCommit = RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint)) return Either.Right(Pair(commitment1, commitSig)) } @@ -734,10 +696,6 @@ data class Commitments( addAll(active) }) -// fun addRemoteCommitNonce(fundingTxId: TxId, nonce: IndividualNonce?): Commitments = nonce?.let { this.copy(remoteCommitNonces = this.remoteCommitNonces + (fundingTxId to it)) } ?: this -// -// fun resetNonces(): Commitments = copy(remoteCommitNonces = emptyMap(), localCloseeNonce = null, remoteCloseeNonce = null, localCloserNonces = null) - fun channelKeys(keyManager: KeyManager): ChannelKeys = channelParams.localParams.channelKeys(keyManager) fun isMoreRecent(other: Commitments): Boolean { @@ -900,11 +858,14 @@ data class Commitments( fun sendCommit(channelKeys: ChannelKeys, remoteCommitNonces: Map, log: MDCLogger): Either> { val remoteNextPerCommitmentPoint = remoteNextCommitInfo.right ?: return Either.Left(CannotSignBeforeRevocation(channelId)) - val commitKeys = channelKeys.remoteCommitmentKeys(channelParams, remoteNextPerCommitmentPoint) if (!changes.localHasChanges()) return Either.Left(CannotSignWithoutChanges(channelId)) - val (active1, sigs) = active.map { - it.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size, remoteCommitNonces.get(it.fundingTxId), log) - .fold({ return Either.Left(it) }, { it }) + 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, log)) { + is Either.Left -> return Either.Left(res.left) + is Either.Right -> res.value + } }.unzip() val commitments1 = copy( active = active1, @@ -939,12 +900,16 @@ data class Commitments( // we will send our revocation preimage + our next revocation hash val localPerCommitmentSecret = channelKeys.commitmentSecret(localCommitIndex) val localNextPerCommitmentPoint = channelKeys.commitmentPoint(localCommitIndex + 2) - val localCommitNonces = active.filter { it.commitmentFormat == Transactions.CommitmentFormat.SimpleTaprootChannels }.map { - val localNonce = NonceGenerator.verificationNonce(it.fundingTxId, it.localFundingKey(channelKeys), it.remoteFundingPubkey, localCommitIndex + 2) - it.fundingTxId to localNonce.publicNonce + val localCommitNonces = active.mapNotNull { c -> + when (c.commitmentFormat) { + Transactions.CommitmentFormat.AnchorOutputs -> null + Transactions.CommitmentFormat.SimpleTaprootChannels -> { + val localNonce = NonceGenerator.verificationNonce(c.fundingTxId, c.localFundingKey(channelKeys), c.remoteFundingPubkey, localCommitIndex + 2) + c.fundingTxId to localNonce.publicNonce + } + } } - val tlvs: Set = if (localCommitNonces.isEmpty()) setOf() else setOf(RevokeAndAckTlv.NextLocalNonces(localCommitNonces)) - val revocation = RevokeAndAck(channelId, localPerCommitmentSecret, localNextPerCommitmentPoint, TlvStream(tlvs)) + val revocation = RevokeAndAck(channelId, localPerCommitmentSecret, localNextPerCommitmentPoint, localCommitNonces) val commitments1 = copy( active = active1, changes = changes.copy( @@ -1009,17 +974,6 @@ data class Commitments( return Either.Right(Pair(commitments1, actions.toList())) } - fun createShutdown(channelKeys: ChannelKeys, finalScriptPubKey: ByteVector): Pair = when (latest.commitmentFormat) { - is Transactions.CommitmentFormat.SimpleTaprootChannels -> { - // We create a fresh local closee nonce every time we send shutdown. - val localFundingPubKey = channelKeys.fundingKey(latest.fundingTxIndex).publicKey() - val localCloseeNonce = NonceGenerator.signingNonce(localFundingPubKey, latest.remoteFundingPubkey, latest.fundingTxId) - localCloseeNonce to Shutdown(channelId, finalScriptPubKey, TlvStream(ShutdownTlv.ShutdownNonce(localCloseeNonce.publicNonce))) - } - - else -> null to Shutdown(channelId, finalScriptPubKey) - } - private fun ChannelContext.updateFundingStatus(fundingTxId: TxId, updateMethod: (Commitment, Long) -> Commitment): Either> { return when (val c = all.find { it.fundingTxId == fundingTxId }) { is Commitment -> { diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Helpers.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Helpers.kt index 522952755..215e70a81 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Helpers.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/Helpers.kt @@ -16,11 +16,7 @@ import fr.acinq.lightning.blockchain.fee.FeerateTolerance import fr.acinq.lightning.blockchain.fee.OnChainFeerates import fr.acinq.lightning.channel.Helpers.Closing.inputsAlreadySpent import fr.acinq.lightning.channel.states.Channel -import fr.acinq.lightning.crypto.ChannelKeys -import fr.acinq.lightning.crypto.LocalCommitmentKeys -import fr.acinq.lightning.crypto.NonceGenerator -import fr.acinq.lightning.crypto.RemoteCommitmentKeys -import fr.acinq.lightning.crypto.ShaChain +import fr.acinq.lightning.crypto.* import fr.acinq.lightning.logging.LoggingContext import fr.acinq.lightning.transactions.* import fr.acinq.lightning.transactions.Transactions.commitTxFee @@ -36,7 +32,7 @@ object Helpers { // NB: we only accept channels from peers who support explicit channel type negotiation. val channelType = open.channelType ?: return Either.Left(MissingChannelType(open.temporaryChannelId)) if (channelType is ChannelType.UnsupportedChannelType) { - return Either.Left(InvalidChannelType(open.temporaryChannelId, ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, channelType)) + return Either.Left(InvalidChannelType(open.temporaryChannelId, ChannelType.SupportedChannelType.SimpleTaprootChannels, channelType)) } // BOLT #2: if the chain_hash value, within the open_channel, message is set to a hash of a chain that is unknown to the receiver: @@ -285,6 +281,19 @@ object Helpers { fun isValidFinalScriptPubkey(scriptPubKey: ByteVector, allowAnySegwit: Boolean, allowOpReturn: Boolean): Boolean = isValidFinalScriptPubkey(scriptPubKey.toByteArray(), allowAnySegwit, allowOpReturn) + fun createShutdown(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptOverride: ByteVector? = null): Pair { + val localScript = localScriptOverride ?: commitment.channelParams.localParams.defaultFinalScriptPubKey + return when (commitment.commitmentFormat) { + Transactions.CommitmentFormat.SimpleTaprootChannels -> { + // We create a fresh local closee nonce every time we send shutdown. + val localFundingPubKey = channelKeys.fundingKey(commitment.fundingTxIndex).publicKey() + val localCloseeNonce = NonceGenerator.signingNonce(localFundingPubKey, commitment.remoteFundingPubkey, commitment.fundingTxId) + Pair(localCloseeNonce, Shutdown(commitment.channelId, localScript, localCloseeNonce.publicNonce)) + } + Transactions.CommitmentFormat.AnchorOutputs -> Pair(null, Shutdown(commitment.channelId, localScript)) + } + } + /** We are the closer: we sign closing transactions for which we pay the fees. */ fun makeClosingTxs( channelKeys: ChannelKeys, @@ -316,31 +325,33 @@ object Helpers { } val localFundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) val localNonces = Transactions.CloserNonces.generate(localFundingKey.publicKey(), commitment.remoteFundingPubkey, commitment.fundingTxId) - val tlvs = when (commitment.commitmentFormat) { - Transactions.CommitmentFormat.SimpleTaprootChannels -> { - // If we cannot create our partial signature for one of our closing txs, we just skip it. - // It will only happen if our peer sent an invalid nonce, in which case we cannot do anything anyway - // apart from eventually force-closing. - fun localSig(tx: Transactions.ClosingTx, localNonce: Transactions.LocalNonce): ChannelSpendSignature.PartialSignatureWithNonce? = - tx.partialSign(localFundingKey, commitment.remoteFundingPubkey, mapOf(), localNonce, listOf(localNonce.publicNonce, remoteNonce!!)).right - - TlvStream( - setOfNotNull( - closingTxs.localAndRemote?.let { localSig(it, localNonces.localAndRemote)?.let { ClosingCompleteTlv.CloserAndCloseeOutputsPartialSignature(it) } }, - closingTxs.localOnly?.let { localSig(it, localNonces.localOnly)?.let { ClosingCompleteTlv.CloserOutputOnlyPartialSignature(it) } }, - closingTxs.remoteOnly?.let { localSig(it, localNonces.remoteOnly)?.let { ClosingCompleteTlv.CloseeOutputOnlyPartialSignature(it) } } - ) - ) - } - - else -> TlvStream( + Transactions.CommitmentFormat.AnchorOutputs -> TlvStream( setOfNotNull( closingTxs.localAndRemote?.let { tx -> ClosingCompleteTlv.CloserAndCloseeOutputs(tx.sign(localFundingKey, commitment.remoteFundingPubkey).sig) }, closingTxs.localOnly?.let { tx -> ClosingCompleteTlv.CloserOutputOnly(tx.sign(localFundingKey, commitment.remoteFundingPubkey).sig) }, closingTxs.remoteOnly?.let { tx -> ClosingCompleteTlv.CloseeOutputOnly(tx.sign(localFundingKey, commitment.remoteFundingPubkey).sig) }, ) ) + Transactions.CommitmentFormat.SimpleTaprootChannels -> when (remoteNonce) { + null -> return Either.Left(MissingClosingNonce(commitment.channelId)) + else -> { + // If we cannot create our partial signature for one of our closing txs, we just skip it. + // It will only happen if our peer sent an invalid nonce, in which case we cannot do anything anyway + // apart from eventually force-closing. + fun localSig(tx: Transactions.ClosingTx, localNonce: Transactions.LocalNonce): ChannelSpendSignature.PartialSignatureWithNonce? { + return tx.partialSign(localFundingKey, commitment.remoteFundingPubkey, mapOf(), localNonce, listOf(localNonce.publicNonce, remoteNonce)).right + } + + TlvStream( + setOfNotNull( + closingTxs.localAndRemote?.let { tx -> localSig(tx, localNonces.localAndRemote)?.let { ClosingCompleteTlv.CloserAndCloseeOutputsPartialSignature(it) } }, + closingTxs.localOnly?.let { tx -> localSig(tx, localNonces.localOnly)?.let { ClosingCompleteTlv.CloserOutputOnlyPartialSignature(it) } }, + closingTxs.remoteOnly?.let { tx -> localSig(tx, localNonces.remoteOnly)?.let { ClosingCompleteTlv.CloseeOutputOnlyPartialSignature(it) } } + ) + ) + } + } } val closingComplete = ClosingComplete(commitment.channelId, localScriptPubkey, remoteScriptPubkey, closingFee.fee, lockTime, tlvs) return Either.Right(Triple(closingTxs, closingComplete, localNonces)) @@ -362,12 +373,13 @@ object Helpers { ): Either> { val closingFee = Transactions.ClosingTxFee.PaidByThem(closingComplete.fees) val closingTxs = Transactions.makeClosingTxs(commitment.commitInput(channelKeys), commitment.localCommit.spec, closingFee, closingComplete.lockTime, localScriptPubkey, remoteScriptPubkey) - + // If our output isn't dust, they must provide a signature for a transaction that includes it. + // Note that we're the closee, so we look for signatures including the closee output. when (commitment.commitmentFormat) { - is Transactions.CommitmentFormat.SimpleTaprootChannels -> { - if (localNonce == null) return Either.Left(MissingClosingNonce(commitment.channelId)) - // If our output isn't dust, they must provide a signature for a transaction that includes it. - // Note that we're the closee, so we look for signatures including the closee output. + Transactions.CommitmentFormat.SimpleTaprootChannels -> { + if (localNonce == null) { + return Either.Left(MissingClosingNonce(commitment.channelId)) + } if (closingTxs.localAndRemote != null && closingTxs.localOnly != null && closingComplete.closerAndCloseeOutputsPartialSig == null && closingComplete.closeeOutputOnlyPartialSig == null) { return Either.Left(MissingCloseSignature(commitment.channelId)) } @@ -388,28 +400,22 @@ object Helpers { else -> { val (closingTx, remoteSig, sigToTlv) = preferred val localFundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) - - val signedClosingTx = closingTx.partialSign(localFundingKey, commitment.remoteFundingPubkey, mapOf(), localNonce, listOf(localNonce.publicNonce, remoteSig.nonce)) - .flatMap { localSig -> - closingTx.aggregateSigs(localFundingKey.publicKey(), commitment.remoteFundingPubkey, localSig, remoteSig, mapOf()) - .map { closingTx.copy(tx = it) to localSig } - } - - when (signedClosingTx) { - is Either.Left -> Either.Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid)) - is Either.Right -> { - if (!signedClosingTx.value.first.validate(mapOf())) return Either.Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid)) - val nextLocalNonce = NonceGenerator.signingNonce(localFundingKey.publicKey(), commitment.remoteFundingPubkey, commitment.fundingTxId) - val tlvs = TlvStream(sigToTlv(signedClosingTx.value.second.partialSig), ClosingSigTlv.NextCloseeNonce(nextLocalNonce.publicNonce)) - Either.Right(Triple(signedClosingTx.value.first, ClosingSig(commitment.channelId, remoteScriptPubkey, localScriptPubkey, closingComplete.fees, closingComplete.lockTime, tlvs), nextLocalNonce)) - } + val localSig = closingTx.partialSign(localFundingKey, commitment.remoteFundingPubkey, mapOf(), localNonce, listOf(localNonce.publicNonce, remoteSig.nonce)).right + val signedTx = localSig?.let { closingTx.aggregateSigs(localFundingKey.publicKey(), commitment.remoteFundingPubkey, it, remoteSig, mapOf()).right } + if (localSig == null || signedTx == null) { + return Either.Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid)) + } + val signedClosingTx = closingTx.copy(tx = signedTx) + if (!signedClosingTx.validate(mapOf())) { + return Either.Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid)) } + val nextLocalNonce = NonceGenerator.signingNonce(localFundingKey.publicKey(), commitment.remoteFundingPubkey, commitment.fundingTxId) + val tlvs = TlvStream(sigToTlv(localSig.partialSig), ClosingSigTlv.NextCloseeNonce(nextLocalNonce.publicNonce)) + Either.Right(Triple(signedClosingTx, ClosingSig(commitment.channelId, remoteScriptPubkey, localScriptPubkey, closingComplete.fees, closingComplete.lockTime, tlvs), nextLocalNonce)) } } } - else -> { - // If our output isn't dust, they must provide a signature for a transaction that includes it. - // Note that we're the closee, so we look for signatures including the closee output. + Transactions.CommitmentFormat.AnchorOutputs -> { if (closingTxs.localAndRemote != null && closingTxs.localOnly != null && closingComplete.closerAndCloseeOutputsSig == null && closingComplete.closeeOutputOnlySig == null) { return Either.Left(MissingCloseSignature(commitment.channelId)) } @@ -472,7 +478,6 @@ object Helpers { else -> { val (closingTx, remoteSig) = preferred val localFundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) - when (remoteSig) { is ChannelSpendSignature.IndividualSignature -> { val localSig = closingTx.sign(localFundingKey, commitment.remoteFundingPubkey) @@ -484,10 +489,9 @@ object Helpers { Either.Right(signedClosingTx) } } - is ChannelSpendSignature.PartialSignatureWithNonce -> { - if (localNonces == null) return Either.Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid)) val localNonce = when { + localNonces == null -> return Either.Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid)) closingTx.tx.txOut.size == 2 -> localNonces.localAndRemote closingTx.toLocalOutput != null -> localNonces.localOnly else -> localNonces.remoteOnly @@ -495,13 +499,11 @@ object Helpers { val signedClosingTx = closingTx.partialSign(localFundingKey, commitment.remoteFundingPubkey, mapOf(), localNonce, listOf(localNonce.publicNonce, remoteSig.nonce)) .flatMap { closingTx.aggregateSigs(localFundingKey.publicKey(), commitment.remoteFundingPubkey, it, remoteSig, mapOf()) } .map { closingTx.copy(tx = it) } - - when (signedClosingTx) { - is Either.Left -> Either.Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid)) - is Either.Right -> { - if (!signedClosingTx.value.validate(mapOf())) return Either.Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid)) - signedClosingTx - } + .right ?: return Either.Left(InvalidCloseSignature(commitment.channelId, closingTx.tx.txid)) + if (!signedClosingTx.validate(mapOf())) { + Either.Left(InvalidCloseSignature(commitment.channelId, signedClosingTx.tx.txid)) + } else { + Either.Right(signedClosingTx) } } } 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 e559ee7ef..bf2fb5ff2 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 @@ -5,10 +5,7 @@ import fr.acinq.bitcoin.Script.tail import fr.acinq.bitcoin.crypto.musig2.IndividualNonce import fr.acinq.bitcoin.crypto.musig2.Musig2 import fr.acinq.bitcoin.crypto.musig2.SecretNonce -import fr.acinq.bitcoin.utils.Either -import fr.acinq.bitcoin.utils.Try -import fr.acinq.bitcoin.utils.getOrDefault -import fr.acinq.bitcoin.utils.runTrying +import fr.acinq.bitcoin.utils.* import fr.acinq.lightning.Lightning.randomBytes32 import fr.acinq.lightning.MilliSatoshi import fr.acinq.lightning.blockchain.electrum.WalletState @@ -43,15 +40,19 @@ data class SharedFundingInput( val weight: Int = commitmentFormat.fundingInputWeight - fun sign(channelKeys: ChannelKeys, tx: Transaction, localNonce: Transactions.LocalNonce?, remoteNonce: IndividualNonce?, spentUtxos: Map): ChannelSpendSignature { + fun sign(channelId: ByteVector32, channelKeys: ChannelKeys, tx: Transaction, localNonce: Transactions.LocalNonce?, remoteNonce: IndividualNonce?, spentUtxos: Map): Either { val fundingKey = channelKeys.fundingKey(fundingTxIndex) val spliceTx = Transactions.SpliceTx(info, tx) return when (commitmentFormat) { - is Transactions.CommitmentFormat.SimpleTaprootChannels -> - spliceTx.partialSign(fundingKey, remoteFundingPubkey, spentUtxos, localNonce!!, listOf(localNonce.publicNonce, remoteNonce!!)).right!! - - else -> - spliceTx.sign(fundingKey, remoteFundingPubkey, spentUtxos) + Transactions.CommitmentFormat.AnchorOutputs -> Either.Right(spliceTx.sign(fundingKey, remoteFundingPubkey, spentUtxos)) + Transactions.CommitmentFormat.SimpleTaprootChannels -> { + val localNonce = localNonce ?: return Either.Left(MissingFundingNonce(channelId, tx.txid)) + val remoteNonce = remoteNonce ?: return Either.Left(MissingFundingNonce(channelId, tx.txid)) + when (val psig = spliceTx.partialSign(fundingKey, remoteFundingPubkey, spentUtxos, localNonce, listOf(localNonce.publicNonce, remoteNonce))) { + is Either.Left -> Either.Left(InvalidFundingNonce(channelId, tx.txid)) + is Either.Right -> Either.Right(psig.value) + } + } } } } @@ -100,12 +101,12 @@ data class InteractiveTxParams( /** Amount of the new funding output, which is the sum of the shared input, if any, and both sides' contributions. */ val fundingAmount: Satoshi = (sharedInput?.info?.txOut?.amount ?: 0.sat) + localContribution + remoteContribution - // BOLT 2: MUST set `feerate` greater than or equal to 25/24 times the `feerate` of the previously constructed transaction, rounded down. val minNextFeerate: FeeratePerKw = targetFeerate * 25 / 24 - // BOLT 2: the initiator's serial IDs MUST use even values and the non-initiator odd values. val serialIdParity = if (isInitiator) 0 else 1 + // If we don't have a shared input, this isn't a splice: it is the initial channel funding transaction. + val fundingTxIndex = sharedInput?.let { it.fundingTxIndex + 1 } ?: 0 fun fundingPubkeyScript(channelKeys: ChannelKeys): ByteVector { val fundingTxIndex = sharedInput?.let { it.fundingTxIndex + 1 } ?: 0 @@ -479,13 +480,15 @@ data class SharedTransaction( return Transaction(2, inputs, outputs, lockTime) } - fun sign(session: InteractiveTxSession, keyManager: KeyManager, fundingParams: InteractiveTxParams, localParams: LocalChannelParams, remoteNodeId: PublicKey): PartiallySignedSharedTransaction { + fun sign(session: InteractiveTxSession, keyManager: KeyManager, fundingParams: InteractiveTxParams, remoteNodeId: PublicKey): Either { val unsignedTx = buildUnsignedTx() - val channelKeys = keyManager.channelKeys(localParams.fundingKeyPath) - val sharedSig = fundingParams.sharedInput?.sign(channelKeys, unsignedTx, session.localFundingNonce, session.txCompleteReceived?.fundingNonce, spentOutputs) + val sharedSig = when (val sig = fundingParams.sharedInput?.sign(session.fundingParams.channelId, session.channelKeys, unsignedTx, session.localFundingNonce, session.remoteFundingNonce, spentOutputs)) { + is Either.Left -> return Either.Left(sig.value) + is Either.Right -> sig.value + null -> null + } // NB: the order in this list must match the order of the transaction's inputs. val previousOutputs = unsignedTx.txIn.map { spentOutputs[it.outPoint]!! } - // Public nonces for all the musig2 swap-in inputs (local and remote). // We have verified that one nonce was provided for each input when receiving `tx_complete`. val remoteNonces: Map = when (session.txCompleteReceived) { @@ -495,7 +498,6 @@ data class SharedTransaction( .zip(session.txCompleteReceived.swapInNonces) .associate { it.first.serialId to it.second } } - // If we are swapping funds in, we provide our partial signatures to the corresponding inputs. val legacySwapUserSigs = unsignedTx.txIn.mapIndexed { i, txIn -> localInputs @@ -516,7 +518,6 @@ data class SharedTransaction( .getOrDefault(null) } }.filterNotNull() - // If the remote is swapping funds in, they'll need our partial signatures to finalize their witness. val legacySwapServerSigs = unsignedTx.txIn.mapIndexed { i, txIn -> remoteInputs @@ -543,8 +544,8 @@ data class SharedTransaction( .getOrDefault(null) } }.filterNotNull() - - return PartiallySignedSharedTransaction(this, TxSignatures(fundingParams.channelId, unsignedTx, listOf(), sharedSig, legacySwapUserSigs, legacySwapServerSigs, swapUserPartialSigs, swapServerPartialSigs)) + val txSigs = TxSignatures(fundingParams.channelId, unsignedTx, listOf(), sharedSig, legacySwapUserSigs, legacySwapServerSigs, swapUserPartialSigs, swapServerPartialSigs) + return Either.Right(PartiallySignedSharedTransaction(this, txSigs)) } } @@ -569,34 +570,28 @@ data class PartiallySignedSharedTransaction(override val tx: SharedTransaction, if (remoteSigs.swapInServerPartialSigs.size != tx.localInputs.filterIsInstance().size) return null if (remoteSigs.witnesses.size != tx.remoteOnlyInputs().size) return null if (remoteSigs.txId != localSigs.txId) return null - val sharedSigs = fundingParams.sharedInput?.let { - val localFundingPubkey = channelKeys.fundingKey(it.fundingTxIndex).publicKey() - val spliceTx = Transactions.SpliceTx(it.info, tx.buildUnsignedTx()) - val signedTx = when (it.commitmentFormat) { - is Transactions.CommitmentFormat.SimpleTaprootChannels -> { - val aggSig = spliceTx.aggregateSigs( - localFundingPubkey, - it.remoteFundingPubkey, - localSigs.previousFundingTxPartialSig ?: return null, - remoteSigs.previousFundingTxPartialSig ?: return null, - extraUtxos = tx.spentOutputs - ).right!! - aggSig - } - - else -> { - spliceTx.aggregateSigs( - localFundingPubkey, - it.remoteFundingPubkey, - localSigs.previousFundingTxSig?.let { ChannelSpendSignature.IndividualSignature(it) } ?: return null, - remoteSigs.previousFundingTxSig?.let { ChannelSpendSignature.IndividualSignature(it) } ?: return null - ) - } + val sharedSigs = fundingParams.sharedInput?.let { input -> + val localFundingPubkey = channelKeys.fundingKey(input.fundingTxIndex).publicKey() + val spliceTx = Transactions.SpliceTx(input.info, tx.buildUnsignedTx()) + val signedTx = when (input.commitmentFormat) { + Transactions.CommitmentFormat.AnchorOutputs -> spliceTx.aggregateSigs( + localFundingPubkey, + input.remoteFundingPubkey, + localSigs.previousFundingTxSig?.let { ChannelSpendSignature.IndividualSignature(it) } ?: return null, + remoteSigs.previousFundingTxSig?.let { ChannelSpendSignature.IndividualSignature(it) } ?: return null + ) + Transactions.CommitmentFormat.SimpleTaprootChannels -> spliceTx.aggregateSigs( + localFundingPubkey, + input.remoteFundingPubkey, + localSigs.previousFundingTxPartialSig ?: return null, + remoteSigs.previousFundingTxPartialSig ?: return null, + extraUtxos = tx.spentOutputs + ).right ?: return null } signedTx.txIn[spliceTx.inputIndex].witness } val fullySignedTx = FullySignedSharedTransaction(tx, localSigs, remoteSigs, sharedSigs) - return when (val check = runTrying { fullySignedTx.signedTx.correctlySpends(tx.spentOutputs, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) }) { + return when (runTrying { fullySignedTx.signedTx.correctlySpends(tx.spentOutputs, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) }) { is Try.Success -> fullySignedTx is Try.Failure -> null } @@ -682,6 +677,7 @@ data class InteractiveTxSession( val channelKeys: ChannelKeys, val swapInKeys: SwapInOnChainKeys, val fundingParams: InteractiveTxParams, + val localCommitIndex: Long, val previousFunding: SharedFundingInputBalances, val toSend: List>, val previousTxs: List = listOf(), @@ -695,17 +691,7 @@ data class InteractiveTxSession( val inputsReceivedCount: Int = 0, val outputsReceivedCount: Int = 0, val swapInSecretNonces: Map> = mapOf(), - val commitTxIndex: Long, - val fundingTxIndex: Long, - // README: this is a field because we want to preserve this value when we use .copy() and it would not be the case if it was a val defined in the class body - val localFundingNonce: Transactions.LocalNonce? = when (fundingParams.sharedInput?.commitmentFormat) { - Transactions.CommitmentFormat.SimpleTaprootChannels -> { - val previousFundingKey = channelKeys.fundingKey(fundingParams.sharedInput.fundingTxIndex).publicKey() - NonceGenerator.signingNonce(previousFundingKey, fundingParams.sharedInput.remoteFundingPubkey, fundingParams.sharedInput.info.outPoint.txid) - } - - else -> null - } + val localFundingNonce: Transactions.LocalNonce? = null, ) { // Example flow: @@ -727,29 +713,34 @@ data class InteractiveTxSession( channelKeys: ChannelKeys, swapInKeys: SwapInOnChainKeys, fundingParams: InteractiveTxParams, + localCommitIndex: Long, previousLocalBalance: MilliSatoshi, previousRemoteBalance: MilliSatoshi, localHtlcs: Set, fundingContributions: FundingContributions, previousTxs: List = listOf(), - commitTxIndex: Long, - fundingTxIndex: Long, ) : this( remoteNodeId, channelKeys, swapInKeys, 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) }, previousTxs, localHtlcs, - commitTxIndex = commitTxIndex, - fundingTxIndex = fundingTxIndex + localFundingNonce = fundingParams.sharedInput?.let { + // If we're splicing an existing channel, we create a random local nonce for this interactive-tx session. + val previousFundingKey = channelKeys.fundingKey(it.fundingTxIndex).publicKey() + NonceGenerator.signingNonce(previousFundingKey, it.remoteFundingPubkey, it.info.outPoint.txid) + } ) val isComplete: Boolean = txCompleteSent != null && txCompleteReceived != null - - val localFundingKey = channelKeys.fundingKey(fundingTxIndex) + val localFundingKey: PrivateKey = channelKeys.fundingKey(fundingParams.fundingTxIndex) + val remoteFundingNonce: IndividualNonce? = txCompleteReceived?.fundingNonce + val currentRemoteCommitNonce: IndividualNonce? = txCompleteReceived?.commitNonces?.commitNonce + val nextRemoteCommitNonce: IndividualNonce? = txCompleteReceived?.commitNonces?.nextCommitNonce fun send(): Pair { return when (val msg = toSend.firstOrNull()) { @@ -761,31 +752,37 @@ data class InteractiveTxSession( .sorted() // We generate secret nonces whenever we send and receive tx_add_input, so we know they exist in the map. .map { serialId -> swapInSecretNonces[serialId]!!.second } - val commitNonces = when (this.fundingParams.commitmentFormat) { + val txComplete = when (fundingParams.commitmentFormat) { + Transactions.CommitmentFormat.AnchorOutputs -> TxComplete(fundingParams.channelId, TlvStream(TxCompleteTlv.SwapInNonces(swapInNonces))) Transactions.CommitmentFormat.SimpleTaprootChannels -> { - val fundingTxId = runTrying { - val sharedInputs = localInputs.filterIsInstance() + remoteInputs.filterIsInstance() - val localOnlyInputs = localInputs.filterIsInstance() - val remoteOnlyInputs = remoteInputs.filterIsInstance() - val sharedOutputs = localOutputs.filterIsInstance() + remoteOutputs.filterIsInstance() - val localOnlyOutputs = localOutputs.filterIsInstance() - val remoteOnlyOutputs = remoteOutputs.filterIsInstance() - val sharedOutput = sharedOutputs.first() - val sharedInput = fundingParams.sharedInput?.let { - sharedInputs.first() - } - val sharedTx = SharedTransaction(sharedInput, sharedOutput, localOnlyInputs, remoteOnlyInputs, localOnlyOutputs, remoteOnlyOutputs, fundingParams.lockTime) - sharedTx.buildUnsignedTx().txid - }.getOrElse { TxId(ByteVector32.Zeroes) } - TxCompleteTlv.CommitNonces( - NonceGenerator.verificationNonce(fundingTxId, localFundingKey, fundingParams.remoteFundingPubkey, this.commitTxIndex).publicNonce, - NonceGenerator.verificationNonce(fundingTxId, localFundingKey, fundingParams.remoteFundingPubkey, this.commitTxIndex + 1).publicNonce, + // We don't have more inputs or outputs to contribute to the shared transaction. + // If our peer doesn't have anything more to contribute either, we will proceed to exchange commitment + // signatures spending this shared transaction, so we need to provide nonces to create those signatures. + // If our peer adds more inputs or outputs, we will simply send a new tx_complete message in response with + // nonces for the updated shared transaction. + // Note that we don't validate the shared transaction at that point: this will be done later once we've + // both sent tx_complete. If the shared transaction is invalid, we will abort and discard our nonces. + val fundingTxId = Transaction( + version = 2, + txIn = (localInputs.filterIsInstance() + remoteInputs.filterIsInstance()) + .map { it.serialId to TxIn(it.outPoint, it.sequence.toLong()) } + .sortedBy { it.first } + .map { it.second }, + txOut = (localOutputs.filterIsInstance() + remoteOutputs.filterIsInstance()) + .map { it.serialId to TxOut(it.amount, it.pubkeyScript) } + .sortedBy { it.first } + .map { it.second }, + lockTime = fundingParams.lockTime + ).txid + TxComplete( + channelId = fundingParams.channelId, + commitNonce = NonceGenerator.verificationNonce(fundingTxId, localFundingKey, fundingParams.remoteFundingPubkey, localCommitIndex).publicNonce, + nextCommitNonce = NonceGenerator.verificationNonce(fundingTxId, localFundingKey, fundingParams.remoteFundingPubkey, localCommitIndex + 1).publicNonce, + fundingNonce = localFundingNonce?.publicNonce, + swapInNonces = swapInNonces, ) } - - else -> null } - val txComplete = TxComplete(fundingParams.channelId, commitNonces, localFundingNonce?.publicNonce, swapInNonces) val next = copy(txCompleteSent = txComplete) if (next.isComplete) { Pair(next, next.validateTx(txComplete)) @@ -815,10 +812,8 @@ data class InteractiveTxSession( val secretNonce = Musig2.generateNonce(randomBytes32(), Either.Left(swapInKeys.userPrivateKey), listOf(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey), null, null) swapInSecretNonces + (inputOutgoing.serialId to secretNonce) } - else -> swapInSecretNonces } - else -> swapInSecretNonces } val next = copy(toSend = toSend.tail(), localInputs = localInputs + msg.value, txCompleteSent = null, swapInSecretNonces = nextSecretNonces) @@ -851,7 +846,6 @@ data class InteractiveTxSession( if (expectedSharedOutpoint != receivedSharedOutpoint) return Either.Left(InteractiveTxSessionAction.PreviousTxMissing(message.channelId, message.serialId)) InteractiveTxInput.Shared(message.serialId, receivedSharedOutpoint, fundingParams.sharedInput.info.txOut.publicKeyScript, message.sequence, previousFunding.toLocal, previousFunding.toRemote, previousFunding.toHtlcs) } - else -> { if (message.previousTx.txOut.size <= message.previousTxOutput) { return Either.Left(InteractiveTxSessionAction.InputOutOfBounds(message.channelId, message.serialId, message.previousTx.txid, message.previousTxOutput)) @@ -867,30 +861,25 @@ data class InteractiveTxSession( val outpoint = OutPoint(message.previousTx, message.previousTxOutput) val txOut = message.previousTx.txOut[message.previousTxOutput.toInt()] when { - message.swapInParams != null -> { - InteractiveTxInput.RemoteSwapIn( - message.serialId, - outpoint, - txOut, - message.sequence, - message.swapInParams.userKey, - message.swapInParams.serverKey, - message.swapInParams.userRefundKey, - message.swapInParams.refundDelay - ) - } - - message.swapInParamsLegacy != null -> { - InteractiveTxInput.RemoteLegacySwapIn( - message.serialId, - outpoint, - txOut, - message.sequence, - message.swapInParamsLegacy.userKey, - message.swapInParamsLegacy.serverKey, - message.swapInParamsLegacy.refundDelay - ) - } + message.swapInParams != null -> InteractiveTxInput.RemoteSwapIn( + message.serialId, + outpoint, + txOut, + message.sequence, + message.swapInParams.userKey, + message.swapInParams.serverKey, + message.swapInParams.userRefundKey, + message.swapInParams.refundDelay + ) + message.swapInParamsLegacy != null -> InteractiveTxInput.RemoteLegacySwapIn( + message.serialId, + outpoint, + txOut, + message.sequence, + message.swapInParamsLegacy.userKey, + message.swapInParamsLegacy.serverKey, + message.swapInParamsLegacy.refundDelay + ) else -> InteractiveTxInput.RemoteOnly(message.serialId, outpoint, txOut, message.sequence) } } @@ -908,10 +897,8 @@ data class InteractiveTxSession( val secretNonce = Musig2.generateNonce(randomBytes32(), Either.Right(input.serverKey), listOf(input.userKey, input.serverKey), null, null) swapInSecretNonces + (input.serialId to secretNonce) } - else -> swapInSecretNonces } - else -> swapInSecretNonces } val session1 = this.copy(remoteInputs = remoteInputs + input, inputsReceivedCount = inputsReceivedCount + 1, txCompleteReceived = null, swapInSecretNonces = secretNonces1) @@ -1098,13 +1085,12 @@ sealed class InteractiveTxSigningSessionAction { */ data class InteractiveTxSigningSession( val fundingParams: InteractiveTxParams, - val fundingTxIndex: Long, val fundingTx: PartiallySignedSharedTransaction, val localCommitParams: CommitParams, val localCommit: Either, val remoteCommitParams: CommitParams, val remoteCommit: RemoteCommit, - val nextRemoteNonce: IndividualNonce? + val nextRemoteCommitNonce: IndividualNonce? ) { // Example flow: // +-------+ +-------+ @@ -1114,16 +1100,14 @@ data class InteractiveTxSigningSession( // | |<------- tx_signatures ------| | // +-------+ +-------+ 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 reconnectNextLocalCommitmentNumber = when (localCommit) { + val nextLocalCommitmentNumber = when (localCommit) { is Either.Left -> localCommit.value.index is Either.Right -> localCommit.value.index + 1 } - fun localFundingKey(channelKeys: ChannelKeys): PrivateKey = channelKeys.fundingKey(fundingTxIndex) + fun localFundingKey(channelKeys: ChannelKeys): PrivateKey = channelKeys.fundingKey(fundingParams.fundingTxIndex) fun commitInput(fundingKey: PrivateKey): Transactions.InputInfo { val fundingScript = Transactions.makeFundingScript(fundingKey.publicKey(), fundingParams.remoteFundingPubkey, fundingParams.commitmentFormat).pubkeyScript @@ -1167,7 +1151,7 @@ data class InteractiveTxSigningSession( if (shouldSignFirst(fundingParams.isInitiator, channelParams, fundingTx.tx)) { val fundingStatus = LocalFundingStatus.UnconfirmedFundingTx(fundingTx, fundingParams, currentBlockHeight) val commitment = Commitment( - fundingTxIndex, + fundingParams.fundingTxIndex, fundingInput.outPoint, fundingParams.fundingAmount, fundingParams.remoteFundingPubkey, @@ -1180,7 +1164,7 @@ data class InteractiveTxSigningSession( remoteCommit, nextRemoteCommit = null ) - val action = InteractiveTxSigningSessionAction.SendTxSigs(fundingStatus, commitment, fundingTx.localSigs, this.nextRemoteNonce) + val action = InteractiveTxSigningSessionAction.SendTxSigs(fundingStatus, commitment, fundingTx.localSigs, nextRemoteCommitNonce) Pair(this.copy(localCommit = Either.Right(signedLocalCommit.value)), action) } else { Pair(this.copy(localCommit = Either.Right(signedLocalCommit.value)), InteractiveTxSigningSessionAction.WaitForTxSigs) @@ -1201,7 +1185,7 @@ data class InteractiveTxSigningSession( val fundingInput = commitInput(channelKeys) val fundingStatus = LocalFundingStatus.UnconfirmedFundingTx(fullySignedTx, fundingParams, currentBlockHeight) val commitment = Commitment( - fundingTxIndex, + fundingParams.fundingTxIndex, fundingInput.outPoint, fundingParams.fundingAmount, fundingParams.remoteFundingPubkey, @@ -1214,7 +1198,7 @@ data class InteractiveTxSigningSession( remoteCommit, nextRemoteCommit = null ) - Either.Right(InteractiveTxSigningSessionAction.SendTxSigs(fundingStatus, commitment, fundingTx.localSigs, this.nextRemoteNonce)) + Either.Right(InteractiveTxSigningSessionAction.SendTxSigs(fundingStatus, commitment, fundingTx.localSigs, nextRemoteCommitNonce)) } } } @@ -1231,7 +1215,6 @@ data class InteractiveTxSigningSession( localCommitParams: CommitParams, remoteCommitParams: CommitParams, fundingParams: InteractiveTxParams, - fundingTxIndex: Long, sharedTx: SharedTransaction, liquidityPurchase: LiquidityAds.Purchase?, localCommitmentIndex: Long, @@ -1240,12 +1223,11 @@ data class InteractiveTxSigningSession( remotePerCommitmentPoint: PublicKey, localHtlcs: Set ): Either> { - val channelKeys = channelParams.localParams.channelKeys(keyManager) - val fundingKey = channelKeys.fundingKey(fundingTxIndex) - val localCommitKeys = channelKeys.localCommitmentKeys(channelParams, localCommitmentIndex) - val remoteCommitKeys = channelKeys.remoteCommitmentKeys(channelParams, remotePerCommitmentPoint) + val fundingKey = session.localFundingKey + val localCommitKeys = session.channelKeys.localCommitmentKeys(channelParams, localCommitmentIndex) + val remoteCommitKeys = session.channelKeys.remoteCommitmentKeys(channelParams, remotePerCommitmentPoint) val unsignedTx = sharedTx.buildUnsignedTx() - val sharedOutputIndex = unsignedTx.txOut.indexOfFirst { it.publicKeyScript == fundingParams.fundingPubkeyScript(channelKeys) }.toLong() + val sharedOutputIndex = unsignedTx.txOut.indexOfFirst { it.publicKeyScript == fundingParams.fundingPubkeyScript(session.channelKeys) }.toLong() val liquidityFees = fundingParams.liquidityFees(liquidityPurchase) return Helpers.Funding.makeCommitTxs( channelParams = channelParams, @@ -1265,59 +1247,35 @@ data class InteractiveTxSigningSession( remoteFundingPubkey = fundingParams.remoteFundingPubkey, localCommitKeys = localCommitKeys, remoteCommitKeys = remoteCommitKeys, - ).map { firstCommitTx -> - val localSigOfRemoteCommitTx = firstCommitTx.remoteCommitTx.sign(fundingKey, fundingParams.remoteFundingPubkey) - val localPartialSigOfRemoteCommitTx = when (fundingParams.commitmentFormat) { + ).flatMap { firstCommitTx -> + val localSigOfRemoteCommitTx = when (fundingParams.commitmentFormat) { + Transactions.CommitmentFormat.AnchorOutputs -> firstCommitTx.remoteCommitTx.sign(fundingKey, fundingParams.remoteFundingPubkey) Transactions.CommitmentFormat.SimpleTaprootChannels -> { - val remoteNonce = session.txCompleteReceived?.commitNonces?.commitNonce ?: return Either.Left(MissingCommitNonce(channelParams.channelId, unsignedTx.txid, localCommitmentIndex)) + val remoteNonce = session.currentRemoteCommitNonce ?: return Either.Left(MissingCommitNonce(channelParams.channelId, unsignedTx.txid, remoteCommitmentIndex)) val localNonce = NonceGenerator.signingNonce(fundingKey.publicKey(), fundingParams.remoteFundingPubkey, unsignedTx.txid) - val psig = firstCommitTx.remoteCommitTx.partialSign(fundingKey, fundingParams.remoteFundingPubkey, mapOf(), localNonce, listOf(localNonce.publicNonce, remoteNonce)) - when (psig) { - is Either.Left -> return Either.Left(InvalidCommitNonce(channelParams.channelId, unsignedTx.txid, localCommitmentIndex)) - is Either.Right -> CommitSigTlv.PartialSignatureWithNonce(psig.value) + when (val psig = firstCommitTx.remoteCommitTx.partialSign(fundingKey, fundingParams.remoteFundingPubkey, mapOf(), localNonce, listOf(localNonce.publicNonce, remoteNonce))) { + is Either.Left -> return Either.Left(InvalidCommitNonce(channelParams.channelId, unsignedTx.txid, remoteCommitmentIndex)) + is Either.Right -> psig.value } } - - else -> null } val localSigsOfRemoteHtlcTxs = firstCommitTx.remoteHtlcTxs.map { it.localSig(remoteCommitKeys) } - val tlvs = if (firstCommitTx.remoteHtlcTxs.isEmpty()) { - val commitSigTlvs = Commitments.alternativeFeerates.map { feerate -> - val alternativeSpec = firstCommitTx.remoteSpec.copy(feerate = feerate) - val (alternativeRemoteCommitTx, _) = Commitments.makeRemoteTxs( - channelParams = channelParams, - commitParams = remoteCommitParams, - commitKeys = remoteCommitKeys, - commitTxNumber = remoteCommitmentIndex, - localFundingKey = fundingKey, - remoteFundingPubKey = fundingParams.remoteFundingPubkey, - commitmentInput = firstCommitTx.remoteCommitTx.input, - commitmentFormat = fundingParams.commitmentFormat, - spec = alternativeSpec - ) - val sig = alternativeRemoteCommitTx.sign(fundingKey, fundingParams.remoteFundingPubkey).sig - CommitSigTlv.AlternativeFeerateSig(feerate, sig) - } - TlvStream(setOfNotNull(CommitSigTlv.AlternativeFeerateSigs(commitSigTlvs), localPartialSigOfRemoteCommitTx)) - } else { - TlvStream(setOfNotNull(localPartialSigOfRemoteCommitTx)) - } - val commitSig = CommitSig(channelParams.channelId, localSigOfRemoteCommitTx, localSigsOfRemoteHtlcTxs, tlvs) + val commitSig = CommitSig(channelParams.channelId, localSigOfRemoteCommitTx, localSigsOfRemoteHtlcTxs, batchSize = 1) // 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) - val signedFundingTx = sharedTx.sign(session, keyManager, fundingParams, channelParams.localParams, channelParams.remoteParams.nodeId) - val signingSession = InteractiveTxSigningSession( - fundingParams, - fundingTxIndex, - signedFundingTx, - localCommitParams, - Either.Left(unsignedLocalCommit), - remoteCommitParams, - remoteCommit, - session.txCompleteReceived?.commitNonces?.nextCommitNonce - ) - Pair(signingSession, commitSig) + sharedTx.sign(session, keyManager, fundingParams, channelParams.remoteParams.nodeId).map { signedFundingTx -> + val signingSession = InteractiveTxSigningSession( + fundingParams, + signedFundingTx, + localCommitParams, + Either.Left(unsignedLocalCommit), + remoteCommitParams, + remoteCommit, + session.nextRemoteCommitNonce + ) + Pair(signingSession, commitSig) + } } } 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 a963f0c7a..d3538438e 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 @@ -6,6 +6,7 @@ import fr.acinq.bitcoin.utils.Either import fr.acinq.lightning.CltvExpiryDelta import fr.acinq.lightning.NodeParams import fr.acinq.lightning.SensitiveTaskEvents +import fr.acinq.lightning.ShortChannelId import fr.acinq.lightning.blockchain.WatchConfirmed import fr.acinq.lightning.blockchain.WatchConfirmedTriggered import fr.acinq.lightning.blockchain.WatchSpent @@ -21,7 +22,11 @@ import fr.acinq.lightning.logging.MDCLogger import fr.acinq.lightning.transactions.Transactions import fr.acinq.lightning.utils.msat import fr.acinq.lightning.utils.sat -import fr.acinq.lightning.wire.* +import fr.acinq.lightning.wire.ChannelReady +import fr.acinq.lightning.wire.ChannelReestablish +import fr.acinq.lightning.wire.ChannelUpdate +import fr.acinq.lightning.wire.Error +import fr.acinq.lightning.wire.Shutdown import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first @@ -294,43 +299,43 @@ sealed class PersistedChannelState : ChannelState() { internal fun ChannelContext.createChannelReestablish(): ChannelReestablish = when (val state = this@PersistedChannelState) { is WaitForFundingSigned -> { val myFirstPerCommitmentPoint = channelKeys().commitmentPoint(0) - val nonceTlvs = when (state.signingSession.fundingParams.commitmentFormat) { - is Transactions.CommitmentFormat.SimpleTaprootChannels -> { + val nextFundingTxId = state.signingSession.fundingTxId + val (currentCommitNonce, nextCommitNonce) = when (state.signingSession.fundingParams.commitmentFormat) { + Transactions.CommitmentFormat.AnchorOutputs -> Pair(null, null) + Transactions.CommitmentFormat.SimpleTaprootChannels -> { val localFundingKey = channelKeys().fundingKey(0) val remoteFundingPubKey = state.signingSession.fundingParams.remoteFundingPubkey val currentCommitNonce = when (state.signingSession.localCommit) { - is Either.Left -> NonceGenerator.verificationNonce(state.signingSession.fundingTx.txId, localFundingKey, remoteFundingPubKey, 0) + is Either.Left -> NonceGenerator.verificationNonce(nextFundingTxId, localFundingKey, remoteFundingPubKey, 0) is Either.Right -> null } - val nextCommitNonce = NonceGenerator.verificationNonce(state.signingSession.fundingTx.txId, localFundingKey, remoteFundingPubKey, 1) - setOfNotNull( - currentCommitNonce?.let { ChannelReestablishTlv.CurrentCommitNonce(it.publicNonce) }, - ChannelReestablishTlv.NextLocalNonces(listOf(state.signingSession.fundingTx.txId to nextCommitNonce.publicNonce)) - ) + val nextCommitNonce = NonceGenerator.verificationNonce(nextFundingTxId, localFundingKey, remoteFundingPubKey, 1) + Pair(currentCommitNonce?.publicNonce, nextCommitNonce.publicNonce) } - - else -> setOf() } ChannelReestablish( channelId = channelId, - nextLocalCommitmentNumber = state.signingSession.reconnectNextLocalCommitmentNumber, + nextLocalCommitmentNumber = state.signingSession.nextLocalCommitmentNumber, nextRemoteRevocationNumber = 0, yourLastCommitmentSecret = PrivateKey(ByteVector32.Zeroes), myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint, - TlvStream(nonceTlvs + ChannelReestablishTlv.NextFunding(state.signingSession.fundingTx.txId)) + nextCommitNonces = nextCommitNonce?.let { listOf(nextFundingTxId to it) } ?: listOf(), + nextFundingTxId = nextFundingTxId, + currentCommitNonce = currentCommitNonce ) } is ChannelStateWithCommitments -> { + val channelKeys = channelKeys() val yourLastPerCommitmentSecret = state.commitments.remotePerCommitmentSecrets.lastIndex?.let { state.commitments.remotePerCommitmentSecrets.getHash(it) } ?: ByteVector32.Zeroes - val myCurrentPerCommitmentPoint = channelKeys().commitmentPoint(state.commitments.localCommitIndex) + 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.reconnectNextLocalCommitmentNumber + is RbfStatus.WaitingForSigs -> state.rbfStatus.session.nextLocalCommitmentNumber else -> state.commitments.localCommitIndex + 1 } is Normal -> when (state.spliceStatus) { - is SpliceStatus.WaitingForSigs -> state.spliceStatus.session.reconnectNextLocalCommitmentNumber + is SpliceStatus.WaitingForSigs -> state.spliceStatus.session.nextLocalCommitmentNumber else -> state.commitments.localCommitIndex + 1 } else -> state.commitments.localCommitIndex + 1 @@ -342,45 +347,42 @@ sealed class PersistedChannelState : ChannelState() { else -> null } // We send our verification nonces for all active commitments. - val nextCommitNonces = state.commitments.active.filter { - when (it.commitmentFormat) { - is Transactions.CommitmentFormat.SimpleTaprootChannels -> true - else -> false + val nextCommitNonces = state.commitments.active.mapNotNull { c -> + when (c.commitmentFormat) { + Transactions.CommitmentFormat.AnchorOutputs -> null + Transactions.CommitmentFormat.SimpleTaprootChannels -> { + val localFundingKey = channelKeys.fundingKey(c.fundingTxIndex) + val localCommitNonce = NonceGenerator.verificationNonce(c.fundingTxId, localFundingKey, c.remoteFundingPubkey, c.localCommit.index + 1) + c.fundingTxId to localCommitNonce.publicNonce + } } - }.map { - val localFundingKey = channelKeys().fundingKey(it.fundingTxIndex) - it.fundingTxId to NonceGenerator.verificationNonce(it.fundingTxId, localFundingKey, it.remoteFundingPubkey, state.commitments.localCommitIndex + 1).publicNonce } - - val (interactiveTxCurrentCommitNonce, interactiveTxNextCommitNonce) = when { - state is WaitForFundingConfirmed && state.rbfStatus is RbfStatus.WaitingForSigs && state.rbfStatus.session.fundingParams.commitmentFormat is Transactions.CommitmentFormat.SimpleTaprootChannels -> { - val nextCommitNonce = listOf(state.rbfStatus.session.fundingTx.txId to state.rbfStatus.session.nextCommitNonce(channelKeys()).publicNonce) - Pair(state.rbfStatus.session.currentCommitNonce(channelKeys())?.publicNonce, nextCommitNonce) + // If an interactive-tx session hasn't been fully signed, we also need to include the corresponding nonces. + val (interactiveTxCurrentCommitNonce, interactiveTxNextCommitNonce) = run { + val signingSession = when { + state is WaitForFundingConfirmed && state.rbfStatus is RbfStatus.WaitingForSigs -> state.rbfStatus.session + state is Normal && state.spliceStatus is SpliceStatus.WaitingForSigs -> state.spliceStatus.session + else -> null } - - state is Normal && state.spliceStatus is SpliceStatus.WaitingForSigs && state.spliceStatus.session.fundingParams.commitmentFormat is Transactions.CommitmentFormat.SimpleTaprootChannels -> { - val nextCommitNonce = listOf(state.spliceStatus.session.fundingTx.txId to state.spliceStatus.session.nextCommitNonce(channelKeys()).publicNonce) - Pair(state.spliceStatus.session.currentCommitNonce(channelKeys())?.publicNonce, nextCommitNonce) + when (signingSession?.fundingParams?.commitmentFormat) { + null -> Pair(null, null) + Transactions.CommitmentFormat.AnchorOutputs -> Pair(null, null) + Transactions.CommitmentFormat.SimpleTaprootChannels -> { + val currentCommitNonce = signingSession.currentCommitNonce(channelKeys)?.publicNonce + val nextCommitNonce = signingSession.nextCommitNonce(channelKeys).publicNonce + Pair(currentCommitNonce, signingSession.fundingTxId to nextCommitNonce) + } } - - else -> Pair(null, listOf>()) } - - val tlvs = setOfNotNull( - unsignedFundingTxId?.let { ChannelReestablishTlv.NextFunding(it) }, - interactiveTxCurrentCommitNonce?.let { ChannelReestablishTlv.CurrentCommitNonce(it) }, - if (nextCommitNonces.isNotEmpty() || interactiveTxNextCommitNonce.isNotEmpty()) { - ChannelReestablishTlv.NextLocalNonces(nextCommitNonces + interactiveTxNextCommitNonce) - } else null - ) - ChannelReestablish( channelId = channelId, nextLocalCommitmentNumber = nextLocalCommitmentNumber, nextRemoteRevocationNumber = state.commitments.remoteCommitIndex, yourLastCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret), myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint, - tlvStream = TlvStream(tlvs) + nextCommitNonces = nextCommitNonces + listOfNotNull(interactiveTxNextCommitNonce), + nextFundingTxId = unsignedFundingTxId, + currentCommitNonce = interactiveTxCurrentCommitNonce, ) } } @@ -392,7 +394,8 @@ sealed class PersistedChannelState : ChannelState() { sealed class ChannelStateWithCommitments : PersistedChannelState() { abstract val commitments: Commitments - abstract val remoteCommitNonces: Map + // Remote nonces that must be used when signing the next remote commitment transaction (one per active commitment). + abstract val remoteNextCommitNonces: Map override val channelId: ByteVector32 get() = commitments.channelId val isChannelOpener: Boolean get() = commitments.channelParams.localParams.isChannelOpener val paysCommitTxFees: Boolean get() = commitments.channelParams.localParams.paysCommitTxFees @@ -419,6 +422,14 @@ sealed class ChannelStateWithCommitments : PersistedChannelState() { } } + internal fun ChannelContext.createChannelReady(): ChannelReady { + val localFundingKey = channelKeys().fundingKey(fundingTxIndex = 0) + val remoteFundingKey = commitments.latest.remoteFundingPubkey + val nextPerCommitmentPoint = channelKeys().commitmentPoint(1) + val nextCommitNonce = NonceGenerator.verificationNonce(commitments.latest.fundingTxId, localFundingKey, remoteFundingKey, commitIndex = 1) + return ChannelReady(channelId, nextPerCommitmentPoint, ShortChannelId.peerId(staticParams.nodeParams.nodeId), nextCommitNonce.publicNonce) + } + /** * Default handler when a funding transaction confirms. */ @@ -447,60 +458,71 @@ sealed class ChannelStateWithCommitments : PersistedChannelState() { internal fun ChannelContext.startClosingNegotiation( cmd: ChannelCommand.Close.MutualClose?, commitments: Commitments, + remoteNextCommitNonces: Map, localShutdown: Shutdown, + localCloseeNonce: Transactions.LocalNonce?, remoteShutdown: Shutdown, actions: List, - remoteCommitNonces: Map, - localCloseeNonce: Transactions.LocalNonce?, - remoteCloseeNonce: IndividualNonce? ): Pair> { val localScript = localShutdown.scriptPubKey val remoteScript = remoteShutdown.scriptPubKey + val remoteCloseeNonce = remoteShutdown.closeeNonce val currentHeight = currentBlockHeight.toLong() return when (cmd) { null -> { logger.info { "mutual close was initiated by our peer, waiting for remote closing_complete" } - val nextState = - Negotiating(commitments, remoteCommitNonces, localScript, remoteScript, listOf(), listOf(), currentHeight, cmd, localCloseeNonce = localCloseeNonce, remoteCloseeNonce = remoteCloseeNonce, localCloserNonces = null) + val nextState = Negotiating( + commitments, + remoteNextCommitNonces, + localScript, + remoteScript, + listOf(), + listOf(), + currentHeight, + cmd, + localCloseeNonce, + remoteCloseeNonce, + localCloserNonces = null + ) val actions1 = listOf(ChannelAction.Storage.StoreState(nextState)) Pair(nextState, actions + actions1) } else -> { - when (val closingResult = Helpers.Closing.makeClosingTxs(channelKeys(), commitments.latest, localScript, remoteScript, cmd.feerate, currentHeight, remoteShutdown.closeeNonce)) { + when (val closingResult = Helpers.Closing.makeClosingTxs(channelKeys(), commitments.latest, localScript, remoteScript, cmd.feerate, currentHeight, remoteCloseeNonce)) { is Either.Left -> { logger.warning { "cannot create local closing txs, waiting for remote closing_complete: ${closingResult.value.message}" } cmd.replyTo.complete(ChannelCloseResponse.Failure.Unknown(closingResult.value)) val nextState = Negotiating( commitments, - remoteCommitNonces, + remoteNextCommitNonces, localScript, remoteScript, listOf(), listOf(), currentHeight, cmd, - localCloseeNonce = localCloseeNonce, - remoteCloseeNonce = remoteCloseeNonce, + localCloseeNonce, + remoteCloseeNonce, localCloserNonces = null ) val actions1 = listOf(ChannelAction.Storage.StoreState(nextState)) Pair(nextState, actions + actions1) } is Either.Right -> { - val (closingTxs, closingComplete, localNonces) = closingResult.value + val (closingTxs, closingComplete, localCloserNonces) = closingResult.value val nextState = Negotiating( commitments, - remoteCommitNonces, + remoteNextCommitNonces, localScript, remoteScript, listOf(closingTxs), listOf(), currentHeight, cmd, - localCloseeNonce = localCloseeNonce, - remoteCloseeNonce = remoteCloseeNonce, - localCloserNonces = localNonces + localCloseeNonce, + remoteCloseeNonce, + localCloserNonces ) val actions1 = listOf( ChannelAction.Storage.StoreState(nextState), @@ -713,7 +735,7 @@ sealed class ChannelStateWithCommitments : PersistedChannelState() { ) val nextState = when (this@ChannelStateWithCommitments) { is Closing -> this@ChannelStateWithCommitments.copy(remoteCommitPublished = remoteCommitPublished) - is Negotiating -> Closing(commitments, remoteCommitNonces = remoteCommitNonces, waitingSinceBlock, proposedClosingTxs.flatMap { it.all }, publishedClosingTxs, remoteCommitPublished = remoteCommitPublished) + is Negotiating -> Closing(commitments, waitingSinceBlock, proposedClosingTxs.flatMap { it.all }, publishedClosingTxs, remoteCommitPublished = remoteCommitPublished) else -> Closing(commitments, waitingSinceBlock = currentBlockHeight.toLong(), remoteCommitPublished = remoteCommitPublished) } return Pair(nextState, buildList { diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Closed.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Closed.kt index de7041977..eae0629cd 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Closed.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Closed.kt @@ -11,8 +11,7 @@ import fr.acinq.lightning.channel.Commitments */ data class Closed(val state: Closing) : ChannelStateWithCommitments() { override val commitments: Commitments get() = state.commitments - - override val remoteCommitNonces: Map get() = state.remoteCommitNonces + override val remoteNextCommitNonces: Map get() = mapOf() override fun updateCommitments(input: Commitments): ChannelStateWithCommitments { return this.copy(state = state.updateCommitments(input) as Closing) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Closing.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Closing.kt index e59ad03eb..c3a0c9d50 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Closing.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Closing.kt @@ -35,7 +35,6 @@ data class RevokedClose(val revokedCommitPublished: RevokedCommitPublished) : Cl data class Closing( override val commitments: Commitments, - override val remoteCommitNonces: Map = mapOf(), // TODO: check this val waitingSinceBlock: Long, // how many blocks since we initiated the closing val mutualCloseProposed: List = emptyList(), // all exchanged closing sigs are flattened, we use this only to keep track of what publishable tx they have val mutualClosePublished: List = emptyList(), @@ -46,6 +45,8 @@ data class Closing( val revokedCommitPublished: List = emptyList() ) : ChannelStateWithCommitments() { + override val remoteNextCommitNonces: Map = mapOf() + private val spendingTxs: List by lazy { mutualClosePublished.map { it.tx } + revokedCommitPublished.map { it.commitTx } + listOfNotNull( localCommitPublished?.commitTx, diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Negotiating.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Negotiating.kt index 53284c3d5..e9cd9a954 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Negotiating.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Negotiating.kt @@ -12,11 +12,10 @@ import fr.acinq.lightning.blockchain.WatchSpentTriggered import fr.acinq.lightning.channel.* import fr.acinq.lightning.transactions.Transactions import fr.acinq.lightning.wire.* -import kotlinx.serialization.Transient data class Negotiating( override val commitments: Commitments, - @Transient override val remoteCommitNonces: Map, + override val remoteNextCommitNonces: Map, val localScript: ByteVector, val remoteScript: ByteVector, // Closing transactions we created, where we pay the fees (unsigned). @@ -26,9 +25,9 @@ data class Negotiating( val publishedClosingTxs: List, val waitingSinceBlock: Long, // how many blocks since we initiated the closing val closeCommand: ChannelCommand.Close.MutualClose?, - @Transient val localCloseeNonce: Transactions.LocalNonce?, - @Transient val remoteCloseeNonce: IndividualNonce?, - @Transient val localCloserNonces: Transactions.CloserNonces?, + val localCloseeNonce: Transactions.LocalNonce?, + val remoteCloseeNonce: IndividualNonce?, + val localCloserNonces: Transactions.CloserNonces?, ) : ChannelStateWithCommitments() { override fun updateCommitments(input: Commitments): ChannelStateWithCommitments = this.copy(commitments = input) @@ -55,16 +54,19 @@ data class Negotiating( val nextState = this@Negotiating.copy(remoteScript = cmd.message.closerScriptPubKey) Pair(nextState, listOf(ChannelAction.Message.Send(Warning(channelId, InvalidCloseeScript(channelId, cmd.message.closeeScriptPubKey, localScript).message)))) } else { - when (val result = Helpers.Closing.signClosingTx(channelKeys(), commitments.latest, cmd.message.closeeScriptPubKey, cmd.message.closerScriptPubKey, cmd.message, this@Negotiating.localCloseeNonce)) { + when (val result = Helpers.Closing.signClosingTx(channelKeys(), commitments.latest, cmd.message.closeeScriptPubKey, cmd.message.closerScriptPubKey, cmd.message, localCloseeNonce)) { is Either.Left -> { logger.warning { "invalid closing_complete: ${result.value.message}" } Pair(this@Negotiating, listOf(ChannelAction.Message.Send(Warning(channelId, result.value.message)))) } is Either.Right -> { - val (signedClosingTx, closingSig, localNonce) = result.value + val (signedClosingTx, closingSig, nextLocalNonce) = result.value logger.debug { "signing remote mutual close transaction: ${signedClosingTx.tx}" } - val nextState = this@Negotiating - .copy(remoteScript = cmd.message.closerScriptPubKey, publishedClosingTxs = publishedClosingTxs + signedClosingTx, localCloseeNonce = localNonce) + val nextState = this@Negotiating.copy( + remoteScript = cmd.message.closerScriptPubKey, + publishedClosingTxs = publishedClosingTxs + signedClosingTx, + localCloseeNonce = nextLocalNonce + ) val actions = listOf( ChannelAction.Storage.StoreState(nextState), ChannelAction.Blockchain.PublishTx(signedClosingTx), @@ -77,17 +79,16 @@ data class Negotiating( } } is ClosingSig -> { - when (val result = Helpers.Closing.receiveClosingSig(channelKeys(), commitments.latest, proposedClosingTxs.last(), cmd.message, this@Negotiating.localCloserNonces, this@Negotiating.remoteCloseeNonce)) { + when (val result = Helpers.Closing.receiveClosingSig(channelKeys(), commitments.latest, proposedClosingTxs.last(), cmd.message, localCloserNonces, remoteCloseeNonce)) { is Either.Left -> { logger.warning { "invalid closing_sig: ${result.value.message}" } - Pair(this@Negotiating, listOf(ChannelAction.Message.Send(Warning(channelId, result.value.message)))) + Pair(this@Negotiating.copy(remoteCloseeNonce = cmd.message.nextCloseeNonce), listOf(ChannelAction.Message.Send(Warning(channelId, result.value.message)))) } is Either.Right -> { val signedClosingTx = result.value logger.debug { "received signatures for local mutual close transaction: ${signedClosingTx.tx}" } closeCommand?.replyTo?.complete(ChannelCloseResponse.Success(signedClosingTx.tx.txid, signedClosingTx.fee)) - val nextState = this@Negotiating - .copy(publishedClosingTxs = publishedClosingTxs + signedClosingTx, remoteCloseeNonce = cmd.message.nextCloseeNonce) + val nextState = this@Negotiating.copy(publishedClosingTxs = publishedClosingTxs + signedClosingTx, remoteCloseeNonce = cmd.message.nextCloseeNonce) val actions = listOf( ChannelAction.Storage.StoreState(nextState), ChannelAction.Blockchain.PublishTx(signedClosingTx), @@ -148,18 +149,23 @@ data class Negotiating( cmd.replyTo.complete(ChannelCloseResponse.Failure.RbfFeerateTooLow(cmd.feerate, closeCommand.feerate * 1.2)) handleCommandError(cmd, InvalidRbfFeerate(channelId, cmd.feerate, closeCommand.feerate * 1.2)) } else { - when (val result = Helpers.Closing.makeClosingTxs(channelKeys(), commitments.latest, cmd.scriptPubKey ?: localScript, remoteScript, cmd.feerate, currentBlockHeight.toLong(), this@Negotiating.remoteCloseeNonce)) { + when (val result = Helpers.Closing.makeClosingTxs(channelKeys(), commitments.latest, cmd.scriptPubKey ?: localScript, remoteScript, cmd.feerate, currentBlockHeight.toLong(), remoteCloseeNonce)) { is Either.Left -> { cmd.replyTo.complete(ChannelCloseResponse.Failure.Unknown(result.value)) handleCommandError(cmd, result.value) } is Either.Right -> { - val (closingTxs, closingComplete) = result.value + val (closingTxs, closingComplete, localCloserNonces) = result.value logger.debug { "signing local mutual close transactions: $closingTxs" } // If we never received our peer's closing_sig, the previous command was not completed, so we must complete now. // If it was already completed because we received closing_sig, this will be a no-op. closeCommand?.replyTo?.complete(ChannelCloseResponse.Failure.ClosingUpdated(cmd.feerate, cmd.scriptPubKey)) - val nextState = this@Negotiating.copy(closeCommand = cmd, localScript = closingComplete.closerScriptPubKey, proposedClosingTxs = proposedClosingTxs + closingTxs) + val nextState = this@Negotiating.copy( + closeCommand = cmd, + localScript = closingComplete.closerScriptPubKey, + proposedClosingTxs = proposedClosingTxs + closingTxs, + localCloserNonces = localCloserNonces + ) val actions = buildList { add(ChannelAction.Storage.StoreState(nextState)) add(ChannelAction.Message.Send(closingComplete)) 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 a6f7f34ce..d2fa87f56 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 @@ -12,10 +12,10 @@ import fr.acinq.lightning.channel.* import fr.acinq.lightning.transactions.Transactions import fr.acinq.lightning.utils.* import fr.acinq.lightning.wire.* -import kotlinx.serialization.Transient data class Normal( override val commitments: Commitments, + override val remoteNextCommitNonces: Map, val shortChannelId: ShortChannelId, val channelUpdate: ChannelUpdate, val remoteChannelUpdate: ChannelUpdate?, @@ -23,16 +23,12 @@ data class Normal( val localShutdown: Shutdown?, val remoteShutdown: Shutdown?, val closeCommand: ChannelCommand.Close.MutualClose?, - @Transient override val remoteCommitNonces: Map, - @Transient val localCloseeNonce: Transactions.LocalNonce?, - @Transient val remoteCloseeNonce: IndividualNonce?, - @Transient val localCloserNonces: Transactions.CloserNonces?, + val localCloseeNonce: Transactions.LocalNonce?, + val localCloserNonces: Transactions.CloserNonces?, ) : ChannelStateWithCommitments() { override fun updateCommitments(input: Commitments): ChannelStateWithCommitments = this.copy(commitments = input) - fun addRemoteCommitNonce(fundingTxId: TxId, nonce: IndividualNonce?): Normal = nonce?.let { this.copy(remoteCommitNonces = this.remoteCommitNonces + (fundingTxId to it)) } ?: this - override suspend fun ChannelContext.processInternal(cmd: ChannelCommand): Pair> { val forbiddenPreSplice = cmd is ChannelCommand.ForbiddenDuringQuiescence && spliceStatus is QuiescenceNegotiation val forbiddenDuringSplice = cmd is ChannelCommand.ForbiddenDuringSplice && spliceStatus is QuiescentSpliceStatus @@ -70,7 +66,7 @@ data class Normal( logger.debug { "already in the process of signing, will sign again as soon as possible" } Pair(this@Normal, listOf()) } - else -> when (val result = commitments.sendCommit(channelKeys(), remoteCommitNonces, logger)) { + else -> when (val result = commitments.sendCommit(channelKeys(), remoteNextCommitNonces, logger)) { is Either.Left -> handleCommandError(cmd, result.value, channelUpdate) is Either.Right -> { val commitments1 = result.value.first @@ -121,7 +117,7 @@ data class Normal( handleCommandError(cmd, InvalidFinalScript(channelId), channelUpdate) } else -> { - val (localCloseeNonce, shutdown) = this@Normal.commitments.createShutdown(channelKeys(), localScriptPubkey) + val (localCloseeNonce, shutdown) = Helpers.Closing.createShutdown(channelKeys(), commitments.latest, localScriptPubkey) val newState = this@Normal.copy(localCloseeNonce = localCloseeNonce, localShutdown = shutdown, closeCommand = cmd) val actions = listOf(ChannelAction.Storage.StoreState(newState), ChannelAction.Message.Send(shutdown)) Pair(newState, actions) @@ -245,11 +241,11 @@ data class Normal( } val nextState = if (remoteShutdown != null && !commitments1.changes.localHasUnsignedOutgoingHtlcs()) { // we were waiting for our pending htlcs to be signed before replying with our local shutdown - val (localCloseeNonce, localShutdown) = commitments1.createShutdown(channelKeys(), commitments.channelParams.localParams.defaultFinalScriptPubKey) + val (localCloseeNonce, localShutdown) = Helpers.Closing.createShutdown(channelKeys(), commitments1.latest) actions.add(ChannelAction.Message.Send(localShutdown)) if (commitments1.latest.remoteCommit.spec.htlcs.isNotEmpty()) { // we just signed htlcs that need to be resolved now - ShuttingDown(commitments, localShutdown, remoteShutdown, closeCommand, remoteCommitNonces = cmd.message.nextCommitNonces, localCloseeNonce = localCloseeNonce) + ShuttingDown(commitments, cmd.message.nextCommitNonces, localShutdown, remoteShutdown, closeCommand, localCloseeNonce) } else { logger.warning { "we have no htlcs but have not replied with our shutdown yet, this should never happen" } Negotiating( @@ -261,13 +257,13 @@ data class Normal( listOf(), currentBlockHeight.toLong(), closeCommand, - localCloseeNonce = localCloseeNonce, - remoteCloseeNonce = localShutdown.closeeNonce, - localCloserNonces = this@Normal.localCloserNonces + localCloseeNonce, + remoteShutdown.closeeNonce, + localCloserNonces ) } } else { - this@Normal.copy(commitments = commitments1, remoteCommitNonces = cmd.message.nextCommitNonces) + this@Normal.copy(commitments = commitments1, remoteNextCommitNonces = cmd.message.nextCommitNonces) } actions.add(0, ChannelAction.Storage.StoreState(nextState)) Pair(nextState, actions) @@ -308,11 +304,11 @@ data class Normal( when (commitments.remoteNextCommitInfo) { is Either.Left -> { // we already have a signature in progress, will resign when we receive the revocation - Pair(this@Normal.copy(remoteShutdown = cmd.message, remoteCloseeNonce = cmd.message.closeeNonce), listOf()) + Pair(this@Normal.copy(remoteShutdown = cmd.message), listOf()) } is Either.Right -> { // no, let's sign right away - val newState = this@Normal.copy(remoteShutdown = cmd.message, remoteCloseeNonce = cmd.message.closeeNonce)//.updateCloseeNonce(cmd.message.closeeNonce) + val newState = this@Normal.copy(remoteShutdown = cmd.message) Pair(newState, listOf(ChannelAction.Message.SendToSelf(ChannelCommand.Commitment.Sign))) } } @@ -320,25 +316,24 @@ data class Normal( else -> { // so we don't have any unsigned outgoing changes val actions = mutableListOf() - val (localCloseeNonce, localShutdown) = when (this@Normal.localShutdown) { - null -> commitments.createShutdown(channelKeys(), commitments.channelParams.localParams.defaultFinalScriptPubKey) - else -> this@Normal.localCloseeNonce to this@Normal.localShutdown + val (localCloseeNonce1, localShutdown1) = when (localShutdown) { + null -> Helpers.Closing.createShutdown(channelKeys(), commitments.latest) + else -> localCloseeNonce to localShutdown } - if (this@Normal.localShutdown == null) actions.add(ChannelAction.Message.Send(localShutdown)) + if (localShutdown == null) actions.add(ChannelAction.Message.Send(localShutdown1)) when { commitments.hasNoPendingHtlcsOrFeeUpdate() -> startClosingNegotiation( closeCommand, commitments, - localShutdown, + remoteNextCommitNonces, + localShutdown1, + localCloseeNonce1, cmd.message, actions, - this@Normal.remoteCommitNonces, - localCloseeNonce, - cmd.message.closeeNonce ) else -> { // there are some pending changes, we need to wait for them to be settled (fail/fulfill htlcs and sign fee updates) - val nextState = ShuttingDown(commitments, localShutdown, cmd.message, closeCommand, remoteCommitNonces = remoteCommitNonces, localCloseeNonce = localCloseeNonce) + val nextState = ShuttingDown(commitments, remoteNextCommitNonces, localShutdown1, cmd.message, closeCommand, localCloseeNonce1) actions.add(ChannelAction.Storage.StoreState(nextState)) Pair(nextState, actions) } @@ -461,15 +456,12 @@ data class Normal( val channelKeys = channelKeys() logger.info { "accepting splice with remote.amount=${cmd.message.fundingContribution}" } val parentCommitment = commitments.active.first() - val (nextCommitmentFormat, channelType) = when { cmd.message.channelType == ChannelType.SupportedChannelType.SimpleTaprootChannels && parentCommitment.commitmentFormat == Transactions.CommitmentFormat.AnchorOutputs -> { Pair(Transactions.CommitmentFormat.SimpleTaprootChannels, cmd.message.channelType) } - else -> Pair(parentCommitment.commitmentFormat, null) } - val spliceAck = SpliceAck( channelId, fundingContribution = 0.sat, // only remote contributes to the splice @@ -495,13 +487,12 @@ data class Normal( channelKeys, keyManager.swapInOnChainWallet, fundingParams, + parentCommitment.localCommit.index, previousLocalBalance = parentCommitment.localCommit.spec.toLocal, previousRemoteBalance = parentCommitment.localCommit.spec.toRemote, localHtlcs = parentCommitment.localCommit.spec.htlcs, fundingContributions = FundingContributions(emptyList(), emptyList()), // as non-initiator we don't contribute to this splice for now previousTxs = emptyList(), - commitTxIndex = parentCommitment.localCommit.index, - fundingTxIndex = parentCommitment.fundingTxIndex + 1 ) val nextState = this@Normal.copy( spliceStatus = SpliceStatus.InProgress( @@ -556,6 +547,7 @@ data class Normal( sharedInput = sharedInput, remoteFundingPubkey = cmd.message.fundingPubkey, localOutputs = spliceStatus.command.spliceOutputs, + // We always upgrade to taproot whenever initiating a splice. commitmentFormat = Transactions.CommitmentFormat.SimpleTaprootChannels, lockTime = spliceStatus.spliceInit.lockTime, dustLimit = commitments.latest.localCommitParams.dustLimit.max(commitments.latest.remoteCommitParams.dustLimit), @@ -590,13 +582,12 @@ data class Normal( channelKeys, keyManager.swapInOnChainWallet, fundingParams, + parentCommitment.localCommit.index, previousLocalBalance = parentCommitment.localCommit.spec.toLocal, previousRemoteBalance = parentCommitment.localCommit.spec.toRemote, localHtlcs = parentCommitment.localCommit.spec.htlcs, fundingContributions = fundingContributions.value, previousTxs = emptyList(), - commitTxIndex = parentCommitment.localCommit.index, - fundingTxIndex = parentCommitment.fundingTxIndex + 1 ).send() when (interactiveTxAction) { is InteractiveTxSessionAction.SendMessage -> { @@ -640,7 +631,6 @@ data class Normal( parentCommitment.localCommitParams, parentCommitment.remoteCommitParams, spliceStatus.spliceSession.fundingParams, - fundingTxIndex = parentCommitment.fundingTxIndex + 1, interactiveTxAction.sharedTx, liquidityPurchase = spliceStatus.liquidityPurchase, localCommitmentIndex = parentCommitment.localCommit.index, @@ -664,7 +654,7 @@ data class Normal( spliceStatus.replyTo?.complete( ChannelFundingResponse.Success( channelId = channelId, - fundingTxIndex = session.fundingTxIndex, + fundingTxIndex = session.fundingParams.fundingTxIndex, fundingTxId = session.fundingTx.txId, capacity = session.fundingParams.fundingAmount, balance = session.localCommit.fold({ it.spec }, { it.spec }).toLocal, @@ -888,7 +878,8 @@ data class Normal( val fundingScript = action.commitment.commitInput(channelKeys()).txOut.publicKeyScript val watchConfirmed = WatchConfirmed(channelId, action.commitment.fundingTxId, fundingScript, staticParams.nodeParams.minDepthBlocks, WatchConfirmed.ChannelFundingDepthOk) val commitments = commitments.add(action.commitment) - val nextState = this@Normal.copy(commitments = commitments, spliceStatus = SpliceStatus.None).addRemoteCommitNonce(action.commitment.fundingTxId, action.nextRemoteCommitNonce) + val remoteNextCommitNonces1 = remoteNextCommitNonces + listOfNotNull(action.nextRemoteCommitNonce?.let { action.commitment.fundingTxId to it }).toMap() + val nextState = this@Normal.copy(commitments = commitments, remoteNextCommitNonces = remoteNextCommitNonces1, spliceStatus = SpliceStatus.None) val actions = buildList { add(ChannelAction.Storage.StoreState(nextState)) action.fundingTx.signedTx?.let { add(ChannelAction.Blockchain.PublishTx(it, ChannelAction.Blockchain.PublishTx.Type.FundingTx)) } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Offline.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Offline.kt index cad51ed40..5c2cfc055 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Offline.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Offline.kt @@ -6,10 +6,7 @@ import fr.acinq.lightning.blockchain.WatchConfirmed import fr.acinq.lightning.blockchain.WatchConfirmedTriggered import fr.acinq.lightning.blockchain.WatchSpentTriggered import fr.acinq.lightning.channel.* -import fr.acinq.lightning.wire.ChannelReady -import fr.acinq.lightning.wire.ChannelReadyTlv import fr.acinq.lightning.wire.Error -import fr.acinq.lightning.wire.TlvStream data class Offline(val state: PersistedChannelState) : ChannelState() { @@ -66,10 +63,9 @@ data class Offline(val state: PersistedChannelState) : ChannelState() { val nextState = when (state) { is WaitForFundingConfirmed -> { logger.info { "was confirmed while offline at blockHeight=${watch.blockHeight} txIndex=${watch.txIndex} with funding txid=${watch.tx.txid}" } - val nextPerCommitmentPoint = commitments1.channelParams.localParams.channelKeys(keyManager).commitmentPoint(1) - val channelReady = ChannelReady(channelId, nextPerCommitmentPoint, TlvStream(ChannelReadyTlv.ShortChannelIdTlv(ShortChannelId.peerId(staticParams.nodeParams.nodeId)))) + val channelReady = state.run { createChannelReady() } val shortChannelId = ShortChannelId(watch.blockHeight, watch.txIndex, commitments1.latest.fundingInput.index.toInt()) - WaitForChannelReady(commitments1, shortChannelId, channelReady, state.remoteCommitNonces) + WaitForChannelReady(commitments1, mapOf(), shortChannelId, channelReady) } else -> state } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/ShuttingDown.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/ShuttingDown.kt index dfc06ab79..7617fb77f 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/ShuttingDown.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/ShuttingDown.kt @@ -8,15 +8,14 @@ import fr.acinq.lightning.blockchain.WatchSpentTriggered import fr.acinq.lightning.channel.* import fr.acinq.lightning.transactions.Transactions import fr.acinq.lightning.wire.* -import kotlinx.serialization.Transient data class ShuttingDown( override val commitments: Commitments, + override val remoteNextCommitNonces: Map, val localShutdown: Shutdown, val remoteShutdown: Shutdown, val closeCommand: ChannelCommand.Close.MutualClose?, - @Transient override val remoteCommitNonces: Map, - @Transient val localCloseeNonce: Transactions.LocalNonce? + val localCloseeNonce: Transactions.LocalNonce? ) : ChannelStateWithCommitments() { override fun updateCommitments(input: Commitments): ChannelStateWithCommitments = this.copy(commitments = input) @@ -52,12 +51,11 @@ data class ShuttingDown( commitments1.hasNoPendingHtlcsOrFeeUpdate() -> startClosingNegotiation( closeCommand, commitments1, + remoteNextCommitNonces, localShutdown, + localCloseeNonce, remoteShutdown, listOf(ChannelAction.Message.Send(revocation)), - this@ShuttingDown.remoteCommitNonces, - localCloseeNonce, - remoteShutdown.closeeNonce ) else -> { val nextState = this@ShuttingDown.copy(commitments = commitments1) @@ -82,15 +80,14 @@ data class ShuttingDown( commitments1.hasNoPendingHtlcsOrFeeUpdate() -> startClosingNegotiation( closeCommand, commitments1, + remoteNextCommitNonces, localShutdown, + localCloseeNonce, remoteShutdown, actions, - this@ShuttingDown.remoteCommitNonces, - localCloseeNonce, - remoteShutdown.closeeNonce ) else -> { - val nextState = this@ShuttingDown.copy(commitments = commitments1) + val nextState = this@ShuttingDown.copy(commitments = commitments1, remoteNextCommitNonces = cmd.message.nextCommitNonces) val actions1 = buildList { addAll(actions) add(ChannelAction.Storage.StoreState(nextState)) @@ -132,7 +129,7 @@ data class ShuttingDown( logger.debug { "already in the process of signing, will sign again as soon as possible" } Pair(this@ShuttingDown, listOf()) } else { - when (val result = commitments.sendCommit(channelKeys(), remoteCommitNonces, logger)) { + when (val result = commitments.sendCommit(channelKeys(), remoteNextCommitNonces, logger)) { is Either.Left -> handleCommandError(cmd, result.value) is Either.Right -> { val commitments1 = result.value.first 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 ea321e2db..4997e24c3 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 @@ -7,11 +7,11 @@ import fr.acinq.lightning.blockchain.WatchConfirmed import fr.acinq.lightning.blockchain.WatchConfirmedTriggered import fr.acinq.lightning.blockchain.WatchSpentTriggered import fr.acinq.lightning.channel.* +import fr.acinq.lightning.crypto.NonceGenerator import fr.acinq.lightning.utils.toByteVector import fr.acinq.lightning.wire.* data class Syncing(val state: PersistedChannelState, val channelReestablishSent: Boolean) : ChannelState() { - val channelId = state.channelId override suspend fun ChannelContext.processInternal(cmd: ChannelCommand): Pair> { @@ -30,7 +30,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: // 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}" } when (val commitSig = state.signingSession.remoteCommit.sign(state.channelParams, channelKeys, state.signingSession, cmd.message.currentCommitNonce)) { - is Either.Left -> return handleLocalError(cmd, commitSig.value) + is Either.Left -> logger.warning { "cannot retransmit commit_sig: ${commitSig.value.message}" } is Either.Right -> add(ChannelAction.Message.Send(commitSig.value)) } } @@ -38,7 +38,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: Pair(state, actions) } is WaitForFundingConfirmed -> { - val state1 = state.copy(remoteCommitNonces = cmd.message.nextCommitNonces) + val state1 = state.copy(remoteNextCommitNonces = cmd.message.nextCommitNonces) when (cmd.message.nextFundingTxId) { null -> Pair(state1, listOf()) else -> { @@ -48,8 +48,8 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: // 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}" } - when (val commitSig = state.rbfStatus.session.remoteCommit.sign(state.commitments.channelParams, channelKeys, state.rbfStatus.session, remoteNonce = cmd.message.currentCommitNonce)) { - is Either.Left -> return handleLocalError(cmd, commitSig.value) + when (val commitSig = state.rbfStatus.session.remoteCommit.sign(state.commitments.channelParams, channelKeys, state.rbfStatus.session, cmd.message.currentCommitNonce)) { + is Either.Left -> logger.warning { "cannot retransmit commit_sig: ${commitSig.value.message}" } is Either.Right -> add(ChannelAction.Message.Send(commitSig.value)) } } @@ -72,7 +72,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: batchSize = 1, remoteNonce = cmd.message.currentCommitNonce )) { - is Either.Left -> return handleLocalError(cmd, commitSig.value) + is Either.Left -> logger.warning { "cannot retransmit commit_sig: ${commitSig.value.message}" } is Either.Right -> add(ChannelAction.Message.Send(commitSig.value)) } } @@ -108,7 +108,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: batchSize = 1, remoteNonce = cmd.message.currentCommitNonce )) { - is Either.Left -> return handleLocalError(cmd, commitSig.value) + is Either.Left -> logger.warning { "cannot retransmit commit_sig: ${commitSig.value.message}" } is Either.Right -> actions.add(ChannelAction.Message.Send(commitSig.value)) } } @@ -122,10 +122,9 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: } } logger.debug { "re-sending channel_ready" } - val nextPerCommitmentPoint = channelKeys.commitmentPoint(1) - val channelReady = ChannelReady(state.commitments.channelId, nextPerCommitmentPoint) + val channelReady = state.run { createChannelReady() } actions.add(ChannelAction.Message.Send(channelReady)) - Pair(state.copy(remoteCommitNonces = cmd.message.nextCommitNonces), actions) + Pair(state.copy(remoteNextCommitNonces = cmd.message.nextCommitNonces), actions) } is Normal -> { when (val syncResult = handleSync(state.commitments, cmd.message)) { @@ -138,8 +137,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: if (state.commitments.latest.fundingTxIndex == 0L && cmd.message.nextLocalCommitmentNumber == 1L && state.commitments.localCommitIndex == 0L) { // If next_local_commitment_number is 1 in both the channel_reestablish it sent and received, then the node MUST retransmit channel_ready, otherwise it MUST NOT logger.debug { "re-sending channel_ready" } - val nextPerCommitmentPoint = channelKeys.commitmentPoint(1) - val channelReady = ChannelReady(state.commitments.channelId, nextPerCommitmentPoint) + val channelReady = state.run { createChannelReady() } actions.add(ChannelAction.Message.Send(channelReady)) } @@ -148,10 +146,9 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: if (cmd.message.nextLocalCommitmentNumber == state.commitments.remoteCommitIndex) { // 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.fundingTxIndex} fundingTxId=${state.spliceStatus.session.fundingTx.txId}" } - when (val commitSig = - state.spliceStatus.session.remoteCommit.sign(state.commitments.channelParams, channelKeys, state.spliceStatus.session, remoteNonce = cmd.message.currentCommitNonce)) { - is Either.Left -> return handleLocalError(cmd, commitSig.value) + logger.info { "re-sending commit_sig for splice attempt with fundingTxIndex=${state.spliceStatus.session.fundingParams.fundingTxIndex} fundingTxId=${state.spliceStatus.session.fundingTx.txId}" } + when (val commitSig = state.spliceStatus.session.remoteCommit.sign(state.commitments.channelParams, channelKeys, state.spliceStatus.session, cmd.message.currentCommitNonce)) { + is Either.Left -> logger.warning { "cannot retransmit commit_sig: ${commitSig.value.message}" } is Either.Right -> actions.add(ChannelAction.Message.Send(commitSig.value)) } } @@ -174,7 +171,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: batchSize = 1, remoteNonce = cmd.message.currentCommitNonce )) { - is Either.Left -> return handleLocalError(cmd, commitSig.value) + is Either.Left -> logger.warning { "cannot retransmit commit_sig: ${commitSig.value.message}" } is Either.Right -> actions.add(ChannelAction.Message.Send(commitSig.value)) } } @@ -234,7 +231,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: logger.debug { "re-sending local shutdown" } actions.add(ChannelAction.Message.Send(it)) } - Pair(state.copy(commitments = commitments1, spliceStatus = spliceStatus1, remoteCommitNonces = cmd.message.nextCommitNonces), actions) + Pair(state.copy(commitments = commitments1, remoteNextCommitNonces = cmd.message.nextCommitNonces, spliceStatus = spliceStatus1), actions) } } } @@ -243,18 +240,21 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: is SyncResult.Failure -> handleSyncFailure(state.commitments, cmd.message, syncResult) is SyncResult.Success -> { val commitments1 = discardUnsignedUpdates(state.commitments) + val (localCloseeNonce, localShutdown) = Helpers.Closing.createShutdown(channelKeys, state.commitments.latest, state.localShutdown.scriptPubKey) val actions = buildList { addAll(syncResult.retransmit) - add(state.localShutdown) + add(localShutdown) }.map { ChannelAction.Message.Send(it) } - Pair(state.copy(commitments = commitments1), actions) + val nextState = state.copy(commitments = commitments1, remoteNextCommitNonces = cmd.message.nextCommitNonces, localShutdown = localShutdown, localCloseeNonce = localCloseeNonce) + Pair(nextState, actions) } } } is Negotiating -> { // BOLT 2: A node if it has sent a previous shutdown MUST retransmit shutdown. - val (localCloseeNonce, shutdown) = state.commitments.createShutdown(channelKeys, state.localScript) - Pair(state.copy(localCloseeNonce = localCloseeNonce), listOf(ChannelAction.Message.Send(shutdown))) + val (localCloseeNonce, localShutdown) = Helpers.Closing.createShutdown(channelKeys, state.commitments.latest, state.localScript) + val nextState = state.copy(remoteNextCommitNonces = cmd.message.nextCommitNonces, localCloseeNonce = localCloseeNonce) + Pair(nextState, listOf(ChannelAction.Message.Send(localShutdown))) } is Closing, is Closed, is WaitForRemotePublishFutureCommitment -> unhandled(cmd) } @@ -306,10 +306,9 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: val nextState = when (state) { is WaitForFundingConfirmed -> { logger.info { "was confirmed while syncing at blockHeight=${watch.blockHeight} txIndex=${watch.txIndex} with funding txid=${watch.tx.txid}" } - val nextPerCommitmentPoint = channelKeys.commitmentPoint(1) - val channelReady = ChannelReady(channelId, nextPerCommitmentPoint, TlvStream(ChannelReadyTlv.ShortChannelIdTlv(ShortChannelId.peerId(staticParams.nodeParams.nodeId)))) + val channelReady = state.run { createChannelReady() } val shortChannelId = ShortChannelId(watch.blockHeight, watch.txIndex, commitments1.latest.fundingInput.index.toInt()) - WaitForChannelReady(commitments1, shortChannelId, channelReady, this@Syncing.state.remoteCommitNonces) + WaitForChannelReady(commitments1, mapOf(), shortChannelId, channelReady) } else -> state } @@ -425,7 +424,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: val batchSize = commitments.active.size val commitSigs = CommitSigs.fromSigs(commitments.active.mapNotNull { c -> val commitInput = c.commitInput(channelKeys) - val remoteNonce = remoteChannelReestablish.nextCommitNonces.get(commitInput.outPoint.txid) + 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. @@ -504,10 +503,16 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: // they just sent a new commit_sig, we have received it but they didn't receive our revocation val localPerCommitmentSecret = channelKeys.commitmentSecret(commitments.localCommitIndex - 1) val localNextPerCommitmentPoint = channelKeys.commitmentPoint(commitments.localCommitIndex + 1) + val localCommitNonces = commitments.active.map { c -> + val fundingKey = channelKeys.fundingKey(c.fundingTxIndex) + val nonce = NonceGenerator.verificationNonce(c.fundingTxId, fundingKey, c.remoteFundingPubkey, commitments.localCommitIndex + 1) + c.fundingTxId to nonce.publicNonce + } val revocation = RevokeAndAck( channelId = commitments.channelId, perCommitmentSecret = localPerCommitmentSecret, - nextPerCommitmentPoint = localNextPerCommitmentPoint + nextPerCommitmentPoint = localNextPerCommitmentPoint, + nextCommitNonces = localCommitNonces ) checkRemoteCommit(remoteChannelReestablish, retransmitRevocation = revocation) } else if (commitments.localCommitIndex > remoteChannelReestablish.nextRemoteRevocationNumber + 1) { @@ -551,7 +556,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: private fun handleOutdatedCommitment(remoteChannelReestablish: ChannelReestablish, commitments: Commitments): Pair> { val exc = PleasePublishYourCommitment(commitments.channelId) val error = Error(commitments.channelId, exc.message.encodeToByteArray().toByteVector()) - val nextState = WaitForRemotePublishFutureCommitment(commitments, remoteChannelReestablish, remoteChannelReestablish.nextCommitNonces) + val nextState = WaitForRemotePublishFutureCommitment(commitments, remoteChannelReestablish) val actions = listOf( ChannelAction.Storage.StoreState(nextState), ChannelAction.Message.Send(error) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannel.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannel.kt index 0cd595da6..2c3d76a26 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannel.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannel.kt @@ -83,12 +83,11 @@ data class WaitForAcceptChannel( channelKeys, keyManager.swapInOnChainWallet, fundingParams, - 0.msat, - 0.msat, - emptySet(), - fundingContributions.value, - commitTxIndex = 0, - fundingTxIndex = 0 + localCommitIndex = 0, + previousLocalBalance = 0.msat, + previousRemoteBalance = 0.msat, + localHtlcs = emptySet(), + fundingContributions = fundingContributions.value, ).send() when (interactiveTxAction) { is InteractiveTxSessionAction.SendMessage -> { diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForChannelReady.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForChannelReady.kt index a95df3b3c..1169511f5 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForChannelReady.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForChannelReady.kt @@ -11,14 +11,13 @@ import fr.acinq.lightning.channel.* import fr.acinq.lightning.router.Announcements import fr.acinq.lightning.utils.toMilliSatoshi import fr.acinq.lightning.wire.* -import kotlinx.serialization.Transient /** The channel funding transaction was confirmed, we exchange funding_locked messages. */ data class WaitForChannelReady( override val commitments: Commitments, + override val remoteNextCommitNonces: Map, val shortChannelId: ShortChannelId, val lastSent: ChannelReady, - @Transient override val remoteCommitNonces: Map, ) : ChannelStateWithCommitments() { override fun updateCommitments(input: Commitments): ChannelStateWithCommitments = this.copy(commitments = input) @@ -76,8 +75,13 @@ data class WaitForChannelReady( commitments.latest.fundingAmount.toMilliSatoshi(), enable = Helpers.aboveReserve(commitments) ) + val remoteNextCommitNonces1 = when (val nextCommitNonce = cmd.message.nextLocalNonce) { + null -> remoteNextCommitNonces + else -> remoteNextCommitNonces + mapOf(commitments.latest.fundingTxId to nextCommitNonce) + } val nextState = Normal( commitments, + remoteNextCommitNonces1, shortChannelId, initialChannelUpdate, null, @@ -86,10 +90,8 @@ data class WaitForChannelReady( null, null, localCloseeNonce = null, - remoteCloseeNonce = null, - remoteCommitNonces = remoteCommitNonces, localCloserNonces = null - ).addRemoteCommitNonce(commitments.latest.fundingTxId, cmd.message.nextLocalNonce) + ) val actions = listOf( ChannelAction.Storage.StoreState(nextState), ChannelAction.Storage.SetLocked(commitments.latest.fundingTxId), 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 9e86cb5d7..9109feda4 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 @@ -9,16 +9,15 @@ import fr.acinq.lightning.blockchain.WatchConfirmedTriggered import fr.acinq.lightning.channel.* import fr.acinq.lightning.utils.msat import fr.acinq.lightning.wire.* -import kotlinx.serialization.Transient /** We wait for the channel funding transaction to confirm. */ data class WaitForFundingConfirmed( override val commitments: Commitments, + override val remoteNextCommitNonces: Map, val waitingSinceBlock: Long, // how many blocks have we been waiting for the funding tx to confirm val deferred: ChannelReady?, // We can have at most one ongoing RBF attempt. val rbfStatus: RbfStatus, - @Transient override val remoteCommitNonces: Map, ) : ChannelStateWithCommitments() { val latestFundingTx = commitments.latest.localFundingStatus as LocalFundingStatus.UnconfirmedFundingTx @@ -105,12 +104,11 @@ data class WaitForFundingConfirmed( channelKeys(), keyManager.swapInOnChainWallet, fundingParams, + localCommitIndex = 0, SharedFundingInputBalances(0.msat, 0.msat, 0.msat), toSend, previousFundingTxs.map { it.sharedTx }, commitments.latest.localCommit.spec.htlcs, - commitTxIndex = 0, - fundingTxIndex = 0 ) val nextState = this@WaitForFundingConfirmed.copy(rbfStatus = RbfStatus.InProgress(session)) Pair(nextState, listOf(ChannelAction.Message.Send(TxAckRbf(channelId, fundingParams.localContribution)))) @@ -152,13 +150,12 @@ data class WaitForFundingConfirmed( channelKeys(), keyManager.swapInOnChainWallet, fundingParams, - 0.msat, - 0.msat, - emptySet(), - contributions.value, - previousFundingTxs.map { it.sharedTx }, - commitTxIndex = 0, - fundingTxIndex = 0 + localCommitIndex = 0, + previousLocalBalance = 0.msat, + previousRemoteBalance = 0.msat, + localHtlcs = emptySet(), + fundingContributions = contributions.value, + previousTxs = previousFundingTxs.map { it.sharedTx }, ).send() when (action) { is InteractiveTxSessionAction.SendMessage -> { @@ -182,7 +179,7 @@ data class WaitForFundingConfirmed( is RbfStatus.InProgress -> { val (rbfSession1, interactiveTxAction) = rbfStatus.rbfSession.receive(cmd.message) when (interactiveTxAction) { - is InteractiveTxSessionAction.SendMessage -> Pair(this@WaitForFundingConfirmed.copy(rbfStatus = rbfStatus.copy(rbfSession1)), listOf(ChannelAction.Message.Send(interactiveTxAction.msg))) + is InteractiveTxSessionAction.SendMessage -> Pair(this@WaitForFundingConfirmed.copy(rbfStatus = rbfStatus.copy(rbfSession = rbfSession1)), listOf(ChannelAction.Message.Send(interactiveTxAction.msg))) is InteractiveTxSessionAction.SignSharedTx -> { val replacedCommitment = commitments.latest val signingSession = InteractiveTxSigningSession.create( @@ -192,7 +189,6 @@ data class WaitForFundingConfirmed( commitments.latest.localCommitParams, commitments.latest.remoteCommitParams, rbfSession1.fundingParams, - fundingTxIndex = replacedCommitment.fundingTxIndex, interactiveTxAction.sharedTx, liquidityPurchase = null, localCommitmentIndex = replacedCommitment.localCommit.index, @@ -271,13 +267,12 @@ data class WaitForFundingConfirmed( is Either.Left -> Pair(this@WaitForFundingConfirmed, listOf()) is Either.Right -> { val (commitments1, commitment, actions) = res.value - val nextPerCommitmentPoint = channelKeys().commitmentPoint(1) - val channelReady = ChannelReady(channelId, nextPerCommitmentPoint, TlvStream(ChannelReadyTlv.ShortChannelIdTlv(ShortChannelId.peerId(staticParams.nodeParams.nodeId)))) + val channelReady = run { createChannelReady() } // this is the temporary channel id that we will use in our channel_update message, the goal is to be able to use our channel // as soon as it reaches NORMAL state, and before it is announced on the network // (this id might be updated when the funding tx gets deeply buried, if there was a reorg in the meantime) val shortChannelId = ShortChannelId(cmd.watch.blockHeight, cmd.watch.txIndex, commitment.fundingInput.index.toInt()) - val nextState = WaitForChannelReady(commitments1, shortChannelId, channelReady, this@WaitForFundingConfirmed.remoteCommitNonces) + val nextState = WaitForChannelReady(commitments1, remoteNextCommitNonces, shortChannelId, channelReady) val actions1 = buildList { if (rbfStatus != RbfStatus.None) add(ChannelAction.Message.Send(TxAbort(channelId, InvalidRbfTxConfirmed(channelId, cmd.watch.tx.txid).message))) add(ChannelAction.Message.Send(channelReady)) @@ -339,12 +334,13 @@ data class WaitForFundingConfirmed( logger.info { "will wait for ${staticParams.nodeParams.minDepthBlocks} confirmations" } val fundingScript = action.commitment.commitInput(channelKeys()).txOut.publicKeyScript val watchConfirmed = WatchConfirmed(channelId, action.commitment.fundingTxId, fundingScript, staticParams.nodeParams.minDepthBlocks, WatchConfirmed.ChannelFundingDepthOk) + val remoteNextCommitNonces1 = remoteNextCommitNonces + listOfNotNull(action.nextRemoteCommitNonce?.let { action.commitment.fundingTxId to it }).toMap() val nextState = WaitForFundingConfirmed( commitments.add(action.commitment), + remoteNextCommitNonces1, waitingSinceBlock, deferred, RbfStatus.None, - remoteCommitNonces = action.nextRemoteCommitNonce?.let { mapOf(action.commitment.fundingTxId to it) } ?: mapOf() ) val actions = buildList { add(ChannelAction.Storage.StoreState(nextState)) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreated.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreated.kt index 1aba3518a..2344e3b0a 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreated.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreated.kt @@ -63,7 +63,6 @@ data class WaitForFundingCreated( localCommitParams, remoteCommitParams, interactiveTxSession.fundingParams, - fundingTxIndex = 0, interactiveTxAction.sharedTx, liquidityPurchase, localCommitmentIndex = 0, @@ -99,7 +98,6 @@ data class WaitForFundingCreated( remoteSecondPerCommitmentPoint, liquidityPurchase, channelOrigin, - remoteCommitNonces = session.nextRemoteNonce?.let { mapOf(session.fundingTx.txId to it) } ?: mapOf() ) val actions = buildList { interactiveTxAction.txComplete?.let { add(ChannelAction.Message.Send(it)) } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSigned.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSigned.kt index 39d3a0e0b..00e3c1ab9 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSigned.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSigned.kt @@ -2,9 +2,7 @@ package fr.acinq.lightning.channel.states import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.PublicKey -import fr.acinq.bitcoin.TxId import fr.acinq.bitcoin.crypto.Pack -import fr.acinq.bitcoin.crypto.musig2.IndividualNonce import fr.acinq.bitcoin.utils.Either import fr.acinq.lightning.ChannelEvents import fr.acinq.lightning.LiquidityEvents @@ -12,12 +10,12 @@ import fr.acinq.lightning.ShortChannelId import fr.acinq.lightning.SwapInEvents import fr.acinq.lightning.blockchain.WatchConfirmed import fr.acinq.lightning.channel.* +import fr.acinq.lightning.crypto.NonceGenerator import fr.acinq.lightning.crypto.ShaChain import fr.acinq.lightning.utils.msat import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.toMilliSatoshi import fr.acinq.lightning.wire.* -import kotlinx.serialization.Transient import kotlin.math.absoluteValue /* @@ -47,7 +45,6 @@ data class WaitForFundingSigned( val remoteSecondPerCommitmentPoint: PublicKey, val liquidityPurchase: LiquidityAds.Purchase?, val channelOrigin: Origin?, - @Transient val remoteCommitNonces: Map ) : PersistedChannelState() { override val channelId: ByteVector32 = channelParams.channelId @@ -122,6 +119,7 @@ data class WaitForFundingSigned( remoteNextCommitInfo = Either.Right(remoteSecondPerCommitmentPoint), remotePerCommitmentSecrets = ShaChain.init ) + val remoteNextCommitNonce = signingSession.nextRemoteCommitNonce?.let { mapOf(signingSession.fundingTxId to it) } ?: mapOf() val commonActions = buildList { action.fundingTx.signedTx?.let { add(ChannelAction.Blockchain.PublishTx(it, ChannelAction.Blockchain.PublishTx.Type.FundingTx)) } add(ChannelAction.Blockchain.SendWatch(watchConfirmed)) @@ -166,13 +164,15 @@ data class WaitForFundingSigned( } return if (staticParams.useZeroConf) { logger.info { "channel is using 0-conf, we won't wait for the funding tx to confirm" } - val nextPerCommitmentPoint = channelParams.localParams.channelKeys(keyManager).commitmentPoint(1) - val channelReady = ChannelReady(channelId, nextPerCommitmentPoint, TlvStream(ChannelReadyTlv.ShortChannelIdTlv(ShortChannelId.peerId(staticParams.nodeParams.nodeId)))) + val channelKeys = channelParams.localParams.channelKeys(keyManager) + val nextPerCommitmentPoint = channelKeys.commitmentPoint(1) + val nextCommitNonce = NonceGenerator.verificationNonce(action.commitment.fundingTxId, channelKeys.fundingKey(action.commitment.fundingTxIndex), action.commitment.remoteFundingPubkey, commitIndex = 1) + val channelReady = ChannelReady(channelId, nextPerCommitmentPoint, ShortChannelId.peerId(staticParams.nodeParams.nodeId), nextCommitNonce.publicNonce) // We use part of the funding txid to create a dummy short channel id. // This gives us a probability of collisions of 0.1% for 5 0-conf channels and 1% for 20 // Collisions mean that users may temporarily see incorrect numbers for their 0-conf channels (until they've been confirmed). val shortChannelId = ShortChannelId(0, Pack.int32BE(action.commitment.fundingTxId.value.slice(0, 16).toByteArray()).absoluteValue, fundingInput.outPoint.index.toInt()) - val nextState = WaitForChannelReady(commitments, shortChannelId, channelReady, this@WaitForFundingSigned.remoteCommitNonces) + val nextState = WaitForChannelReady(commitments, remoteNextCommitNonce, shortChannelId, channelReady) val actions = buildList { add(ChannelAction.Storage.StoreState(nextState)) add(ChannelAction.EmitEvent(ChannelEvents.Created(nextState))) @@ -184,10 +184,10 @@ data class WaitForFundingSigned( logger.info { "will wait for ${staticParams.nodeParams.minDepthBlocks} confirmations" } val nextState = WaitForFundingConfirmed( commitments, + remoteNextCommitNonce, currentBlockHeight.toLong(), null, RbfStatus.None, - this@WaitForFundingSigned.remoteCommitNonces ) val actions = buildList { add(ChannelAction.Storage.StoreState(nextState)) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannel.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannel.kt index 506d62d14..aea4cad61 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannel.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannel.kt @@ -107,12 +107,11 @@ data class WaitForOpenChannel( channelKeys, keyManager.swapInOnChainWallet, fundingParams, - 0.msat, - 0.msat, - emptySet(), - fundingContributions.value, - commitTxIndex = 0, - fundingTxIndex = 0 + localCommitIndex = 0, + previousLocalBalance = 0.msat, + previousRemoteBalance = 0.msat, + localHtlcs = emptySet(), + fundingContributions = fundingContributions.value, ) val nextState = WaitForFundingCreated( replyTo, diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForRemotePublishFutureCommitment.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForRemotePublishFutureCommitment.kt index 94b1ae6e5..ba6e573b4 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForRemotePublishFutureCommitment.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForRemotePublishFutureCommitment.kt @@ -8,22 +8,22 @@ import fr.acinq.lightning.channel.ChannelAction import fr.acinq.lightning.channel.ChannelCommand import fr.acinq.lightning.channel.Commitments import fr.acinq.lightning.wire.ChannelReestablish -import kotlinx.serialization.Transient data class WaitForRemotePublishFutureCommitment( override val commitments: Commitments, val remoteChannelReestablish: ChannelReestablish, - @Transient override val remoteCommitNonces: Map ) : ChannelStateWithCommitments() { + override val remoteNextCommitNonces: Map get() = mapOf() + override fun updateCommitments(input: Commitments): ChannelStateWithCommitments = this.copy(commitments = input) override suspend fun ChannelContext.processInternal(cmd: ChannelCommand): Pair> { - return when { - cmd is ChannelCommand.WatchReceived -> when (cmd.watch) { + return when (cmd) { + is ChannelCommand.WatchReceived -> when (cmd.watch) { is WatchSpentTriggered -> handlePotentialForceClose(cmd.watch) is WatchConfirmedTriggered -> Pair(this@WaitForRemotePublishFutureCommitment, listOf()) } - cmd is ChannelCommand.Disconnected -> Pair(Offline(this@WaitForRemotePublishFutureCommitment), listOf()) + is ChannelCommand.Disconnected -> Pair(Offline(this@WaitForRemotePublishFutureCommitment), listOf()) else -> unhandled(cmd) } } 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 3eaa459b2..ef61dde11 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 @@ -48,7 +48,6 @@ data class OpenChannel( val walletInputs: List, val commitTxFeerate: FeeratePerKw, val fundingTxFeerate: FeeratePerKw, - val channelType: ChannelType.SupportedChannelType ) : PeerCommand() /** Consume all the spendable utxos in the wallet state provided to open a channel or splice into an existing channel. */ @@ -1417,7 +1416,7 @@ class Peer( remoteInit = theirInit!!, channelFlags = ChannelFlags(announceChannel = false, nonInitiatorPaysCommitFees = false), channelConfig = ChannelConfig.standard, - channelType = cmd.channelType, + channelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, requestRemoteFunding = null, channelOrigin = null, ) @@ -1659,7 +1658,7 @@ class Peer( remoteInit = theirInit!!, channelFlags = channelFlags, channelConfig = ChannelConfig.standard, - channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, + channelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, requestRemoteFunding = LiquidityAds.RequestFunding(cmd.requestedAmount, cmd.fundingRate, paymentDetails), channelOrigin = Origin.OffChainPayment(cmd.preimage, cmd.paymentAmount, totalFees), ) 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 16a73cccc..6ffb6d22a 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 @@ -25,6 +25,8 @@ JsonSerializers.PrivateKeySerializer::class, JsonSerializers.IndividualNonceSerializer::class, JsonSerializers.PartialSignatureWithNonceSerializer::class, + JsonSerializers.LocalNonceSerializer::class, + JsonSerializers.CloserNoncesSerializer::class, JsonSerializers.TxIdSerializer::class, JsonSerializers.KeyPathSerializer::class, JsonSerializers.SatoshiSerializer::class, @@ -86,6 +88,7 @@ JsonSerializers.FundingCreatedSerializer::class, JsonSerializers.ChannelReadySerializer::class, JsonSerializers.ChannelReadyTlvShortChannelIdTlvSerializer::class, + JsonSerializers.ChannelReadyTlvNextLocalNonceSerializer::class, JsonSerializers.GenericTlvSerializer::class, JsonSerializers.TlvStreamSerializer::class, JsonSerializers.ShutdownTlvSerializer::class, @@ -93,9 +96,8 @@ JsonSerializers.ClosingCompleteTlvSerializer::class, JsonSerializers.ClosingSigTlvSerializer::class, JsonSerializers.ChannelReestablishTlvSerializer::class, + JsonSerializers.ChannelReestablishTlvNextLocalNoncesSerializer::class, JsonSerializers.ChannelReadyTlvSerializer::class, - JsonSerializers.CommitSigTlvAlternativeFeerateSigSerializer::class, - JsonSerializers.CommitSigTlvAlternativeFeerateSigsSerializer::class, JsonSerializers.CommitSigTlvBatchSerializer::class, JsonSerializers.CommitSigTlvPartialSignatureWithNonceSerializer::class, JsonSerializers.CommitSigTlvSerializer::class, @@ -209,11 +211,12 @@ object JsonSerializers { } polymorphic(Tlv::class) { subclass(ChannelReadyTlv.ShortChannelIdTlv::class, ChannelReadyTlvShortChannelIdTlvSerializer) - subclass(CommitSigTlv.AlternativeFeerateSigs::class, CommitSigTlvAlternativeFeerateSigsSerializer) + subclass(ChannelReadyTlv.NextLocalNonce::class, ChannelReadyTlvNextLocalNonceSerializer) subclass(CommitSigTlv.Batch::class, CommitSigTlvBatchSerializer) subclass(CommitSigTlv.PartialSignatureWithNonce::class, CommitSigTlvPartialSignatureWithNonceSerializer) subclass(UpdateAddHtlcTlv.PathKey::class, UpdateAddHtlcTlvPathKeySerializer) subclass(ShutdownTlv.ShutdownNonce::class, ShutdownTlvShutdownNonceSerializer) + subclass(ChannelReestablishTlv.NextLocalNonces::class, ChannelReestablishTlvNextLocalNoncesSerializer) } contextual(Bolt11InvoiceSerializer) contextual(OfferSerializer) @@ -340,12 +343,12 @@ object JsonSerializers { object IndividualSignatureSerializer @Serializable - data class ChannelSpendSignatureSurrogate(val sig: ByteVector64) + data class ChannelSpendSignatureSurrogate(val sig: ByteVector, val nonce: IndividualNonce?) object ChannelSpendSignatureSerializer : SurrogateSerializer( transform = { s -> when (s) { - is ChannelSpendSignature.IndividualSignature -> ChannelSpendSignatureSurrogate(s.sig) - is ChannelSpendSignature.PartialSignatureWithNonce -> ChannelSpendSignatureSurrogate(ByteVector64.Zeroes) // FIXME + is ChannelSpendSignature.IndividualSignature -> ChannelSpendSignatureSurrogate(s.sig, nonce = null) + is ChannelSpendSignature.PartialSignatureWithNonce -> ChannelSpendSignatureSurrogate(s.partialSig, s.nonce) } }, delegateSerializer = ChannelSpendSignatureSurrogate.serializer() @@ -375,6 +378,9 @@ object JsonSerializers { delegateSerializer = RemoteFundingStatusSurrogate.serializer() ) + @Serializer(forClass = Transactions.CloserNonces::class) + object CloserNoncesSerializer + @Serializer(forClass = Transactions.ClosingTx::class) object ClosingTxSerializer @@ -435,6 +441,7 @@ object JsonSerializers { object PublicKeySerializer : StringSerializer() object IndividualNonceSerializer : StringSerializer() object PartialSignatureWithNonceSerializer : StringSerializer() + object LocalNonceSerializer : StringSerializer() object TxIdSerializer : StringSerializer() object KeyPathSerializer : StringSerializer() object ShortChannelIdSerializer : StringSerializer() @@ -547,18 +554,15 @@ object JsonSerializers { @Serializer(forClass = ChannelReadyTlv.ShortChannelIdTlv::class) object ChannelReadyTlvShortChannelIdTlvSerializer + @Serializer(forClass = ChannelReadyTlv.NextLocalNonce::class) + object ChannelReadyTlvNextLocalNonceSerializer + @Serializer(forClass = ShutdownTlv::class) object ShutdownTlvSerializer @Serializer(forClass = ShutdownTlv.ShutdownNonce::class) object ShutdownTlvShutdownNonceSerializer - @Serializer(forClass = CommitSigTlv.AlternativeFeerateSig::class) - object CommitSigTlvAlternativeFeerateSigSerializer - - @Serializer(forClass = CommitSigTlv.AlternativeFeerateSigs::class) - object CommitSigTlvAlternativeFeerateSigsSerializer - @Serializer(forClass = CommitSigTlv.Batch::class) object CommitSigTlvBatchSerializer @@ -580,6 +584,9 @@ object JsonSerializers { @Serializer(forClass = ChannelReestablishTlv::class) object ChannelReestablishTlvSerializer + @Serializer(forClass = ChannelReestablishTlv.NextLocalNonces::class) + object ChannelReestablishTlvNextLocalNoncesSerializer + @Serializer(forClass = GenericTlv::class) object GenericTlvSerializer diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v4/Deserialization.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v4/Deserialization.kt index 371ad8958..421f228cb 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v4/Deserialization.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v4/Deserialization.kt @@ -76,7 +76,7 @@ object Deserialization { val remoteSecondPerCommitmentPoint = readPublicKey() val liquidityPurchase = readNullable { readLiquidityPurchase() } val channelOrigin = readNullable { readChannelOrigin() } - return WaitForFundingSigned(channelParams, signingSession, remoteSecondPerCommitmentPoint, liquidityPurchase, channelOrigin, mapOf()) + return WaitForFundingSigned(channelParams, signingSession, remoteSecondPerCommitmentPoint, liquidityPurchase, channelOrigin) } private fun Input.readWaitForFundingSignedWithPushAmount(): WaitForFundingSigned { @@ -88,7 +88,7 @@ object Deserialization { val remoteSecondPerCommitmentPoint = readPublicKey() val liquidityPurchase = readNullable { readLiquidityPurchase() } val channelOrigin = readNullable { readChannelOrigin() } - return WaitForFundingSigned(channelParams, signingSession, remoteSecondPerCommitmentPoint, liquidityPurchase, channelOrigin, mapOf()) + return WaitForFundingSigned(channelParams, signingSession, remoteSecondPerCommitmentPoint, liquidityPurchase, channelOrigin) } private fun Input.readWaitForFundingSignedLegacy(): WaitForFundingSigned { @@ -99,7 +99,7 @@ object Deserialization { readNumber() val remoteSecondPerCommitmentPoint = readPublicKey() val channelOrigin = readNullable { readChannelOrigin() } - return WaitForFundingSigned(channelParams, signingSession, remoteSecondPerCommitmentPoint, liquidityPurchase = null, channelOrigin, mapOf()) + return WaitForFundingSigned(channelParams, signingSession, remoteSecondPerCommitmentPoint, liquidityPurchase = null, channelOrigin) } private fun Input.readWaitForFundingConfirmedWithPushAmount(): WaitForFundingConfirmed { @@ -116,7 +116,7 @@ object Deserialization { 0x01 -> RbfStatus.WaitingForSigs(readInteractiveTxSigningSession(emptySet(), localCommitParams, remoteCommitParams)) else -> error("unknown discriminator $discriminator for class ${RbfStatus::class}") } - return WaitForFundingConfirmed(commitments, waitingSinceBlock, deferred, rbfStatus, remoteCommitNonces = mapOf()) + return WaitForFundingConfirmed(commitments, remoteNextCommitNonces = mapOf(), waitingSinceBlock, deferred, rbfStatus) } private fun Input.readWaitForFundingConfirmed(): WaitForFundingConfirmed { @@ -130,14 +130,14 @@ object Deserialization { 0x01 -> RbfStatus.WaitingForSigs(readInteractiveTxSigningSession(emptySet(), localCommitParams, remoteCommitParams)) else -> error("unknown discriminator $discriminator for class ${RbfStatus::class}") } - return WaitForFundingConfirmed(commitments, waitingSinceBlock, deferred, rbfStatus, remoteCommitNonces = mapOf()) + return WaitForFundingConfirmed(commitments, remoteNextCommitNonces = mapOf(), waitingSinceBlock, deferred, rbfStatus) } private fun Input.readWaitForChannelReady() = WaitForChannelReady( commitments = readCommitments(), + remoteNextCommitNonces = mapOf(), shortChannelId = ShortChannelId(readNumber()), - lastSent = readLightningMessage() as ChannelReady, - remoteCommitNonces = mapOf() + lastSent = readLightningMessage() as ChannelReady ) private fun Input.readNormal(): Normal { @@ -146,6 +146,7 @@ object Deserialization { val remoteCommitParams = commitments.latest.remoteCommitParams return Normal( commitments = commitments, + remoteNextCommitNonces = mapOf(), shortChannelId = ShortChannelId(readNumber()), channelUpdate = readLightningMessage() as ChannelUpdate, remoteChannelUpdate = readNullable { readLightningMessage() as ChannelUpdate }, @@ -157,7 +158,8 @@ object Deserialization { localShutdown = readNullable { readLightningMessage() as Shutdown }, remoteShutdown = readNullable { readLightningMessage() as Shutdown }, closeCommand = readNullable { readCloseCommand() }, - remoteCommitNonces = mapOf(), localCloseeNonce = null, remoteCloseeNonce = null, localCloserNonces = null + localCloseeNonce = null, + localCloserNonces = null, ) } @@ -184,6 +186,7 @@ object Deserialization { } return Normal( commitments, + remoteNextCommitNonces = mapOf(), shortChannelId, channelUpdate, remoteChannelUpdate, @@ -191,9 +194,7 @@ object Deserialization { localShutdown, remoteShutdown, closeCommand, - remoteCommitNonces = mapOf(), localCloseeNonce = null, - remoteCloseeNonce = null, localCloserNonces = null ) } @@ -221,6 +222,7 @@ object Deserialization { } return Normal( commitments, + remoteNextCommitNonces = mapOf(), shortChannelId, channelUpdate, remoteChannelUpdate, @@ -228,9 +230,7 @@ object Deserialization { localShutdown, remoteShutdown, closeCommand, - remoteCommitNonces = mapOf(), localCloseeNonce = null, - remoteCloseeNonce = null, localCloserNonces = null ) } @@ -246,15 +246,15 @@ object Deserialization { readNumber() ChannelCommand.Close.MutualClose(CompletableDeferred(), localShutdown.scriptPubKey, preferred) } - return ShuttingDown(commitments, localShutdown, remoteShutdown, closeCommand, remoteCommitNonces = mapOf(), localCloseeNonce = null) + return ShuttingDown(commitments, remoteNextCommitNonces = mapOf(), localShutdown, remoteShutdown, closeCommand, localCloseeNonce = null) } private fun Input.readShuttingDown(): ShuttingDown = ShuttingDown( commitments = readCommitments(), + remoteNextCommitNonces = mapOf(), localShutdown = readLightningMessage() as Shutdown, remoteShutdown = readLightningMessage() as Shutdown, closeCommand = readNullable { readCloseCommand() }, - remoteCommitNonces = mapOf(), localCloseeNonce = null ) @@ -280,7 +280,7 @@ object Deserialization { } return Negotiating( commitments, - remoteCommitNonces = mapOf(), + remoteNextCommitNonces = mapOf(), localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, listOf(), @@ -295,6 +295,7 @@ object Deserialization { private fun Input.readNegotiating(): Negotiating = Negotiating( commitments = readCommitments(), + remoteNextCommitNonces = mapOf(), localScript = readDelimitedByteArray().byteVector(), remoteScript = readDelimitedByteArray().byteVector(), proposedClosingTxs = readCollection { @@ -307,7 +308,9 @@ object Deserialization { publishedClosingTxs = readCollection { readClosingTx() }.toList(), waitingSinceBlock = readNumber(), closeCommand = readNullable { readCloseCommand() }, - remoteCommitNonces = mapOf(), localCloseeNonce = null, remoteCloseeNonce = null, localCloserNonces = null + localCloseeNonce = null, + remoteCloseeNonce = null, + localCloserNonces = null ) private fun Input.readClosing(): Closing = Closing( @@ -382,7 +385,6 @@ object Deserialization { private fun Input.readWaitForRemotePublishFutureCommitment(): WaitForRemotePublishFutureCommitment = WaitForRemotePublishFutureCommitment( commitments = readCommitments(), remoteChannelReestablish = readLightningMessage() as ChannelReestablish, - remoteCommitNonces = mapOf() ) private fun Input.readClosed(): Closed = Closed( @@ -677,7 +679,7 @@ object Deserialization { private fun Input.readInteractiveTxSigningSession(htlcs: Set, localCommitParams: CommitParams, remoteCommitParams: CommitParams): InteractiveTxSigningSession { val fundingParams = readInteractiveTxParams() - val fundingTxIndex = readNumber() + readNumber() // the fundingTxIndex was explicitly encoded, even though it is already in the fundingParams val fundingTx = readSignedSharedTransaction() as PartiallySignedSharedTransaction val (localCommit, remoteCommit) = when (val discriminator = read()) { 0 -> Pair(Either.Left(readUnsignedLocalCommitWithHtlcs()), readRemoteCommitWithHtlcs()) @@ -694,7 +696,7 @@ object Deserialization { 5 -> Pair(Either.Right(readLocalCommitWithoutHtlcs(htlcs, fundingParams.remoteFundingPubkey).second), readRemoteCommitWithoutHtlcs(htlcs)) else -> error("unknown discriminator $discriminator for class ${InteractiveTxSigningSession::class}") } - return InteractiveTxSigningSession(fundingParams, fundingTxIndex, fundingTx, localCommitParams, localCommit, remoteCommitParams, remoteCommit, null) + return InteractiveTxSigningSession(fundingParams, fundingTx, localCommitParams, localCommit, remoteCommitParams, remoteCommit, null) } private fun Input.readChannelOrigin(): Origin = when (val discriminator = read()) { diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v5/Deserialization.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v5/Deserialization.kt index 8087151fe..cd71c33ba 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v5/Deserialization.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v5/Deserialization.kt @@ -1,7 +1,6 @@ package fr.acinq.lightning.serialization.channel.v5 import fr.acinq.bitcoin.* -import fr.acinq.bitcoin.crypto.musig2.IndividualNonce import fr.acinq.bitcoin.io.ByteArrayInput import fr.acinq.bitcoin.io.Input import fr.acinq.bitcoin.utils.Either @@ -68,11 +67,11 @@ object Deserialization { remoteSecondPerCommitmentPoint = readPublicKey(), liquidityPurchase = readNullable { readLiquidityPurchase() }, channelOrigin = readNullable { readChannelOrigin() }, - remoteCommitNonces = mapOf() ) private fun Input.readWaitForFundingConfirmed() = WaitForFundingConfirmed( commitments = readCommitments(), + remoteNextCommitNonces = mapOf(), waitingSinceBlock = readNumber(), deferred = readNullable { readLightningMessage() as ChannelReady }, rbfStatus = when (val discriminator = read()) { @@ -80,20 +79,20 @@ object Deserialization { 0x01 -> RbfStatus.WaitingForSigs(readInteractiveTxSigningSession(emptySet())) else -> error("unknown discriminator $discriminator for class ${RbfStatus::class}") }, - remoteCommitNonces = mapOf() ) private fun Input.readWaitForChannelReady() = WaitForChannelReady( commitments = readCommitments(), + remoteNextCommitNonces = mapOf(), shortChannelId = ShortChannelId(readNumber()), lastSent = readLightningMessage() as ChannelReady, - remoteCommitNonces = mapOf() ) private fun Input.readNormal(): Normal { val commitments = readCommitments() return Normal( commitments = commitments, + remoteNextCommitNonces = mapOf(), shortChannelId = ShortChannelId(readNumber()), channelUpdate = readLightningMessage() as ChannelUpdate, remoteChannelUpdate = readNullable { readLightningMessage() as ChannelUpdate }, @@ -105,21 +104,23 @@ object Deserialization { localShutdown = readNullable { readLightningMessage() as Shutdown }, remoteShutdown = readNullable { readLightningMessage() as Shutdown }, closeCommand = readNullable { readCloseCommand() }, - remoteCommitNonces = mapOf(), localCloseeNonce = null, localCloserNonces = null, remoteCloseeNonce = null + localCloseeNonce = null, + localCloserNonces = null, ) } private fun Input.readShuttingDown(): ShuttingDown = ShuttingDown( commitments = readCommitments(), + remoteNextCommitNonces = mapOf(), localShutdown = readLightningMessage() as Shutdown, remoteShutdown = readLightningMessage() as Shutdown, closeCommand = readNullable { readCloseCommand() }, - remoteCommitNonces = mapOf(), localCloseeNonce = null ) private fun Input.readNegotiating(): Negotiating = Negotiating( commitments = readCommitments(), + remoteNextCommitNonces = mapOf(), localScript = readDelimitedByteArray().byteVector(), remoteScript = readDelimitedByteArray().byteVector(), proposedClosingTxs = readCollection { @@ -132,7 +133,9 @@ object Deserialization { publishedClosingTxs = readCollection { readClosingTx() }.toList(), waitingSinceBlock = readNumber(), closeCommand = readNullable { readCloseCommand() }, - remoteCommitNonces = mapOf(), localCloserNonces = null, remoteCloseeNonce = null, localCloseeNonce = null + localCloserNonces = null, + remoteCloseeNonce = null, + localCloseeNonce = null ) private fun Input.readClosing(): Closing = Closing( @@ -192,7 +195,6 @@ object Deserialization { private fun Input.readWaitForRemotePublishFutureCommitment(): WaitForRemotePublishFutureCommitment = WaitForRemotePublishFutureCommitment( commitments = readCommitments(), remoteChannelReestablish = readLightningMessage() as ChannelReestablish, - remoteCommitNonces = mapOf() ) private fun Input.readClosed(): Closed = Closed( @@ -378,7 +380,6 @@ object Deserialization { private fun Input.readInteractiveTxSigningSession(htlcs: Set): InteractiveTxSigningSession = InteractiveTxSigningSession( fundingParams = readInteractiveTxParams(), - fundingTxIndex = readNumber(), fundingTx = readSignedSharedTransaction() as PartiallySignedSharedTransaction, localCommitParams = readCommitParams(), localCommit = when (val discriminator = read()) { @@ -388,7 +389,7 @@ object Deserialization { }, remoteCommitParams = readCommitParams(), remoteCommit = readRemoteCommitWithoutHtlcs(htlcs), - nextRemoteNonce = null + nextRemoteCommitNonce = null ) private fun Input.readChannelOrigin(): Origin = when (val discriminator = read()) { diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v5/Serialization.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v5/Serialization.kt index 3ad77ffef..0864c5fa6 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v5/Serialization.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/serialization/channel/v5/Serialization.kt @@ -415,7 +415,6 @@ object Serialization { private fun Output.writeInteractiveTxSigningSession(s: InteractiveTxSigningSession) = s.run { writeInteractiveTxParams(fundingParams) - writeNumber(s.fundingTxIndex) writeSignedSharedTransaction(fundingTx) writeCommitParams(localCommitParams) when (localCommit) { diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/transactions/Scripts.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/transactions/Scripts.kt index 108b02efc..8d37535cc 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/transactions/Scripts.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/transactions/Scripts.kt @@ -39,8 +39,8 @@ object Scripts { * @return a script witness that matches the msig 2-of-2 pubkey script for pubkey1 and pubkey2 */ fun witness2of2(sig1: ByteVector64, sig2: ByteVector64, pubkey1: PublicKey, pubkey2: PublicKey): ScriptWitness { - val encodedSig1 = der(sig1, SigHash.SIGHASH_ALL) - val encodedSig2 = der(sig2, SigHash.SIGHASH_ALL) + val encodedSig1 = der(sig1, SIGHASH_ALL) + val encodedSig2 = der(sig2, SIGHASH_ALL) val redeemScript = ByteVector(Script.write(multiSig2of2(pubkey1, pubkey2))) return when { LexicographicalOrdering.isLessThan(pubkey1.value, pubkey2.value) -> ScriptWitness(listOf(ByteVector.empty, encodedSig1, encodedSig2, redeemScript)) @@ -123,20 +123,20 @@ object Scripts { * This witness script spends a [toLocalDelayed] output using a local sig after a delay */ fun witnessToRemoteDelayedAfterDelay(localSig: ByteVector64, toRemoteDelayedScript: ByteVector) = - ScriptWitness(listOf(der(localSig, SigHash.SIGHASH_ALL), toRemoteDelayedScript)) + ScriptWitness(listOf(der(localSig, SIGHASH_ALL), toRemoteDelayedScript)) /** * This witness script spends a [toLocalDelayed] output using a local sig after a delay */ fun witnessToLocalDelayedAfterDelay(localSig: ByteVector64, toLocalDelayedScript: ByteVector) = - ScriptWitness(listOf(der(localSig, SigHash.SIGHASH_ALL), ByteVector.empty, toLocalDelayedScript)) + ScriptWitness(listOf(der(localSig, SIGHASH_ALL), ByteVector.empty, toLocalDelayedScript)) /** * This witness script spends (steals) a [toLocalDelayed] output using a revocation key as a punishment * for having published a revoked transaction */ fun witnessToLocalDelayedWithRevocationSig(revocationSig: ByteVector64, toLocalScript: ByteVector) = - ScriptWitness(listOf(der(revocationSig, SigHash.SIGHASH_ALL), ByteVector(byteArrayOf(1)), toLocalScript)) + ScriptWitness(listOf(der(revocationSig, SIGHASH_ALL), ByteVector(byteArrayOf(1)), toLocalScript)) fun htlcOffered(keys: CommitmentPublicKeys, paymentHash: ByteVector32): List = listOf( // @formatter:off @@ -164,16 +164,18 @@ object Scripts { * remote signature is created with SIGHASH_SINGLE || SIGHASH_ANYONECANPAY */ fun witnessHtlcSuccess(localSig: ByteVector64, remoteSig: ByteVector64, preimage: ByteVector32, htlcOfferedScript: ByteVector) = - ScriptWitness(listOf(ByteVector.empty, der(remoteSig, SigHash.SIGHASH_SINGLE or SigHash.SIGHASH_ANYONECANPAY), der(localSig, SigHash.SIGHASH_ALL), preimage, htlcOfferedScript)) + ScriptWitness(listOf(ByteVector.empty, der(remoteSig, SIGHASH_SINGLE or SIGHASH_ANYONECANPAY), der(localSig, SIGHASH_ALL), preimage, htlcOfferedScript)) /** Extract payment preimages from a 2nd-stage HTLC Success transaction's witness script. */ fun extractPreimagesFromHtlcSuccess(tx: Transaction): Set { return tx.txIn.map { it.witness }.mapNotNull { when { - it.stack.size < 5 -> null - !it.stack[0].isEmpty() -> null - it.stack[3].size() != 32 -> null - else -> ByteVector32(it.stack[3]) + it.stack.size != 5 -> null + // anchor-outputs + it.stack[0].isEmpty() && it.stack[3].size() == 32 -> ByteVector32(it.stack[3]) + // taproot + it.stack[2].size() == 32 -> ByteVector32(it.stack[2]) + else -> null } }.toSet() } @@ -183,15 +185,17 @@ object Scripts { * claim its funds using a payment preimage (consumes htlcOffered script from commit tx) */ fun witnessClaimHtlcSuccessFromCommitTx(localSig: ByteVector64, preimage: ByteVector32, htlcOffered: ByteVector) = - ScriptWitness(listOf(der(localSig, SigHash.SIGHASH_ALL), preimage, htlcOffered)) + ScriptWitness(listOf(der(localSig, SIGHASH_ALL), preimage, htlcOffered)) /** Extract payment preimages from a claim-htlc transaction. */ fun extractPreimagesFromClaimHtlcSuccess(tx: Transaction): Set { return tx.txIn.map { it.witness }.mapNotNull { when { - it.stack.size < 3 -> null - it.stack[1].size() != 32 -> null - else -> ByteVector32(it.stack[1]) + // anchor-outputs + it.stack.size == 3 && it.stack[1].size() == 32 -> ByteVector32(it.stack[1]) + // taproot + it.stack.size == 4 && it.stack[1].size() == 32 -> ByteVector32(it.stack[1]) + else -> null } }.toSet() } @@ -224,21 +228,21 @@ object Scripts { * remote signature is created with SIGHASH_SINGLE || SIGHASH_ANYONECANPAY */ fun witnessHtlcTimeout(localSig: ByteVector64, remoteSig: ByteVector64, htlcOfferedScript: ByteVector) = - ScriptWitness(listOf(ByteVector.empty, der(remoteSig, SigHash.SIGHASH_SINGLE or SigHash.SIGHASH_ANYONECANPAY), der(localSig, SigHash.SIGHASH_ALL), ByteVector.empty, htlcOfferedScript)) + ScriptWitness(listOf(ByteVector.empty, der(remoteSig, SIGHASH_SINGLE or SIGHASH_ANYONECANPAY), der(localSig, SIGHASH_ALL), ByteVector.empty, htlcOfferedScript)) /** * If remote publishes its commit tx where there was a local->remote htlc, then local uses this script to * claim its funds after timeout (consumes htlcReceived script from commit tx) */ fun witnessClaimHtlcTimeoutFromCommitTx(localSig: ByteVector64, htlcReceivedScript: ByteVector) = - ScriptWitness(listOf(der(localSig, SigHash.SIGHASH_ALL), ByteVector.empty, htlcReceivedScript)) + ScriptWitness(listOf(der(localSig, SIGHASH_ALL), ByteVector.empty, htlcReceivedScript)) /** * This witness script spends (steals) a [[htlcOffered]] or [[htlcReceived]] output using a revocation key as a punishment * for having published a revoked transaction */ fun witnessHtlcWithRevocationSig(commitKeys: RemoteCommitmentKeys, revocationSig: ByteVector64, htlcScript: ByteVector) = - ScriptWitness(listOf(der(revocationSig, SigHash.SIGHASH_ALL), commitKeys.revocationPublicKey.value, htlcScript)) + ScriptWitness(listOf(der(revocationSig, SIGHASH_ALL), commitKeys.revocationPublicKey.value, htlcScript)) /** * Specific scripts for taproot channels @@ -255,12 +259,10 @@ object Scripts { /** * Sort and aggregate the public keys of a musig2 session. * - * @param pubkey1 public key - * @param pubkey2 public key * @return the aggregated public key - * @see [[fr.acinq.bitcoin.Musig2.aggregateKeys()]] + * @see [fr.acinq.bitcoin.crypto.musig2.Musig2.aggregateKeys] */ - fun musig2Aggregate(pubkey1: PublicKey, pubkey2: PublicKey): XonlyPublicKey = Musig2.aggregateKeys(listOf(pubkey1, pubkey2).sortedWith { p1, p2 -> LexicographicalOrdering.compare(p1, p2) }) + fun musig2Aggregate(pubkey1: PublicKey, pubkey2: PublicKey): XonlyPublicKey = Musig2.aggregateKeys(sort(listOf(pubkey1, pubkey2))) /** * "Nothing Up My Sleeve" point, for which there is no known private key. @@ -277,7 +279,6 @@ object Scripts { */ fun anchor(anchorKey: PublicKey): List = Script.pay2tr(anchorKey.xOnly(), anchorScriptTree) - /** * Script that can be spent with the revocation key and reveals the delayed payment key to allow observers to claim * unused anchor outputs. @@ -298,7 +299,7 @@ object Scripts { * @return a script that will be used to add a "to local key" leaf to a script tree */ private fun toLocalDelayed(keys: CommitmentPublicKeys, toSelfDelay: CltvExpiryDelta): List = listOf( - OP_PUSHDATA(keys.localDelayedPaymentPublicKey.xOnly()), OP_CHECKSIGVERIFY, Scripts.encodeNumber(toSelfDelay.toLong()), OP_CHECKSEQUENCEVERIFY + OP_PUSHDATA(keys.localDelayedPaymentPublicKey.xOnly()), OP_CHECKSIGVERIFY, encodeNumber(toSelfDelay.toLong()), OP_CHECKSEQUENCEVERIFY ) data class ToLocalScriptTree(val localDelayed: ScriptTree.Leaf, val revocation: ScriptTree.Leaf) { @@ -386,7 +387,7 @@ object Scripts { Script.witnessScriptPathPay2tr( commitKeys.revocationPublicKey.xOnly(), timeout, - ScriptWitness(listOf(Taproot.encodeSig(remoteSig, htlcRemoteSighash(Transactions.CommitmentFormat.SimpleTaprootChannels)), localSig)), + ScriptWitness(listOf(encodeSig(remoteSig, htlcRemoteSighash(Transactions.CommitmentFormat.SimpleTaprootChannels)), localSig)), scriptTree ) @@ -439,7 +440,7 @@ object Scripts { Script.witnessScriptPathPay2tr( commitKeys.revocationPublicKey.xOnly(), success, - ScriptWitness(listOf(Taproot.encodeSig(remoteSig, htlcRemoteSighash(Transactions.CommitmentFormat.SimpleTaprootChannels)), localSig, paymentPreimage)), + ScriptWitness(listOf(encodeSig(remoteSig, htlcRemoteSighash(Transactions.CommitmentFormat.SimpleTaprootChannels)), localSig, paymentPreimage)), scriptTree ) @@ -463,11 +464,6 @@ object Scripts { fun htlcDelayedScriptTree(keys: CommitmentPublicKeys, toSelfDelay: CltvExpiryDelta): ScriptTree.Leaf = ScriptTree.Leaf(toLocalDelayed(keys, toSelfDelay)) - /** - * Script used for the output of pre-signed HTLC 2nd-stage transactions. - */ - fun htlcDelayed(keys: CommitmentPublicKeys, toSelfDelay: CltvExpiryDelta): List = - Script.pay2tr(keys.revocationPublicKey.xOnly(), htlcDelayedScriptTree(keys, toSelfDelay)) } } \ No newline at end of file diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/transactions/Transactions.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/transactions/Transactions.kt index 67c45dcb6..26b29fbde 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/transactions/Transactions.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/transactions/Transactions.kt @@ -149,12 +149,13 @@ object Transactions { */ data class TaprootScriptPath(val internalKey: XonlyPublicKey, val scriptTree: ScriptTree, val leafHash: ByteVector32) : RedeemInfo() { val leaf: ScriptTree.Leaf = scriptTree.findScript(leafHash) ?: throw IllegalArgumentException("script tree must contain the provided leaf") - val redeemScript: ByteVector = leaf.script override val pubkeyScript: ByteVector = Script.write(Script.pay2tr(internalKey, scriptTree)).byteVector() } } - data class LocalNonce(val secretNonce: SecretNonce, val publicNonce: IndividualNonce) + data class LocalNonce(val secretNonce: SecretNonce, val publicNonce: IndividualNonce) { + override fun toString(): String = publicNonce.toString() + } sealed class TransactionWithInputInfo { abstract val input: InputInfo @@ -175,10 +176,6 @@ object Transactions { fun sign(key: PrivateKey, sigHash: Int, redeemInfo: RedeemInfo, extraUtxos: Map): ByteVector64 { val spentOutputs = buildSpentOutputs(extraUtxos) - // Note that we only need to provide details about all transaction inputs when using taproot, but we want to - // test that we're always correctly providing all inputs in all code paths to benefit from our existing test coverage. - val inputsMap = extraUtxos + (input.outPoint to input.txOut) - tx.txIn.forEach { require(inputsMap.contains(it.outPoint)) { "cannot sign txId=${tx.txid}: missing input details for ${it.outPoint}" } } return when (redeemInfo) { is RedeemInfo.P2wsh -> { val sigDER = tx.signInput(inputIndex, redeemInfo.redeemScript, sigHash, amountIn, SigVersion.SIGVERSION_WITNESS_V0, key) @@ -187,7 +184,6 @@ object Transactions { is RedeemInfo.TaprootKeyPath -> { tx.signInputTaprootKeyPath(key, inputIndex, spentOutputs, sigHash, redeemInfo.scriptTree_opt) } - is RedeemInfo.TaprootScriptPath -> { tx.signInputTaprootScriptPath(key, inputIndex, spentOutputs, sigHash, redeemInfo.leafHash) } @@ -206,7 +202,6 @@ object Transactions { val data = tx.hashForSigningTaprootKeyPath(inputIndex, listOf(input.txOut), sigHash) Crypto.verifySignatureSchnorr(data, sig, publicKey.xOnly()) } - is RedeemInfo.TaprootScriptPath -> { val data = tx.hashForSigningTaprootScriptPath(inputIndex, listOf(input.txOut), sigHash, redeemInfo.leafHash) Crypto.verifySignatureSchnorr(data, sig, publicKey.xOnly()) @@ -238,7 +233,7 @@ object Transactions { return ChannelSpendSignature.IndividualSignature(sig) } - /** Create a partial transaction for the channel's musig2 funding output when using a [[TaprootCommitmentFormat]]. */ + /** Create a partial transaction for the channel's musig2 funding output when using [CommitmentFormat.SimpleTaprootChannels]. */ fun partialSign( localFundingKey: PrivateKey, remoteFundingPubkey: PublicKey, @@ -257,7 +252,7 @@ object Transactions { return tx.updateWitness(inputIndex, witness) } - /** Aggregate local and remote channel spending partial signatures for a [[TaprootCommitmentFormat]]. */ + /** Aggregate local and remote channel spending partial signatures when using [CommitmentFormat.SimpleTaprootChannels]. */ fun aggregateSigs( localFundingPubkey: PublicKey, remoteFundingPubkey: PublicKey, @@ -274,9 +269,10 @@ object Transactions { Scripts.sort(listOf(localFundingPubkey, remoteFundingPubkey)), listOf(localSig.nonce, remoteSig.nonce), null - ) - .map { Script.witnessKeyPathPay2tr(it) } - .map { tx.updateWitness(inputIndex, it) } + ).map { + val witness = Script.witnessKeyPathPay2tr(it) + tx.updateWitness(inputIndex, witness) + } } /** Verify a signature received from the remote channel participant. */ @@ -303,7 +299,6 @@ object Transactions { scriptTree = null ) } - } /** This transaction collaboratively spends the channel funding output to change its capacity. */ @@ -533,7 +528,6 @@ object Transactions { val sig = sign(commitKeys.ourDelayedPaymentKey, sigHash, RedeemInfo.P2wsh(redeemScript), extraUtxos = mapOf()) Scripts.witnessToLocalDelayedAfterDelay(sig, redeemScript) } - CommitmentFormat.SimpleTaprootChannels -> { val scriptTree: ScriptTree.Leaf = Scripts.Taproot.htlcDelayedScriptTree(commitKeys.publicKeys, toLocalDelay) val redeemInfo = RedeemInfo.TaprootScriptPath(commitKeys.revocationPublicKey.xOnly(), scriptTree, scriptTree.hash()) @@ -767,8 +761,7 @@ object Transactions { commitmentFormat: CommitmentFormat ): Either { val redeemInfo = when (commitmentFormat) { - CommitmentFormat.AnchorOutputs -> - RedeemInfo.P2wsh(Scripts.toLocalDelayed(commitKeys.publicKeys, toLocalDelay)) + CommitmentFormat.AnchorOutputs -> RedeemInfo.P2wsh(Scripts.toLocalDelayed(commitKeys.publicKeys, toLocalDelay)) CommitmentFormat.SimpleTaprootChannels -> { val toLocalTree = Scripts.Taproot.toLocalScriptTree(commitKeys.publicKeys, toLocalDelay) RedeemInfo.TaprootScriptPath(NUMS_POINT.xOnly(), toLocalTree.scriptTree, toLocalTree.localDelayed.hash()) @@ -1306,7 +1299,7 @@ object Transactions { } /** - * When sending [[fr.acinq.lightning.wire.ClosingComplete]], we use a different nonce for each closing transaction we create. + * When sending [fr.acinq.lightning.wire.ClosingComplete], we use a different nonce for each closing transaction we create. * We generate nonces for all variants of the closing transaction for simplicity, even though we never use them all. */ data class CloserNonces(val localAndRemote: LocalNonce, val localOnly: LocalNonce, val remoteOnly: LocalNonce) { 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 82ed2d292..64e31ec49 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 @@ -7,12 +7,10 @@ import fr.acinq.bitcoin.io.Output import fr.acinq.lightning.Features import fr.acinq.lightning.MilliSatoshi import fr.acinq.lightning.ShortChannelId -import fr.acinq.lightning.blockchain.fee.FeeratePerKw import fr.acinq.lightning.channel.ChannelSpendSignature import fr.acinq.lightning.channel.ChannelType import fr.acinq.lightning.serialization.InputExtensions.readIndividualNonce import fr.acinq.lightning.utils.msat -import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.toByteVector64 sealed class ChannelTlv : Tlv { @@ -59,8 +57,12 @@ sealed class ChannelTlv : Tlv { } } - // TLV used to upgrade to "simple taproot channels" format during splices. - // We cannot reuse the channel_type TLV defined above because the tag is different + /** + * TLV used to upgrade to [ChannelType.SupportedChannelType.SimpleTaprootChannels] during splices. + * We don't reuse the [ChannelTypeTlv] above because updating the channel type during a splice is a custom + * protocol extension that may not be accepted into the BOLTs. If it is eventually added to the BOLTs, we + * should remove this TLV in favor of [ChannelTypeTlv]. + */ data class SpliceChannelTypeTlv(val channelType: ChannelType) : ChannelTlv() { override val tag: Long get() = SpliceChannelTypeTlv.tag @@ -139,6 +141,7 @@ sealed class ChannelReadyTlv : Tlv { } } + /** Verification nonce used for the next commitment transaction that will be signed (when using taproot channels). */ data class NextLocalNonce(val nonce: IndividualNonce) : ChannelReadyTlv() { override val tag: Long get() = NextLocalNonce.tag override fun write(out: Output) = LightningCodecs.writeBytes(nonce.toByteArray(), out) @@ -151,6 +154,7 @@ sealed class ChannelReadyTlv : Tlv { } sealed class CommitSigTlv : Tlv { + /** 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 @@ -172,72 +176,6 @@ sealed class CommitSigTlv : Tlv { } } - data class AlternativeFeerateSig(val feerate: FeeratePerKw, val sig: ByteVector64) - - /** - * When there are no pending HTLCs, we provide a list of signatures for the commitment transaction signed at various feerates. - * This gives more options to the remote node to recover their funds if the user disappears without closing channels. - */ - data class AlternativeFeerateSigs(val sigs: List) : CommitSigTlv() { - override val tag: Long get() = AlternativeFeerateSigs.tag - override fun write(out: Output) { - LightningCodecs.writeByte(sigs.size, out) - sigs.forEach { - LightningCodecs.writeU32(it.feerate.toLong().toInt(), out) - LightningCodecs.writeBytes(it.sig, out) - } - } - - companion object : TlvValueReader { - const val tag: Long = 0x47010001 - override fun read(input: Input): AlternativeFeerateSigs { - val count = LightningCodecs.byte(input) - val sigs = (0 until count).map { - AlternativeFeerateSig( - FeeratePerKw(LightningCodecs.u32(input).toLong().sat), - LightningCodecs.bytes(input, 64).toByteVector64() - ) - } - return AlternativeFeerateSigs(sigs) - } - } - } - - data class AlternativeFeeratePartialSig(val feerate: FeeratePerKw, val psig: ChannelSpendSignature.PartialSignatureWithNonce) - - /** - * When there are no pending HTLCs, we provide a list of signatures for the commitment transaction signed at various feerates. - * This gives more options to the remote node to recover their funds if the user disappears without closing channels. - */ - data class AlternativeFeeratePartialSigs(val psigs: List) : CommitSigTlv() { - override val tag: Long get() = AlternativeFeeratePartialSigs.tag - override fun write(out: Output) { - LightningCodecs.writeByte(psigs.size, out) - psigs.forEach { - LightningCodecs.writeU32(it.feerate.toLong().toInt(), out) - LightningCodecs.writeBytes(it.psig.partialSig, out) - LightningCodecs.writeBytes(it.psig.nonce.toByteArray(), out) - } - } - - companion object : TlvValueReader { - const val tag: Long = 0x47010003 - override fun read(input: Input): AlternativeFeeratePartialSigs { - val count = LightningCodecs.byte(input) - val sigs = (0 until count).map { - AlternativeFeeratePartialSig( - FeeratePerKw(LightningCodecs.u32(input).toLong().sat), - ChannelSpendSignature.PartialSignatureWithNonce( - LightningCodecs.bytes(input, 32).byteVector32(), - IndividualNonce(LightningCodecs.bytes(input, 66)) - ) - ) - } - return AlternativeFeeratePartialSigs(sigs) - } - } - } - data class Batch(val size: Int) : CommitSigTlv() { override val tag: Long get() = Batch.tag override fun write(out: Output) = LightningCodecs.writeTU16(size, out) @@ -326,6 +264,7 @@ sealed class ChannelReestablishTlv : Tlv { } sealed class ShutdownTlv : Tlv { + /** When closing taproot channels, local nonce that will be used to sign the remote closing transaction. */ data class ShutdownNonce(val nonce: IndividualNonce) : ShutdownTlv() { override val tag: Long get() = ShutdownNonce.tag 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 0d3f03f29..5e83647a8 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 @@ -13,7 +13,6 @@ import fr.acinq.lightning.channel.ChannelSpendSignature import fr.acinq.lightning.channel.ChannelType import fr.acinq.lightning.router.Announcements import fr.acinq.lightning.utils.* -import fr.acinq.lightning.wire.ChannelTlv.SpliceChannelTypeTlv import fr.acinq.secp256k1.Hex import io.ktor.utils.io.charsets.* import io.ktor.utils.io.core.* @@ -456,10 +455,8 @@ data class TxComplete( val commitNonces: TxCompleteTlv.CommitNonces? = tlvs.get() val fundingNonce: IndividualNonce? = tlvs.get()?.nonce - constructor(channelId: ByteVector32, commitNonces: TxCompleteTlv.CommitNonces?, fundingNonce: IndividualNonce?, swapInNonces: List) - : this(channelId, TlvStream(setOfNotNull(TxCompleteTlv.SwapInNonces(swapInNonces), commitNonces, fundingNonce?.let { TxCompleteTlv.FundingInputNonce(it) }))) - - constructor(channelId: ByteVector32, swapInNonces: List) : this(channelId, TlvStream(TxCompleteTlv.SwapInNonces(swapInNonces))) + constructor(channelId: ByteVector32, commitNonce: IndividualNonce, nextCommitNonce: IndividualNonce, fundingNonce: IndividualNonce?, swapInNonces: List) + : this(channelId, TlvStream(setOfNotNull(TxCompleteTlv.SwapInNonces(swapInNonces), TxCompleteTlv.CommitNonces(commitNonce, nextCommitNonce), fundingNonce?.let { TxCompleteTlv.FundingInputNonce(it) }))) override fun write(out: Output) { LightningCodecs.writeBytes(channelId.toByteArray(), out) @@ -904,6 +901,13 @@ data class ChannelReady( val nextPerCommitmentPoint: PublicKey, val tlvStream: TlvStream = TlvStream.empty() ) : ChannelMessage, HasChannelId { + + constructor(channelId: ByteVector32, nextPerCommitmentPoint: PublicKey, alias: ShortChannelId, nextLocalNonce: IndividualNonce) : this( + channelId, + nextPerCommitmentPoint, + TlvStream(ChannelReadyTlv.ShortChannelIdTlv(alias), ChannelReadyTlv.NextLocalNonce(nextLocalNonce)) + ) + override val type: Long get() = ChannelReady.type val alias: ShortChannelId? = tlvStream.get()?.alias val nextLocalNonce: IndividualNonce? = tlvStream.get()?.nonce @@ -967,19 +971,6 @@ data class SpliceInit( val requestFunding: LiquidityAds.RequestFunding? = tlvStream.get()?.request val channelType: ChannelType? = tlvStream.get()?.channelType - constructor(channelId: ByteVector32, fundingContribution: Satoshi, feerate: FeeratePerKw, lockTime: Long, fundingPubkey: PublicKey, requestFunding: LiquidityAds.RequestFunding?) : this( - channelId, - fundingContribution, - feerate, - lockTime, - fundingPubkey, - TlvStream( - setOfNotNull( - requestFunding?.let { ChannelTlv.RequestFundingTlv(it) }, - ) - ) - ) - constructor(channelId: ByteVector32, fundingContribution: Satoshi, feerate: FeeratePerKw, lockTime: Long, fundingPubkey: PublicKey, requestFunding: LiquidityAds.RequestFunding?, channelType: ChannelType?) : this( channelId, fundingContribution, @@ -1036,16 +1027,6 @@ data class SpliceAck( val feeCreditUsed: MilliSatoshi = tlvStream.get()?.amount ?: 0.msat val channelType: ChannelType? = tlvStream.get()?.channelType - constructor(channelId: ByteVector32, fundingContribution: Satoshi, fundingPubkey: PublicKey, willFund: LiquidityAds.WillFund?) : this( - channelId, - fundingContribution, - fundingPubkey, - TlvStream( - setOfNotNull( - willFund?.let { ChannelTlv.ProvideFundingTlv(it) } - )) - ) - constructor(channelId: ByteVector32, fundingContribution: Satoshi, fundingPubkey: PublicKey, willFund: LiquidityAds.WillFund?, channelType: ChannelType?) : this( channelId, fundingContribution, @@ -1054,7 +1035,8 @@ data class SpliceAck( setOfNotNull( willFund?.let { ChannelTlv.ProvideFundingTlv(it) }, channelType?.let { ChannelTlv.SpliceChannelTypeTlv(it) } - )) + ) + ) ) override fun write(out: Output) { @@ -1072,7 +1054,7 @@ data class SpliceAck( ChannelTlv.RequireConfirmedInputsTlv.tag to ChannelTlv.RequireConfirmedInputsTlv as TlvValueReader, ChannelTlv.ProvideFundingTlv.tag to ChannelTlv.ProvideFundingTlv as TlvValueReader, ChannelTlv.FeeCreditUsedTlv.tag to ChannelTlv.FeeCreditUsedTlv.Companion as TlvValueReader, - SpliceChannelTypeTlv.tag to ChannelTlv.SpliceChannelTypeTlv as TlvValueReader, + ChannelTlv.SpliceChannelTypeTlv.tag to ChannelTlv.SpliceChannelTypeTlv as TlvValueReader, ) override fun read(input: Input): SpliceAck = SpliceAck( @@ -1273,14 +1255,20 @@ data class CommitSig( val tlvStream: TlvStream = TlvStream.empty() ) : CommitSigs() { - constructor(channelId: ByteVector32, signature: ChannelSpendSignature, htlcSignatures: List, batchSize: Int) : this( + constructor(channelId: ByteVector32, signature: ChannelSpendSignature, htlcSignatures: List, batchSize: Int) : this( channelId, - if (signature is ChannelSpendSignature.IndividualSignature) signature else ChannelSpendSignature.IndividualSignature(ByteVector64.Zeroes), + when (signature) { + is ChannelSpendSignature.IndividualSignature -> signature + is ChannelSpendSignature.PartialSignatureWithNonce -> ChannelSpendSignature.IndividualSignature(ByteVector64.Zeroes) + }, htlcSignatures, TlvStream( setOfNotNull( if (batchSize > 1) CommitSigTlv.Batch(batchSize) else null, - if (signature is ChannelSpendSignature.PartialSignatureWithNonce) CommitSigTlv.PartialSignatureWithNonce(signature) else null + when (signature) { + is ChannelSpendSignature.PartialSignatureWithNonce -> CommitSigTlv.PartialSignatureWithNonce(signature) + is ChannelSpendSignature.IndividualSignature -> null + } ) ) ) @@ -1289,7 +1277,6 @@ data class CommitSig( val partialSignature: ChannelSpendSignature.PartialSignatureWithNonce? = tlvStream.get()?.psig val sigOrPartialSig: ChannelSpendSignature = partialSignature ?: signature - val alternativeFeerateSigs: List = tlvStream.get()?.sigs ?: listOf() val batchSize: Int = tlvStream.get()?.size ?: 1 override fun write(out: Output) { @@ -1306,9 +1293,7 @@ data class CommitSig( @Suppress("UNCHECKED_CAST") val readers = mapOf( CommitSigTlv.PartialSignatureWithNonce.tag to CommitSigTlv.PartialSignatureWithNonce.Companion as TlvValueReader, - CommitSigTlv.AlternativeFeerateSigs.tag to CommitSigTlv.AlternativeFeerateSigs.Companion as TlvValueReader, CommitSigTlv.Batch.tag to CommitSigTlv.Batch.Companion as TlvValueReader, - CommitSigTlv.AlternativeFeeratePartialSigs.tag to CommitSigTlv.AlternativeFeeratePartialSigs.Companion as TlvValueReader, ) override fun read(input: Input): CommitSig { @@ -1345,6 +1330,18 @@ data class RevokeAndAck( val nextPerCommitmentPoint: PublicKey, val tlvStream: TlvStream = TlvStream.empty() ) : HtlcMessage, HasChannelId, RequirePeerStorageStore { + + constructor(channelId: ByteVector32, perCommitmentSecret: PrivateKey, nextPerCommitmentPoint: PublicKey, nextCommitNonces: List>) : this( + channelId, + perCommitmentSecret, + nextPerCommitmentPoint, + TlvStream( + setOfNotNull( + if (nextCommitNonces.isNotEmpty()) RevokeAndAckTlv.NextLocalNonces(nextCommitNonces) else null + ) + ) + ) + val nextCommitNonces: Map = tlvStream.get()?.nonces?.toMap() ?: mapOf() override val type: Long get() = RevokeAndAck.type @@ -1406,10 +1403,34 @@ data class ChannelReestablish( val myCurrentPerCommitmentPoint: PublicKey, val tlvStream: TlvStream = TlvStream.empty() ) : HasChannelId { + + constructor( + channelId: ByteVector32, + nextLocalCommitmentNumber: Long, + nextRemoteRevocationNumber: Long, + yourLastCommitmentSecret: PrivateKey, + myCurrentPerCommitmentPoint: PublicKey, + nextCommitNonces: List>, + nextFundingTxId: TxId? = null, + currentCommitNonce: IndividualNonce? = null + ) : this( + channelId = channelId, + nextLocalCommitmentNumber = nextLocalCommitmentNumber, + nextRemoteRevocationNumber = nextRemoteRevocationNumber, + yourLastCommitmentSecret = yourLastCommitmentSecret, + myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint, + tlvStream = TlvStream( + setOfNotNull( + if (nextCommitNonces.isNotEmpty()) ChannelReestablishTlv.NextLocalNonces(nextCommitNonces) else null, + nextFundingTxId?.let { ChannelReestablishTlv.NextFunding(it) }, + currentCommitNonce?.let { ChannelReestablishTlv.CurrentCommitNonce(it) }, + ) + ) + ) + override val type: Long get() = ChannelReestablish.type val nextFundingTxId: TxId? = tlvStream.get()?.txId - val nextCommitNonces: Map = tlvStream.get()?.nonces?.toMap() ?: mapOf() val currentCommitNonce: IndividualNonce? = tlvStream.get()?.nonce @@ -1625,6 +1646,9 @@ data class Shutdown( val scriptPubKey: ByteVector, val tlvStream: TlvStream = TlvStream.empty() ) : ChannelMessage, HasChannelId, RequirePeerStorageStore, ForbiddenMessageDuringSplice { + + constructor(channelId: ByteVector32, scriptPubKey: ByteVector, localNonce: IndividualNonce) : this(channelId, scriptPubKey, TlvStream(ShutdownTlv.ShutdownNonce(localNonce))) + override val type: Long get() = Shutdown.type val closeeNonce: IndividualNonce? = tlvStream.get()?.nonce diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/CommitmentsTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/CommitmentsTestsCommon.kt index 871a18545..794cc5c62 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/CommitmentsTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/CommitmentsTestsCommon.kt @@ -65,7 +65,7 @@ class CommitmentsTestsCommon : LightningTestSuite(), LoggingContext { assertEquals(bc1.availableBalanceForSend(), b) assertEquals(bc1.availableBalanceForReceive(), a - p - htlcOutputFee) - val (ac2, commit1) = ac1.sendCommit(alice.channelKeys, alice.state.remoteCommitNonces, logger).right!! + val (ac2, commit1) = ac1.sendCommit(alice.channelKeys, alice.state.remoteNextCommitNonces, logger).right!! assertEquals(ac2.availableBalanceForSend(), a - p - htlcOutputFee) assertEquals(ac2.availableBalanceForReceive(), b) @@ -77,7 +77,7 @@ class CommitmentsTestsCommon : LightningTestSuite(), LoggingContext { assertEquals(ac3.availableBalanceForSend(), a - p - htlcOutputFee) assertEquals(ac3.availableBalanceForReceive(), b) - val (bc3, commit2) = bc2.sendCommit(bob.channelKeys, bob.state.remoteCommitNonces, logger).right!! + val (bc3, commit2) = bc2.sendCommit(bob.channelKeys, bob.state.remoteNextCommitNonces, logger).right!! assertEquals(bc3.availableBalanceForSend(), b) assertEquals(bc3.availableBalanceForReceive(), a - p - htlcOutputFee) @@ -151,7 +151,7 @@ class CommitmentsTestsCommon : LightningTestSuite(), LoggingContext { assertEquals(bc1.availableBalanceForSend(), b) assertEquals(bc1.availableBalanceForReceive(), a - p - htlcOutputFee) - val (ac2, commit1) = ac1.sendCommit(alice.channelKeys, alice.state.remoteCommitNonces, logger).right!! + val (ac2, commit1) = ac1.sendCommit(alice.channelKeys, alice.state.remoteNextCommitNonces, logger).right!! assertEquals(ac2.availableBalanceForSend(), a - p - htlcOutputFee) assertEquals(ac2.availableBalanceForReceive(), b) @@ -163,7 +163,7 @@ class CommitmentsTestsCommon : LightningTestSuite(), LoggingContext { assertEquals(ac3.availableBalanceForSend(), a - p - htlcOutputFee) assertEquals(ac3.availableBalanceForReceive(), b) - val (bc3, commit2) = bc2.sendCommit(bob.channelKeys, bob.state.remoteCommitNonces, logger).right!! + val (bc3, commit2) = bc2.sendCommit(bob.channelKeys, bob.state.remoteNextCommitNonces, logger).right!! assertEquals(bc3.availableBalanceForSend(), b) assertEquals(bc3.availableBalanceForReceive(), a - p - htlcOutputFee) @@ -212,8 +212,6 @@ class CommitmentsTestsCommon : LightningTestSuite(), LoggingContext { @Test fun `correct values for availableForSend - availableForReceive -- multiple htlcs`() { val (alice, bob) = reachNormal(aliceFundingAmount = 800_000.sat, bobFundingAmount = 200_000.sat, channelType = ChannelType.SupportedChannelType.SimpleTaprootChannels) - var aNonces = alice.state.remoteCommitNonces - var bNonces = bob.state.remoteCommitNonces val a = 786_220_000.msat // initial balance alice val b = 200_000_000.msat // initial balance bob @@ -260,7 +258,7 @@ class CommitmentsTestsCommon : LightningTestSuite(), LoggingContext { assertEquals(ac3.availableBalanceForSend(), a - p1 - htlcOutputFee - p2 - htlcOutputFee) assertEquals(ac3.availableBalanceForReceive(), b - p3) - val (ac4, commit1) = ac3.sendCommit(alice.channelKeys, alice.state.remoteCommitNonces, logger).right!! + val (ac4, commit1) = ac3.sendCommit(alice.channelKeys, alice.state.remoteNextCommitNonces, logger).right!! assertEquals(ac4.availableBalanceForSend(), a - p1 - htlcOutputFee - p2 - htlcOutputFee) assertEquals(ac4.availableBalanceForReceive(), b - p3) @@ -272,7 +270,7 @@ class CommitmentsTestsCommon : LightningTestSuite(), LoggingContext { assertEquals(ac5.availableBalanceForSend(), a - p1 - htlcOutputFee - p2 - htlcOutputFee) assertEquals(ac5.availableBalanceForReceive(), b - p3) - val (bc5, commit2) = bc4.sendCommit(bob.channelKeys, bob.state.remoteCommitNonces, logger).right!! + val (bc5, commit2) = bc4.sendCommit(bob.channelKeys, bob.state.remoteNextCommitNonces, logger).right!! assertEquals(bc5.availableBalanceForSend(), b - p3) assertEquals(bc5.availableBalanceForReceive(), a - p1 - htlcOutputFee - p2 - htlcOutputFee) @@ -422,7 +420,7 @@ class CommitmentsTestsCommon : LightningTestSuite(), LoggingContext { ChannelParams( channelId = randomBytes32(), channelConfig = ChannelConfig.standard, - channelFeatures = ChannelFeatures(ChannelType.SupportedChannelType.AnchorOutputs.features), + channelFeatures = ChannelFeatures(ChannelType.SupportedChannelType.SimpleTaprootChannels.features), localParams = localChannelParams, remoteParams = remoteChannelParams, channelFlags = ChannelFlags(announceChannel = false, nonInitiatorPaysCommitFees = false), diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt index 9dd995bcc..b2722d3c9 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt @@ -36,8 +36,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(f.fundingParamsA.fundingAmount, fundingA + fundingB) assertEquals(f.fundingParamsA.fundingAmount, fundingA + fundingB) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // 3 swap-in inputs, 2 legacy swap-in inputs, and 2 outputs from Alice // 2 swap-in inputs, 2 legacy swap-in inputs, and 1 output from Bob @@ -89,7 +89,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertTrue(sharedTxB.sharedTx.localFees < sharedTxA.sharedTx.localFees) // Bob sends signatures first as he contributed less than Alice. - val signedTxB = sharedTxB.sharedTx.sign(bob8, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA) + val signedTxB = sharedTxB.sharedTx.sign(bob8, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right!! assertEquals(signedTxB.localSigs.swapInUserSigs.size, 2) assertEquals(signedTxB.localSigs.swapInUserPartialSigs.size, 2) assertEquals(signedTxB.localSigs.swapInServerSigs.size, 2) @@ -97,39 +97,39 @@ class InteractiveTxTestsCommon : LightningTestSuite() { // Alice detects invalid signatures from Bob. val sigsInvalidTxId = signedTxB.localSigs.copy(txId = TxId(randomBytes32())) - assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidTxId)) + assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidTxId)) val sigsMissingUserSigs = signedTxB.localSigs.copy(tlvs = TlvStream(signedTxB.localSigs.tlvs.records.filterNot { it is TxSignaturesTlv.SwapInUserSigs }.toSet())) - assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsMissingUserSigs)) + assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsMissingUserSigs)) val sigsMissingUserPartialSigs = signedTxB.localSigs.copy(tlvs = TlvStream(signedTxB.localSigs.tlvs.records.filterNot { it is TxSignaturesTlv.SwapInUserPartialSigs }.toSet())) - assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsMissingUserPartialSigs)) + assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsMissingUserPartialSigs)) val sigsMissingServerSigs = signedTxB.localSigs.copy(tlvs = TlvStream(signedTxB.localSigs.tlvs.records.filterNot { it is TxSignaturesTlv.SwapInServerSigs }.toSet())) - assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsMissingServerSigs)) + assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsMissingServerSigs)) val sigsMissingServerPartialSigs = signedTxB.localSigs.copy(tlvs = TlvStream(signedTxB.localSigs.tlvs.records.filterNot { it is TxSignaturesTlv.SwapInServerPartialSigs }.toSet())) - assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsMissingServerPartialSigs)) + assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsMissingServerPartialSigs)) val invalidUserSigs = signedTxB.localSigs.swapInUserSigs.map { randomBytes64() } val sigsInvalidUserSig = signedTxB.localSigs.copy(tlvs = TlvStream(signedTxB.localSigs.tlvs.records.filterNot { it is TxSignaturesTlv.SwapInUserSigs }.toSet() + TxSignaturesTlv.SwapInUserSigs(invalidUserSigs))) - assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidUserSig)) + assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidUserSig)) val invalidPartialUserSigs = signedTxB.localSigs.swapInUserPartialSigs.map { TxSignaturesTlv.PartialSignature(randomBytes32(), it.localNonce, it.remoteNonce) } val sigsInvalidUserPartialSig = signedTxB.localSigs.copy(tlvs = TlvStream(signedTxB.localSigs.tlvs.records.filterNot { it is TxSignaturesTlv.SwapInUserPartialSigs }.toSet() + TxSignaturesTlv.SwapInUserPartialSigs(invalidPartialUserSigs))) - assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidUserPartialSig)) + assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidUserPartialSig)) val invalidServerSigs = signedTxB.localSigs.swapInServerSigs.map { randomBytes64() } val sigsInvalidServerSig = signedTxB.localSigs.copy(tlvs = TlvStream(signedTxB.localSigs.tlvs.records.filterNot { it is TxSignaturesTlv.SwapInServerSigs }.toSet() + TxSignaturesTlv.SwapInServerSigs(invalidServerSigs))) - assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidServerSig)) + assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidServerSig)) val invalidPartialServerSigs = signedTxB.localSigs.swapInServerPartialSigs.map { TxSignaturesTlv.PartialSignature(randomBytes32(), it.localNonce, it.remoteNonce) } val sigsInvalidServerPartialSig = signedTxB.localSigs.copy(tlvs = TlvStream(TxSignaturesTlv.SwapInUserPartialSigs(signedTxB.localSigs.swapInUserPartialSigs), TxSignaturesTlv.SwapInServerPartialSigs(invalidPartialServerSigs))) - assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidServerPartialSig)) + assertNull(sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, sigsInvalidServerPartialSig)) // The resulting transaction is valid and has the right feerate. - val signedTxA = sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) + val signedTxA = sharedTxA.sharedTx.sign(alice7, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) assertNotNull(signedTxA) assertEquals(signedTxA.localSigs.swapInUserSigs.size, 2) assertEquals(signedTxA.localSigs.swapInUserPartialSigs.size, 3) @@ -158,8 +158,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { val f = createFixture(fundingA, utxosA, legacyUtxosA, fundingB, utxosB, legacyUtxosB, targetFeerate, 660.sat, 0) assertEquals(f.fundingParamsA.fundingAmount, fundingA + fundingB) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Even though the initiator isn't contributing, they're paying the fees for the common parts of the transaction. // Alice --- tx_add_input --> Bob val (alice1, inputA1) = sendMessage(alice0) @@ -192,7 +192,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertTrue(sharedTxB.sharedTx.localFees < sharedTxA.sharedTx.localFees) // Alice sends signatures first as she contributed less than Bob. - val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB) + val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right assertNotNull(signedTxA) assertEquals(signedTxA.localSigs.swapInUserSigs.size, 1) assertEquals(signedTxA.localSigs.swapInServerSigs.size, 1) @@ -200,7 +200,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(signedTxA.localSigs.swapInServerPartialSigs.size, 1) // The resulting transaction is valid and has the right feerate. - val signedTxB = sharedTxB.sharedTx.sign(bob5, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA).addRemoteSigs(f.channelKeysB, f.fundingParamsB, signedTxA.localSigs) + val signedTxB = sharedTxB.sharedTx.sign(bob5, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right?.addRemoteSigs(f.channelKeysB, f.fundingParamsB, signedTxA.localSigs) assertNotNull(signedTxB) assertEquals(signedTxB.localSigs.swapInUserSigs.size, 1) assertEquals(signedTxB.localSigs.swapInServerSigs.size, 1) @@ -229,8 +229,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { val f = createFixture(fundingA, utxosA, legacyUtxosA, fundingB, utxosB, legacyUtxosB, targetFeerate, 660.sat, 0) assertEquals(f.fundingParamsA.fundingAmount, fundingA + fundingB) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, inputA1) = sendMessage(alice0) // Alice <-- tx_add_input --- Bob @@ -262,7 +262,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertTrue(sharedTxA.sharedTx.remoteFees < sharedTxA.sharedTx.localFees) // Alice contributes more than Bob to the funding output, but Bob's inputs are bigger than Alice's, so Alice must sign first. - val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB) + val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right assertNotNull(signedTxA) assertEquals(signedTxA.localSigs.swapInUserSigs.size, 1) assertEquals(signedTxA.localSigs.swapInServerSigs.size, 1) @@ -270,7 +270,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(signedTxA.localSigs.swapInServerPartialSigs.size, 1) // The resulting transaction is valid and has the right feerate. - val signedTxB = sharedTxB.sharedTx.sign(bob5, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA).addRemoteSigs(f.channelKeysB, f.fundingParamsB, signedTxA.localSigs) + val signedTxB = sharedTxB.sharedTx.sign(bob5, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right?.addRemoteSigs(f.channelKeysB, f.fundingParamsB, signedTxA.localSigs) assertNotNull(signedTxB) Transaction.correctlySpends(signedTxB.signedTx, (sharedTxA.sharedTx.localInputs + sharedTxB.sharedTx.localInputs).map { it.previousTx }, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val feerate = Transactions.fee2rate(signedTxB.tx.fees, signedTxB.signedTx.weight()) @@ -286,8 +286,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { val f = createFixture(fundingA, utxosA, legacyUtxosA, 0.sat, listOf(), listOf(), targetFeerate, 330.sat, 0) assertEquals(f.fundingParamsA.fundingAmount, fundingA) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, inputA1) = sendMessage(alice0) // Alice <-- tx_complete --- Bob @@ -326,7 +326,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(sharedTxB.sharedTx.remoteFees, 2_985_000.msat) // Bob sends signatures first as he did not contribute at all. - val signedTxB = sharedTxB.sharedTx.sign(bob6, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA) + val signedTxB = sharedTxB.sharedTx.sign(bob6, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right assertNotNull(signedTxB) assertEquals(signedTxB.localSigs.swapInUserSigs.size, 0) assertEquals(signedTxB.localSigs.swapInUserPartialSigs.size, 0) @@ -334,7 +334,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(signedTxB.localSigs.swapInServerPartialSigs.size, 2) // The resulting transaction is valid and has the right feerate. - val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) + val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) assertNotNull(signedTxA) assertEquals(signedTxA.localSigs.swapInUserPartialSigs.size, 2) assertEquals(signedTxA.localSigs.swapInServerSigs.size, 0) @@ -359,8 +359,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { val f = createFixture(0.sat, listOf(), listOf(), fundingB, utxosB, listOf(), targetFeerate, 330.sat, 0, nonInitiatorPaysCommitFees = true) assertEquals(f.fundingParamsA.fundingAmount, fundingB) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Alice --- tx_add_output --> Bob val (alice1, sharedOutput) = sendMessage(alice0) // Alice <-- tx_add_input --- Bob @@ -383,8 +383,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(0.msat, sharedTxB.sharedTx.remoteFees) // Alice signs first since she didn't contribute. - val signedTxA = sharedTxA.sharedTx.sign(alice4, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB) - val signedTxB = sharedTxB.sharedTx.sign(bob3, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA).addRemoteSigs(f.channelKeysB, f.fundingParamsB, signedTxA.localSigs) + val signedTxA = sharedTxA.sharedTx.sign(alice4, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right!! + val signedTxB = sharedTxB.sharedTx.sign(bob3, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right?.addRemoteSigs(f.channelKeysB, f.fundingParamsB, signedTxA.localSigs) assertNotNull(signedTxB) Transaction.correctlySpends(signedTxB.signedTx, sharedTxB.sharedTx.localInputs.map { it.previousTx }, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) // The feerate is lower than expected since Alice didn't contribute. @@ -406,8 +406,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertNotNull(f.fundingParamsA.sharedInput) assertNotNull(f.fundingParamsB.sharedInput) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, balanceA, balanceB, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, balanceB, balanceA, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, balanceA, balanceB, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, balanceB, balanceA, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, inputA1) = sendMessage(alice0) // Alice <-- tx_add_input --- Bob @@ -446,14 +446,14 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(sharedTxB.sharedTx.remoteFees, 953_000.msat) // Bob sends signatures first as he contributed less than Alice. - val signedTxB = sharedTxB.sharedTx.sign(bob5, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA) + val signedTxB = sharedTxB.sharedTx.sign(bob5, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right assertNotNull(signedTxB) assertEquals(signedTxB.localSigs.swapInUserPartialSigs.size, 1) assertEquals(signedTxB.localSigs.swapInServerPartialSigs.size, 1) assertNotNull(signedTxB.localSigs.previousFundingTxSig) // The resulting transaction is valid and has the right feerate. - val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) + val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) assertNotNull(signedTxA) assertEquals(signedTxA.localSigs.swapInUserPartialSigs.size, 1) assertEquals(signedTxA.localSigs.swapInServerPartialSigs.size, 1) @@ -483,8 +483,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertNotNull(f.fundingParamsA.sharedInput) assertNotNull(f.fundingParamsB.sharedInput) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, balanceA, balanceB, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, balanceB, balanceA, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, balanceA, balanceB, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, balanceB, balanceA, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, inputA) = sendMessage(alice0) // Alice <-- tx_add_output --- Bob @@ -520,7 +520,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(sharedTxB.sharedTx.remoteFees, 1_000_000.msat) // Bob sends signatures first as he did not contribute. - val signedTxB = sharedTxB.sharedTx.sign(bob3, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA) + val signedTxB = sharedTxB.sharedTx.sign(bob3, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right assertNotNull(signedTxB) assertTrue(signedTxB.localSigs.witnesses.isEmpty()) assertTrue(signedTxB.localSigs.swapInUserSigs.isEmpty()) @@ -528,7 +528,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertNotNull(signedTxB.localSigs.previousFundingTxSig) // The resulting transaction is valid. - val signedTxA = sharedTxA.sharedTx.sign(alice3, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) + val signedTxA = sharedTxA.sharedTx.sign(alice3, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) assertNotNull(signedTxA) assertTrue(signedTxA.localSigs.witnesses.isEmpty()) assertTrue(signedTxA.localSigs.swapInUserSigs.isEmpty()) @@ -556,8 +556,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertNotNull(f.fundingParamsA.sharedInput) assertNotNull(f.fundingParamsB.sharedInput) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, balanceA, balanceB, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, balanceB, balanceA, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, balanceA, balanceB, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, balanceB, balanceA, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, inputA) = sendMessage(alice0) // Alice <-- tx_add_output --- Bob @@ -600,13 +600,13 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(sharedTxB.sharedTx.remoteFees, 1_000_000.msat) // Bob sends signatures first as he did not contribute. - val signedTxB = sharedTxB.sharedTx.sign(bob5, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA) + val signedTxB = sharedTxB.sharedTx.sign(bob5, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right assertNotNull(signedTxB) assertTrue(signedTxB.localSigs.swapInUserSigs.isEmpty()) assertNotNull(signedTxB.localSigs.previousFundingTxSig) // The resulting transaction is valid. - val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) + val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) assertNotNull(signedTxA) assertTrue(signedTxA.localSigs.swapInUserSigs.isEmpty()) assertNotNull(signedTxA.localSigs.previousFundingTxSig) @@ -634,8 +634,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertNotNull(f.fundingParamsA.sharedInput) assertNotNull(f.fundingParamsB.sharedInput) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, balanceA, balanceB, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, balanceB, balanceA, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, balanceA, balanceB, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, balanceB, balanceA, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, inputA1) = sendMessage(alice0) // Alice <-- tx_add_input --- Bob @@ -677,14 +677,14 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(sharedTxB.sharedTx.remoteFees, 1_077_000.msat) // Bob sends signatures first as he did not contribute. - val signedTxB = sharedTxB.sharedTx.sign(bob6, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA) + val signedTxB = sharedTxB.sharedTx.sign(bob6, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right assertNotNull(signedTxB) assertEquals(signedTxB.localSigs.swapInUserPartialSigs.size, 1) assertEquals(signedTxB.localSigs.swapInServerPartialSigs.size, 1) assertNotNull(signedTxB.localSigs.previousFundingTxSig) // The resulting transaction is valid. - val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB).addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) + val signedTxA = sharedTxA.sharedTx.sign(alice5, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs) assertNotNull(signedTxA) assertEquals(signedTxA.localSigs.swapInUserPartialSigs.size, 1) assertEquals(signedTxA.localSigs.swapInServerPartialSigs.size, 1) @@ -711,8 +711,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { val f = createSpliceFixture(balanceA, 0.sat, listOf(), listOf(), balanceB, additionalFundingB, utxosB, listOf(), targetFeerate, 330.sat, 0, nonInitiatorPaysCommitFees = true) assertEquals(f.fundingParamsA.fundingAmount, 125_000.sat) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, balanceA, balanceB, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, balanceB, balanceA, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, balanceA, balanceB, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, balanceB, balanceA, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, sharedInput) = sendMessage(alice0) // Alice <-- tx_add_input --- Bob @@ -729,8 +729,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { val (alice4, sharedTxA) = receiveFinalMessage(alice3, sharedTxB.txComplete) // Alice signs first since she didn't contribute. - val signedTxA = sharedTxA.sharedTx.sign(alice4, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.nodeIdB) - val signedTxB = sharedTxB.sharedTx.sign(bob3, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.nodeIdA).addRemoteSigs(f.channelKeysB, f.fundingParamsB, signedTxA.localSigs) + val signedTxA = sharedTxA.sharedTx.sign(alice4, f.keyManagerA, f.fundingParamsA, f.nodeIdB).right!! + val signedTxB = sharedTxB.sharedTx.sign(bob3, f.keyManagerB, f.fundingParamsB, f.nodeIdA).right?.addRemoteSigs(f.channelKeysB, f.fundingParamsB, signedTxA.localSigs) assertNotNull(signedTxB) Transaction.correctlySpends(signedTxB.signedTx, previousOutputs(f.fundingParamsA, sharedTxB.sharedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) // The feerate is lower than expected since Alice didn't contribute. @@ -742,8 +742,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { fun `remove input - output`() { val f = createFixture(100_000.sat, listOf(150_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(2500.sat), 330.sat, 0) // In this flow we introduce dummy inputs/outputs from Bob to Alice that are then removed. - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), FundingContributions(listOf(), listOf()), commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), FundingContributions(listOf(), listOf())) // Alice --- tx_add_input --> Bob val (alice1, inputA) = sendMessage(alice0) @@ -859,7 +859,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { TxAddInput(f.channelId, 9, previousTx, 2, 0xffffffffU) to InteractiveTxSessionAction.NonReplaceableInput(f.channelId, 9, previousTx.txid, 2, 0xffffffff), ) testCases.forEach { (input, expected) -> - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) // Alice --- tx_add_input --> Bob val (alice1, _) = sendMessage(alice0) // Alice <-- tx_add_input --- Bob @@ -879,7 +879,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { TxAddOutput(f.channelId, 1, 25_000.sat, Script.write(listOf(OP_1)).byteVector()), ) testCases.forEach { output -> - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) // Alice --- tx_add_input --> Bob val (alice1, _) = sendMessage(alice0) // Alice <-- tx_add_output --- Bob @@ -899,7 +899,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { TxAddOutput(f.channelId, 3, 329.sat, validScript) to InteractiveTxSessionAction.OutputBelowDust(f.channelId, 3, 329.sat, 330.sat), ) testCases.forEach { (output, expected) -> - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) // Alice --- tx_add_input --> Bob val (alice1, _) = sendMessage(alice0) // Alice <-- tx_add_output --- Bob @@ -920,7 +920,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { TxRemoveInput(f.channelId, 57) to InteractiveTxSessionAction.UnknownSerialId(f.channelId, 57), ) testCases.forEach { (msg, expected) -> - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) // Alice --- tx_add_input --> Bob val (alice1, _) = sendMessage(alice0) // Alice <-- tx_remove_(in|out)put --- Bob @@ -933,7 +933,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { fun `too many protocol rounds`() { val f = createFixture(100_000.sat, listOf(120_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) val validScript = Script.write(Script.pay2wpkh(randomKey().publicKey())).byteVector() - var (alice, _) = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0).send() + var (alice, _) = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA).send() (1..InteractiveTxSession.MAX_INPUTS_OUTPUTS_RECEIVED).forEach { i -> // Alice --- tx_message --> Bob val (alice1, _) = alice.receive(TxAddOutput(f.channelId, 2 * i.toLong() + 1, 2500.sat, validScript)) @@ -946,7 +946,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { @Test fun `too many inputs`() { val f = createFixture(100_000.sat, listOf(120_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) - var (alice, _) = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0).send() + var (alice, _) = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA).send() (1..252).forEach { i -> // Alice --- tx_message --> Bob val (alice1, _) = alice.receive(createTxAddInput(f.channelId, 2 * i.toLong() + 1, 5000.sat)) @@ -962,7 +962,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { @Test fun `too many outputs`() { val f = createFixture(100_000.sat, listOf(120_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) - var (alice, _) = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0).send() + var (alice, _) = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA).send() val validScript = Script.write(Script.pay2wpkh(randomKey().publicKey())).byteVector() (1..252).forEach { i -> // Alice --- tx_message --> Bob @@ -980,7 +980,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { fun `missing funding output`() { val f = createFixture(100_000.sat, listOf(120_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) val validScript = Script.write(Script.pay2wpkh(randomKey().publicKey())).byteVector() - val bob0 = InteractiveTxSession(f.nodeIdB, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val bob0 = InteractiveTxSession(f.nodeIdB, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (bob1, _) = receiveMessage(bob0, createTxAddInput(f.channelId, 0, 150_000.sat)) // Alice --- tx_add_output --> Bob @@ -993,7 +993,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { @Test fun `multiple funding outputs`() { val f = createFixture(100_000.sat, listOf(120_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (bob1, _) = receiveMessage(bob0, createTxAddInput(f.channelId, 0, 150_000.sat)) // Alice --- tx_add_output --> Bob @@ -1011,7 +1011,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { val spliceOutputA = TxOut(20_000.sat, Script.pay2wpkh(randomKey().publicKey())) val subtractedFundingA = 25_000.sat val f = createSpliceFixture(balanceA, -subtractedFundingA, listOf(), listOf(spliceOutputA), 0.msat, 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, balanceA, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, balanceA, emptySet(), f.fundingContributionsB) // Alice --- tx_add_output --> Bob val (bob1, _) = receiveMessage(bob0, TxAddOutput(f.channelId, 0, 75_000.sat, f.fundingParamsB.fundingPubkeyScript(f.channelKeysB))) // Alice --- tx_add_output --> Bob @@ -1024,8 +1024,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { @Test fun `swap-in input missing user key`() { val f = createFixture(100_000.sat, listOf(), listOf(150_000.sat), 0.sat, listOf(), listOf(), FeeratePerKw(2500.sat), 330.sat, 0) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, inputA) = sendMessage(alice0) // Alice <-- tx_complete --- Bob @@ -1045,17 +1045,17 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertNull(sharedTxB.txComplete) // Alice didn't send her user key, so Bob thinks there aren't any swap inputs - val signedTxB = sharedTxB.sharedTx.sign(bob3, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.localParamsA.nodeId) + val signedTxB = sharedTxB.sharedTx.sign(bob3, f.keyManagerB, f.fundingParamsB, f.localParamsA.nodeId).right!! assertTrue(signedTxB.localSigs.swapInServerSigs.isEmpty()) // Alice is unable to sign her input since Bob didn't provide his signature. - assertNull(sharedTxA.sharedTx.sign(alice3, f.keyManagerA, f.fundingParamsA, f.localParamsA, f.localParamsB.nodeId).addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs)) + assertNull(sharedTxA.sharedTx.sign(alice3, f.keyManagerA, f.fundingParamsA, f.localParamsB.nodeId).right?.addRemoteSigs(f.channelKeysA, f.fundingParamsA, signedTxB.localSigs)) } @Test fun `swap-in input missing user nonce`() { val f = createFixture(100_000.sat, listOf(150_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(2500.sat), 330.sat, 0) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0.msat, 0.msat, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, inputA) = sendMessage(alice0) // Alice <-- tx_complete --- Bob @@ -1076,7 +1076,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { @Test fun `invalid funding amount`() { val f = createFixture(100_000.sat, listOf(120_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (bob1, _) = receiveMessage(bob0, createTxAddInput(f.channelId, 0, 150_000.sat)) // Alice --- tx_add_output --> Bob @@ -1097,8 +1097,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertNotNull(f.fundingParamsA.sharedInput) assertNotNull(f.fundingParamsB.sharedInput) - val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, balanceA, balanceB, emptySet(), f.fundingContributionsA, commitTxIndex = 0, fundingTxIndex = 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, balanceB, balanceA, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val alice0 = InteractiveTxSession(f.nodeIdB, f.channelKeysA, f.keyManagerA.swapInOnChainWallet, f.fundingParamsA, 0, balanceA, balanceB, emptySet(), f.fundingContributionsA) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, balanceB, balanceA, emptySet(), f.fundingContributionsB) // Alice --- tx_add_input --> Bob val (alice1, inputA) = sendMessage(alice0) // Alice <-- tx_complete --- Bob @@ -1137,7 +1137,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { @Test fun `missing previous tx`() { val f = createFixture(100_000.sat, listOf(120_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) // Alice --- tx_add_output --> Bob val failure = receiveInvalidMessage(bob0, TxAddInput(f.channelId, 0, null, 3, 0u)) assertIs(failure) @@ -1146,7 +1146,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { @Test fun `total input amount too low`() { val f = createFixture(100_000.sat, listOf(120_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) val validScript = Script.write(Script.pay2wpkh(randomKey().publicKey())).byteVector() // Alice --- tx_add_input --> Bob val (bob1, _) = receiveMessage(bob0, createTxAddInput(f.channelId, 0, 150_000.sat)) @@ -1162,7 +1162,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { @Test fun `minimum fee not met`() { val f = createFixture(100_000.sat, listOf(120_000.sat), listOf(), 0.sat, listOf(), listOf(), FeeratePerKw(5000.sat), 330.sat, 0) - val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, commitTxIndex = 0, fundingTxIndex = 0) + val bob0 = InteractiveTxSession(f.nodeIdA, f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB) val validScript = Script.write(Script.pay2wpkh(randomKey().publicKey())).byteVector() // Alice --- tx_add_input --> Bob val (bob1, _) = receiveMessage(bob0, createTxAddInput(f.channelId, 0, 150_000.sat)) @@ -1198,13 +1198,12 @@ class InteractiveTxTestsCommon : LightningTestSuite() { f.channelKeysB, f.keyManagerB.swapInOnChainWallet, f.fundingParamsB, + 0, 0.msat, 0.msat, emptySet(), f.fundingContributionsB, listOf(firstAttempt, secondAttempt), - commitTxIndex = 0, - fundingTxIndex = 0 ) // Alice --- tx_add_input --> Bob val (bob1, _) = receiveMessage(bob0, TxAddInput(f.channelId, 4, previousTx2, 1, 0u)) diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/RecoveryTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/RecoveryTestsCommon.kt deleted file mode 100644 index 2e05db717..000000000 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/RecoveryTestsCommon.kt +++ /dev/null @@ -1,91 +0,0 @@ -package fr.acinq.lightning.channel - -import fr.acinq.bitcoin.* -import fr.acinq.lightning.Lightning.randomKey -import fr.acinq.lightning.MilliSatoshi -import fr.acinq.lightning.blockchain.fee.FeeratePerKw -import fr.acinq.lightning.crypto.CommitmentPublicKeys -import fr.acinq.lightning.crypto.LocalKeyManager -import fr.acinq.lightning.crypto.RemoteCommitmentKeys -import fr.acinq.lightning.tests.TestConstants -import fr.acinq.lightning.transactions.Scripts -import fr.acinq.lightning.transactions.Transactions -import fr.acinq.lightning.utils.toByteVector32 -import kotlin.test.Test -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue - -class RecoveryTestsCommon { - - @Test - fun `use funding pubkeys from published commitment to spend our output`() { - // Alice creates and uses a LN channel to Bob - val (alice, bob) = TestsHelper.reachNormal() - val (alice1, _) = TestsHelper.addHtlc(MilliSatoshi(50000), alice, bob).first - - // Alice force-closes the channel and publishes her commit tx - val (_, actions) = alice1.process(ChannelCommand.Close.ForceClose) - val transactions = actions.findPublishTxs() - val commitTx = transactions[0] - val aliceTx = transactions[1] - - // how can Bob find and spend his output in Alice's published commit tx with just his wallet seed (derived from his mnemonic words) and nothing else? - - // extract funding pubkeys from the commit tx witness, which is a multisig 2-of-2 - val redeemScript = Script.parse(commitTx.txIn[0].witness.last()) - assertTrue(redeemScript.size == 5 && redeemScript[0] == OP_2 && redeemScript[3] == OP_2 && redeemScript[4] == OP_CHECKMULTISIG) - val pub1 = PublicKey((redeemScript[1] as OP_PUSHDATA).data) - val pub2 = PublicKey((redeemScript[2] as OP_PUSHDATA).data) - - // use Bob's mnemonic words to initialise his key manager - val seed = MnemonicCode.toSeed(TestConstants.Bob.mnemonics, "").toByteVector32() - val keyManager = LocalKeyManager(seed, Chain.Regtest, TestConstants.aliceSwapInServerXpub) - - // recompute our channel keys from the extracted funding pubkey and see if we can find and spend our output - // we only need our payment key and basepoint for our main output - fun findAndSpend(fundingKey: PublicKey): Transaction? { - val channelKeys = keyManager.recoverChannelKeys(fundingKey) - val commitKeys = RemoteCommitmentKeys( - ourPaymentKey = channelKeys.paymentKey, - theirDelayedPaymentPublicKey = randomKey().publicKey(), - ourPaymentBasePoint = channelKeys.paymentBasepoint, - ourHtlcKey = randomKey(), - theirHtlcPublicKey = randomKey().publicKey(), - revocationPublicKey = randomKey().publicKey() - ) - val finalScript = Script.write(Script.pay2wpkh(fundingKey)).byteVector() - val mainTx = Transactions.ClaimRemoteDelayedOutputTx.createUnsignedTx( - commitKeys, - commitTx, - TestConstants.Bob.nodeParams.dustLimit, - finalScript, - FeeratePerKw(750.sat()), - Transactions.CommitmentFormat.AnchorOutputs - ).map { it.sign().tx }.right - mainTx?.let { Transaction.correctlySpends(it, commitTx, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } - return mainTx - } - - // this is the script of the output that we're spending - val bobTx = findAndSpend(pub1) ?: findAndSpend(pub2)!! - assertNotEquals(aliceTx, bobTx) - - val outputScript = Script.parse(commitTx.txOut[bobTx.txIn[0].outPoint.index.toInt()].publicKeyScript) - - // this is what our main output script should be - fun ourDelayedOutputScript(pub: PublicKey): List { - val channelKeys = keyManager.recoverChannelKeys(pub) - val commitKeys = CommitmentPublicKeys( - localDelayedPaymentPublicKey = randomKey().publicKey(), - remotePaymentPublicKey = channelKeys.paymentBasepoint, - localHtlcPublicKey = randomKey().publicKey(), - remoteHtlcPublicKey = randomKey().publicKey(), - revocationPublicKey = randomKey().publicKey() - ) - return Script.pay2wsh(Scripts.toRemoteDelayed(commitKeys)) - } - - assertTrue(outputScript == ourDelayedOutputScript(pub1) || outputScript == ourDelayedOutputScript(pub2)) - } - -} diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/TestsHelper.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/TestsHelper.kt index 3b513d6a0..ad4f1fa16 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/TestsHelper.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/TestsHelper.kt @@ -152,14 +152,20 @@ data class LNChannel( private fun checkSerialization(state: PersistedChannelState) { // We don't persist unsigned funding RBF or splice attempts. + // We don't persist taproot nonces either, they will be retransmitted on reconnection. fun removeTemporaryStatuses(state: PersistedChannelState): PersistedChannelState = when (state) { + is WaitForFundingSigned -> state.copy(signingSession = state.signingSession.copy(nextRemoteCommitNonce = null)) + is WaitForFundingConfirmed -> when (state.rbfStatus) { + is RbfStatus.WaitingForSigs -> state.copy(remoteNextCommitNonces = mapOf(), rbfStatus = state.rbfStatus.copy(session = state.rbfStatus.session.copy(nextRemoteCommitNonce = null))) + else -> state.copy(remoteNextCommitNonces = mapOf(), rbfStatus = RbfStatus.None) + } + is WaitForChannelReady -> state.copy(remoteNextCommitNonces = mapOf()) is Normal -> when (state.spliceStatus) { - is SpliceStatus.WaitingForSigs -> state.copy(spliceStatus = state.spliceStatus.copy(session = state.spliceStatus.session.copy(nextRemoteNonce = null))) - else -> state.copy(spliceStatus = SpliceStatus.None) + is SpliceStatus.WaitingForSigs -> state.copy(remoteNextCommitNonces = mapOf(), localCloseeNonce = null, localCloserNonces = null, spliceStatus = state.spliceStatus.copy(session = state.spliceStatus.session.copy(nextRemoteCommitNonce = null))) + else -> state.copy(remoteNextCommitNonces = mapOf(), localCloseeNonce = null, localCloserNonces = null, spliceStatus = SpliceStatus.None) } - - is WaitForFundingSigned -> state.copy(signingSession = state.signingSession.copy(nextRemoteNonce = null), remoteCommitNonces = mapOf()) - + is ShuttingDown -> state.copy(remoteNextCommitNonces = mapOf(), localCloseeNonce = null) + is Negotiating -> state.copy(remoteNextCommitNonces = mapOf(), localCloseeNonce = null, remoteCloseeNonce = null, localCloserNonces = null) else -> state } @@ -173,7 +179,7 @@ data class LNChannel( val serialized = Serialization.serialize(state) val deserialized = Serialization.deserialize(serialized).value -// assertEquals(removeTemporaryStatuses(ignoreClosingReplyTo(state)), ignoreClosingReplyTo(deserialized), "serialization error") + assertEquals(removeTemporaryStatuses(ignoreClosingReplyTo(state)), ignoreClosingReplyTo(deserialized), "serialization error") } private fun checkSerialization(actions: List) { @@ -186,7 +192,7 @@ data class LNChannel( object TestsHelper { fun init( - channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, + channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, aliceFeatures: Features = TestConstants.Alice.nodeParams.features, bobFeatures: Features = TestConstants.Bob.nodeParams.features, bobUsePeerStorage: Boolean = true, @@ -197,22 +203,14 @@ object TestsHelper { zeroConf: Boolean = false, channelOrigin: Origin? = null ): Triple, LNChannel, OpenDualFundedChannel> { - val (aliceFeatures1, bobFeatures1) = when (channelType) { - ChannelType.SupportedChannelType.SimpleTaprootChannels -> Pair( - aliceFeatures.add(Feature.SimpleTaprootChannels to FeatureSupport.Mandatory), - bobFeatures.add(Feature.SimpleTaprootChannels to FeatureSupport.Mandatory) - ) - - else -> Pair(aliceFeatures, bobFeatures) - } val (aliceNodeParams, bobNodeParams) = when (zeroConf) { true -> Pair( - TestConstants.Alice.nodeParams.copy(features = aliceFeatures1, zeroConfPeers = setOf(TestConstants.Bob.nodeParams.nodeId), usePeerStorage = false), - TestConstants.Bob.nodeParams.copy(features = bobFeatures1, zeroConfPeers = setOf(TestConstants.Alice.nodeParams.nodeId), usePeerStorage = bobUsePeerStorage) + TestConstants.Alice.nodeParams.copy(features = aliceFeatures, zeroConfPeers = setOf(TestConstants.Bob.nodeParams.nodeId), usePeerStorage = false), + TestConstants.Bob.nodeParams.copy(features = bobFeatures, zeroConfPeers = setOf(TestConstants.Alice.nodeParams.nodeId), usePeerStorage = bobUsePeerStorage) ) false -> Pair( - TestConstants.Alice.nodeParams.copy(features = aliceFeatures1, usePeerStorage = false), - TestConstants.Bob.nodeParams.copy(features = bobFeatures1, usePeerStorage = bobUsePeerStorage) + TestConstants.Alice.nodeParams.copy(features = aliceFeatures, usePeerStorage = false), + TestConstants.Bob.nodeParams.copy(features = bobFeatures, usePeerStorage = bobUsePeerStorage) ) } val alice = LNChannel( @@ -235,10 +233,10 @@ object TestsHelper { ) val channelFlags = ChannelFlags(announceChannel = false, nonInitiatorPaysCommitFees = requestRemoteFunding != null) - val aliceChannelParams = TestConstants.Alice.channelParams(payCommitTxFees = !channelFlags.nonInitiatorPaysCommitFees).copy(features = aliceFeatures1.initFeatures()) - val bobChannelParams = TestConstants.Bob.channelParams(payCommitTxFees = channelFlags.nonInitiatorPaysCommitFees).copy(features = bobFeatures1.initFeatures()) - val aliceInit = Init(aliceFeatures1) - val bobInit = Init(bobFeatures1) + val aliceChannelParams = TestConstants.Alice.channelParams(payCommitTxFees = !channelFlags.nonInitiatorPaysCommitFees).copy(features = aliceFeatures.initFeatures()) + val bobChannelParams = TestConstants.Bob.channelParams(payCommitTxFees = channelFlags.nonInitiatorPaysCommitFees).copy(features = bobFeatures.initFeatures()) + val aliceInit = Init(aliceFeatures) + val bobInit = Init(bobFeatures) val cmd = ChannelCommand.Init.Initiator( CompletableDeferred(), aliceFundingAmount, @@ -290,7 +288,7 @@ object TestsHelper { } fun reachNormal( - channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, + channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, aliceFeatures: Features = TestConstants.Alice.nodeParams.features.initFeatures(), bobFeatures: Features = TestConstants.Bob.nodeParams.features.initFeatures(), bobUsePeerStorage: Boolean = true, @@ -386,7 +384,7 @@ object TestsHelper { fun localClose(s: LNChannel, htlcSuccessCount: Int = 0, htlcTimeoutCount: Int = 0): Triple, LocalCommitPublished, LocalCloseTxs> { assertIs>(s) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, s.state.commitments.latest.commitmentFormat) + assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, s.state.commitments.latest.commitmentFormat) // An error occurs and we publish our commit tx. val commitTxId = s.state.commitments.latest.localCommit.txId val (s1, actions1) = s.process(ChannelCommand.MessageReceived(Error(ByteVector32.Zeroes, "oops"))) @@ -448,7 +446,7 @@ object TestsHelper { fun remoteClose(rCommitTx: Transaction, s: LNChannel, htlcSuccessCount: Int = 0, htlcTimeoutCount: Int = 0): Triple, RemoteCommitPublished, RemoteCloseTxs> { assertIs>(s) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, s.state.commitments.latest.commitmentFormat) + assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, s.state.commitments.latest.commitmentFormat) // Our peer has unilaterally closed the channel. val (s1, actions1) = s.process(ChannelCommand.WatchReceived(WatchSpentTriggered(s.state.channelId, WatchSpent.ChannelSpent(TestConstants.fundingAmount), rCommitTx))) assertIs>(s1) @@ -507,13 +505,13 @@ object TestsHelper { return Triple(closingState, remoteCommitPublished, RemoteCloseTxs(mainTx, htlcSuccessTxs, htlcTimeoutTxs)) } - fun useAlternativeCommitSig(s: LNChannel, commitment: Commitment, alternative: CommitSigTlv.AlternativeFeerateSig): Transaction { + fun useAlternativeCommitSig(s: LNChannel, commitment: Commitment, feerate: FeeratePerKw): Transaction { val channelKeys = s.commitments.channelKeys(s.ctx.keyManager) val fundingKey = commitment.localFundingKey(channelKeys) val commitKeys = channelKeys.localCommitmentKeys(s.commitments.channelParams, commitment.localCommit.index) - val alternativeSpec = commitment.localCommit.spec.copy(feerate = alternative.feerate) - val alternativeSig = ChannelSpendSignature.IndividualSignature(alternative.sig) + val alternativeSpec = commitment.localCommit.spec.copy(feerate = feerate) val remoteFundingPubKey = commitment.remoteFundingPubkey + // This commitment transaction isn't signed, but we don't care, we will make it look like it was confirmed anyway. val (localCommitTx, _) = Commitments.makeLocalTxs( channelParams = s.commitments.channelParams, commitParams = commitment.localCommitParams, @@ -525,10 +523,7 @@ object TestsHelper { commitmentFormat = commitment.commitmentFormat, spec = alternativeSpec, ) - val localSig = localCommitTx.sign(fundingKey, remoteFundingPubKey) - val signedCommitTx = localCommitTx.aggregateSigs(fundingKey.publicKey(), remoteFundingPubKey, localSig, alternativeSig) - Transaction.correctlySpends(signedCommitTx, mapOf(commitment.fundingInput to commitment.localFundingStatus.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - return signedCommitTx + return localCommitTx.tx } fun signAndRevack(alice: LNChannel, bob: LNChannel): Pair, LNChannel> { diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/ClosingTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/ClosingTestsCommon.kt index 1c408ed8b..4f4a8168d 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/ClosingTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/ClosingTestsCommon.kt @@ -49,7 +49,7 @@ class ClosingTestsCommon : LightningTestSuite() { @Test fun `recv ChannelFundingDepthOk`() { - val (alice, bob, _) = WaitForFundingConfirmedTestsCommon.init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, _) = WaitForFundingConfirmedTestsCommon.init() val fundingTx = alice.state.latestFundingTx.sharedTx.tx.buildUnsignedTx() run { val (aliceClosing, _) = localClose(alice) @@ -74,7 +74,7 @@ class ClosingTestsCommon : LightningTestSuite() { @Test fun `recv ChannelFundingDepthOk -- previous funding tx`() { - val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init() val (alice1, bob1, fundingTx) = WaitForFundingConfirmedTestsCommon.rbf(alice, bob, walletAlice) assertNotEquals(previousFundingTx.txid, fundingTx.txid) run { @@ -644,7 +644,7 @@ class ClosingTestsCommon : LightningTestSuite() { val (bob6, actionsBob6) = bob5.process(ChannelCommand.MessageReceived(commitSigAlice)) val revBob = actionsBob6.hasOutgoingMessage() val (alice6, _) = alice5.process(ChannelCommand.MessageReceived(revBob)) - val alternativeCommitTx = useAlternativeCommitSig(alice6, alice6.commitments.active.first(), commitSigBob.alternativeFeerateSigs.first()) + val alternativeCommitTx = useAlternativeCommitSig(alice6, alice6.commitments.active.first(), Commitments.alternativeFeerates.first()) remoteClose(alternativeCommitTx, bob6) } @@ -857,7 +857,7 @@ class ClosingTestsCommon : LightningTestSuite() { val (bob4, actionsBob4) = bob3.process(ChannelCommand.Commitment.Sign) val commitSigBob = actionsBob4.hasOutgoingMessage() val (alice4, _) = alice3.process(ChannelCommand.MessageReceived(commitSigBob)) - val alternativeCommitTx = useAlternativeCommitSig(alice4, alice4.commitments.active.first(), commitSigBob.alternativeFeerateSigs.first()) + val alternativeCommitTx = useAlternativeCommitSig(alice4, alice4.commitments.active.first(), Commitments.alternativeFeerates.first()) remoteClose(alternativeCommitTx, bob4) } diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NegotiatingTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NegotiatingTestsCommon.kt index d2dcc7151..86dcf9f9a 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NegotiatingTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NegotiatingTestsCommon.kt @@ -41,18 +41,6 @@ class NegotiatingTestsCommon : LightningTestSuite() { mutualCloseBob(alice, bob, FeeratePerKw(500.sat)) } - @Test - fun `basic mutual close -- alice -- simple taproot channels`() = runSuspendTest { - val (alice, bob) = reachNormal(channelType = ChannelType.SupportedChannelType.SimpleTaprootChannels) - mutualCloseAlice(alice, bob, FeeratePerKw(500.sat)) - } - - @Test - fun `basic mutual close -- bob -- simple taproot channels`() = runSuspendTest { - val (alice, bob) = reachNormal(ChannelType.SupportedChannelType.SimpleTaprootChannels) - mutualCloseBob(alice, bob, FeeratePerKw(500.sat)) - } - @Test fun `recv ChannelCommand_Htlc_Add`() { val (alice, _, _) = init() @@ -124,15 +112,15 @@ class NegotiatingTestsCommon : LightningTestSuite() { assertNull(actionsBob2.findOutgoingMessageOpt()) // Bob cannot pay mutual close fees. val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(shutdownBob)) val closingCompleteAlice = actionsAlice2.findOutgoingMessage() - assertNull(closingCompleteAlice.closerAndCloseeOutputsSig) - assertNotNull(closingCompleteAlice.closerOutputOnlySig) + assertNull(closingCompleteAlice.closerAndCloseeOutputsPartialSig) + assertNotNull(closingCompleteAlice.closerOutputOnlyPartialSig) val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(closingCompleteAlice)) assertIs(bob3.state) val closingTxAlice = actionsBob3.findPublishTxs().first() assertEquals(1, closingTxAlice.txOut.size) val closingSigBob = actionsBob3.findOutgoingMessage() - assertNotNull(closingSigBob.closerOutputOnlySig) + assertNotNull(closingSigBob.closerOutputOnlyPartialSig) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(closingSigBob)) assertIs(alice3.state) @@ -169,24 +157,33 @@ class NegotiatingTestsCommon : LightningTestSuite() { val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(closingSigAlice1)) actionsBob3.hasOutgoingMessage() - // Alice handles Bob's updated closing_complete. + // Bob's closing_complete doesn't use Alice's latest closee nonce, so she ignores his closing_complete. val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(closingCompleteBob2)) - assertIs(alice2.state) - assertEquals(4, actionsAlice2.size) - actionsAlice2.has() - val closingTx2 = actionsAlice2.findPublishTxs().first() + actionsAlice2.hasOutgoingMessage() + + // Bob retries sending closing_complete. + val (bob4, actionsBob4) = bob3.process(ChannelCommand.Close.MutualClose(CompletableDeferred(), closingScript, TestConstants.feeratePerKw * 1.5)) + val closingCompleteBob3 = actionsBob4.findOutgoingMessage() + assertEquals(closingScript, closingCompleteBob3.closerScriptPubKey) + + // Alice handles Bob's updated closing_complete. + val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(closingCompleteBob3)) + assertIs(alice3.state) + assertEquals(4, actionsAlice3.size) + actionsAlice3.has() + val closingTx2 = actionsAlice3.findPublishTxs().first() assertTrue(closingTx2.txOut.any { it.publicKeyScript == closingScript }) - actionsAlice2.hasWatchConfirmed(closingTx2.txid) - val closingSigAlice2 = actionsAlice2.findOutgoingMessage() + actionsAlice3.hasWatchConfirmed(closingTx2.txid) + val closingSigAlice2 = actionsAlice3.findOutgoingMessage() // Bob receives Alice's closing_sig for his updated closing_complete. - val (bob4, actionsBob4) = bob3.process(ChannelCommand.MessageReceived(closingSigAlice2)) - assertIs(bob4.state) - assertEquals(4, actionsBob4.size) - actionsBob4.has() - actionsBob4.has() - assertEquals(closingTx2, actionsBob4.findPublishTxs().first()) - actionsBob4.hasWatchConfirmed(closingTx2.txid) + val (bob5, actionsBob5) = bob4.process(ChannelCommand.MessageReceived(closingSigAlice2)) + assertIs(bob5.state) + assertEquals(4, actionsBob5.size) + actionsBob5.has() + actionsBob5.has() + assertEquals(closingTx2, actionsBob5.findPublishTxs().first()) + actionsBob5.hasWatchConfirmed(closingTx2.txid) } @Test @@ -195,7 +192,7 @@ class NegotiatingTestsCommon : LightningTestSuite() { // Bob expects to receive a signature for a closing transaction containing his output, so he ignores Alice's // closing_complete instead of sending back his closing_sig. - val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(closingComplete.copy(tlvStream = TlvStream(ClosingCompleteTlv.CloserOutputOnly(closingComplete.closerOutputOnlySig!!))))) + val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(closingComplete.copy(tlvStream = TlvStream(ClosingCompleteTlv.CloserOutputOnlyPartialSignature(closingComplete.closerOutputOnlyPartialSig!!))))) assertIs(bob1.state) assertEquals(1, actionsBob1.size) actionsBob1.hasOutgoingMessage() @@ -203,7 +200,7 @@ class NegotiatingTestsCommon : LightningTestSuite() { val (bob2, actionsBob2) = bob1.process(ChannelCommand.MessageReceived(closingComplete)) assertIs(bob2.state) val closingTxAlice = actionsBob2.findPublishTxs().first() - assertTrue(closingTxAlice.txOut.size == 2) + assertEquals(closingTxAlice.txOut.size, 2) actionsBob2.findOutgoingMessage() } @@ -465,7 +462,7 @@ class NegotiatingTestsCommon : LightningTestSuite() { data class Fixture(val alice: LNChannel, val bob: LNChannel, val closingCompleteAlice: ClosingComplete, val closingCompleteBob: ClosingComplete) fun init( - channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, + channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, aliceFundingAmount: Satoshi = TestConstants.aliceFundingAmount, bobFundingAmount: Satoshi = TestConstants.bobFundingAmount, aliceClosingFeerate: FeeratePerKw = TestConstants.feeratePerKw, diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NormalTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NormalTestsCommon.kt index 625cb4fb6..4d73995f4 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NormalTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NormalTestsCommon.kt @@ -72,7 +72,7 @@ class NormalTestsCommon : LightningTestSuite() { @Test fun `recv ChannelCommand_Htlc_Add -- zero-reserve`() { - val (_, bob0) = reachNormal(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, bobFundingAmount = 10_000.sat) + val (_, bob0) = reachNormal(bobFundingAmount = 10_000.sat) assertEquals(bob0.commitments.availableBalanceForSend(), 10_000_000.msat) val add = defaultAdd.copy(amount = 10_000_000.msat, paymentHash = randomBytes32()) @@ -86,7 +86,7 @@ class NormalTestsCommon : LightningTestSuite() { @Test fun `recv ChannelCommand_Htlc_Add -- zero-conf -- zero-reserve`() { - val (_, bob0) = reachNormal(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, bobFundingAmount = 10_000.sat, zeroConf = true) + val (_, bob0) = reachNormal(bobFundingAmount = 10_000.sat, zeroConf = true) assertEquals(bob0.commitments.availableBalanceForSend(), 10_000_000.msat) val add = defaultAdd.copy(amount = 10_000_000.msat, paymentHash = randomBytes32()) @@ -164,7 +164,7 @@ class NormalTestsCommon : LightningTestSuite() { @Test fun `recv ChannelCommand_Htlc_Add -- increasing balance but still below reserve`() { - val (alice0, bob0) = reachNormal(bobFundingAmount = 0.sat) + val (alice0, bob0) = reachNormal(channelType = ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) assertFalse(alice0.commitments.channelParams.channelFeatures.hasFeature(Feature.ZeroReserveChannels)) assertFalse(bob0.commitments.channelParams.channelFeatures.hasFeature(Feature.ZeroReserveChannels)) assertEquals(0.msat, bob0.commitments.availableBalanceForSend()) @@ -186,7 +186,7 @@ class NormalTestsCommon : LightningTestSuite() { val add = defaultAdd.copy(amount = Int.MAX_VALUE.msat) val (alice1, actions) = alice0.process(add) val actualError = actions.findCommandError() - val expectError = InsufficientFunds(alice0.channelId, amount = Int.MAX_VALUE.msat, missing = 1_322_823.sat, reserve = 10_000.sat, fees = 7_140.sat) + val expectError = InsufficientFunds(alice0.channelId, amount = Int.MAX_VALUE.msat, missing = 1_311_263.sat, reserve = 0.sat, fees = 6_360.sat) assertEquals(expectError, actualError) assertEquals(alice0, alice1) } @@ -197,7 +197,7 @@ class NormalTestsCommon : LightningTestSuite() { val add = defaultAdd.copy(amount = bob0.commitments.availableBalanceForSend() + 1.sat.toMilliSatoshi()) val (bob1, actions) = bob0.process(add) val actualError = actions.findCommandError() - val expectedError = InsufficientFunds(bob0.channelId, amount = add.amount, missing = 1.sat, reserve = 10000.sat, fees = 0.sat) + val expectedError = InsufficientFunds(bob0.channelId, amount = add.amount, missing = 1.sat, reserve = 0.sat, fees = 0.sat) assertEquals(expectedError, actualError) assertEquals(bob0, bob1) } @@ -205,7 +205,7 @@ class NormalTestsCommon : LightningTestSuite() { @Test fun `recv ChannelCommand_Htlc_Add -- commit tx fee greater than remote initiator balance`() { val (alice0, bob0) = reachNormal(bobFundingAmount = 200_000.sat) - val (alice1, bob1) = addHtlc(824_160_000.msat, alice0, bob0).first + val (alice1, bob1) = addHtlc(836_220_000.msat, alice0, bob0).first val (alice2, bob2) = crossSign(alice1, bob1) assertEquals(0.msat, alice2.state.commitments.availableBalanceForSend()) @@ -221,7 +221,7 @@ class NormalTestsCommon : LightningTestSuite() { } // Add a bunch of HTLCs, which increases the commit tx fee that Alice has to pay and consume almost all of her balance. - val (alice3, bob3) = addHtlcs(alice2, bob2, 21) + val (alice3, bob3) = addHtlcs(alice2, bob2, 8) run { // We can sign those HTLCs and make Alice drop below her reserve. val (_, alice4) = crossSign(bob3, alice3) @@ -230,7 +230,6 @@ class NormalTestsCommon : LightningTestSuite() { assertTrue(commitTx.txOut.all { txOut -> txOut.amount > 0.sat }) val aliceBalance = aliceCommit.spec.toLocal - commitTxFeeMsat(alice4.commitments.latest.localCommitParams.dustLimit, aliceCommit.spec, alice4.commitments.latest.commitmentFormat) assertTrue(aliceBalance >= 0.msat) - assertTrue(aliceBalance < alice4.commitments.latest.localChannelReserve) } run { // If we try adding one more HTLC, Alice won't be able to pay the commit tx fee. @@ -249,7 +248,7 @@ class NormalTestsCommon : LightningTestSuite() { actionsAlice2.hasOutgoingMessage() val (_, actionsAlice3) = alice2.process(defaultAdd.copy(amount = 500_000_000.msat)) val actualError = actionsAlice3.findCommandError() - val expectError = InsufficientFunds(alice0.channelId, amount = 500_000_000.msat, missing = 278_780.sat, reserve = 10_000.sat, fees = 8_860.sat) + val expectError = InsufficientFunds(alice0.channelId, amount = 500_000_000.msat, missing = 267_220.sat, reserve = 0.sat, fees = 8_080.sat) assertEquals(expectError, actualError) } @@ -260,11 +259,12 @@ class NormalTestsCommon : LightningTestSuite() { actionsAlice1.hasOutgoingMessage() val (alice2, actionsAlice2) = alice1.process(defaultAdd.copy(amount = 200_000_000.msat)) actionsAlice2.hasOutgoingMessage() - val (alice3, actionsAlice3) = alice2.process(defaultAdd.copy(amount = 121_120_000.msat)) + val (alice3, actionsAlice3) = alice2.process(defaultAdd.copy(amount = 132_780_000.msat)) actionsAlice3.hasOutgoingMessage() + assertEquals(0.msat, alice3.commitments.availableBalanceForSend()) val (_, actionsAlice4) = alice3.process(defaultAdd.copy(amount = 1_000_000.msat)) val actualError = actionsAlice4.findCommandError() - val expectedError = InsufficientFunds(alice0.channelId, amount = 1_000_000.msat, missing = 900.sat, reserve = 10_000.sat, fees = 8_860.sat) + val expectedError = InsufficientFunds(alice0.channelId, amount = 1_000_000.msat, missing = 1_000.sat, reserve = 0.sat, fees = 8_080.sat) assertEquals(expectedError, actualError) } @@ -366,7 +366,7 @@ class NormalTestsCommon : LightningTestSuite() { val failAdd = defaultAdd.copy(amount = alice0.commitments.latest.fundingAmount.toMilliSatoshi() * 2 / 3) val (_, actionsAlice3) = alice2.process(failAdd) val actualError = actionsAlice3.findCommandError() - val expectedError = InsufficientFunds(alice0.channelId, failAdd.amount, 510_393.sat, 10_000.sat, 8_000.sat) + val expectedError = InsufficientFunds(alice0.channelId, failAdd.amount, 498_833.sat, 0.sat, 7_220.sat) assertEquals(expectedError, actualError) } @@ -414,20 +414,9 @@ class NormalTestsCommon : LightningTestSuite() { ) } - @Test - fun `recv UpdateAddHtlc -- simple taproot channels`() { - val (_, bob0) = reachNormal(channelType = ChannelType.SupportedChannelType.SimpleTaprootChannels) - val add = UpdateAddHtlc(bob0.channelId, 0, 15_000.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(bob0.currentBlockHeight.toLong()), TestConstants.emptyOnionPacket) - val (bob1, actions1) = bob0.process(ChannelCommand.MessageReceived(add)) - assertTrue(actions1.isEmpty()) - assertEquals( - bob0.copy(state = bob0.state.copy(commitments = bob0.commitments.copy(changes = bob0.commitments.changes.copy(remoteNextHtlcId = 1, remoteChanges = bob0.commitments.changes.remoteChanges.copy(proposed = listOf(add)))))), - bob1 - ) - } @Test fun `recv UpdateAddHtlc -- zero-reserve`() { - val (alice0, _) = reachNormal(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, bobFundingAmount = 10_000.sat) + val (alice0, _) = reachNormal(bobFundingAmount = 10_000.sat) assertEquals(alice0.commitments.availableBalanceForReceive(), 10_000_000.msat) val add = UpdateAddHtlc(alice0.channelId, 0, 10_000_000.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(alice0.currentBlockHeight.toLong()), TestConstants.emptyOnionPacket) val (alice1, actions1) = alice0.process(ChannelCommand.MessageReceived(add)) @@ -438,7 +427,7 @@ class NormalTestsCommon : LightningTestSuite() { @Test fun `recv UpdateAddHtlc -- zero-conf -- zero-reserve`() { - val (alice0, _) = reachNormal(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, bobFundingAmount = 10_000.sat, zeroConf = true) + val (alice0, _) = reachNormal(bobFundingAmount = 10_000.sat, zeroConf = true) assertEquals(alice0.commitments.availableBalanceForReceive(), 10_000_000.msat) val add = UpdateAddHtlc(alice0.channelId, 0, 10_000_000.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(alice0.currentBlockHeight.toLong()), TestConstants.emptyOnionPacket) val (alice1, actions1) = alice0.process(ChannelCommand.MessageReceived(add)) @@ -480,7 +469,7 @@ class NormalTestsCommon : LightningTestSuite() { val (bob1, actions1) = bob0.process(ChannelCommand.MessageReceived(add)) assertIs>(bob1) val error = actions1.hasOutgoingMessage() - assertEquals(error.toAscii(), InsufficientFunds(bob0.channelId, 800_000_000.msat, 17_140.sat, 10_000.sat, 7_140.sat).message) + assertEquals(error.toAscii(), InsufficientFunds(bob0.channelId, 800_000_000.msat, 6_360.sat, 0.sat, 6_360.sat).message) } @Test @@ -496,7 +485,7 @@ class NormalTestsCommon : LightningTestSuite() { val (bob4, actions4) = bob3.process(ChannelCommand.MessageReceived(add.copy(id = 3, amountMsat = 800_000_000.msat))) assertIs>(bob4) val error = actions4.hasOutgoingMessage() - assertEquals(error.toAscii(), InsufficientFunds(bob0.channelId, 800_000_000.msat, 14_720.sat, 10_000.sat, 9_720.sat).message) + assertEquals(error.toAscii(), InsufficientFunds(bob0.channelId, 800_000_000.msat, 3_940.sat, 0.sat, 8_940.sat).message) } @Test @@ -780,7 +769,7 @@ class NormalTestsCommon : LightningTestSuite() { @Test fun `recv CommitSig -- multiple htlcs in both directions`() { - val (alice0, bob0) = reachNormal(channelType = ChannelType.SupportedChannelType.SimpleTaprootChannels) + val (alice0, bob0) = reachNormal() val (nodes1, _, _) = addHtlc(50_000_000.msat, alice0, bob0) // a->b (regular) val (alice1, bob1) = nodes1 val (nodes2, _, _) = addHtlc(8_000_000.msat, alice1, bob1) // a->b (regular) @@ -1002,7 +991,7 @@ class NormalTestsCommon : LightningTestSuite() { @Test fun `recv RevokeAndAck -- multiple htlcs in both directions`() { - val (alice0, bob0) = reachNormal(ChannelType.SupportedChannelType.SimpleTaprootChannels) + val (alice0, bob0) = reachNormal() val (nodes1, _, add1) = addHtlc(50_000_000.msat, alice0, bob0) // a->b (regular) val (alice1, bob1) = nodes1 val (nodes2, _, add2) = addHtlc(8_000_000.msat, alice1, bob1) // a->b (regular) @@ -1418,7 +1407,7 @@ class NormalTestsCommon : LightningTestSuite() { actions.hasPublishTx(commitTx) actions.hasWatchConfirmed(commitTx.txid) val error = actions.findOutgoingMessage() - assertEquals(error.toAscii(), CannotAffordFees(bob.channelId, missing = 11_240.sat, reserve = 10_000.sat, fees = 26_580.sat).message) + assertEquals(error.toAscii(), CannotAffordFees(bob.channelId, missing = 9_680.sat, reserve = 0.sat, fees = 23_460.sat).message) } @Test @@ -1783,7 +1772,7 @@ class NormalTestsCommon : LightningTestSuite() { claimTx.txOut[0].amount }.sum() // at best we have a little less than 500 000 + 250 000 + 100 000 + 50 000 = 900 000 (because fees) - assertEquals(879_720.sat, amountClaimed) + assertEquals(880_880.sat, amountClaimed) val rcp = aliceClosing.state.remoteCommitPublished assertNotNull(rcp) @@ -1854,7 +1843,7 @@ class NormalTestsCommon : LightningTestSuite() { claimTx.txOut[0].amount }.sum() // at best we have a little less than 500 000 + 250 000 + 100 000 = 850 000 (because fees) - assertEquals(883_450.sat, amountClaimed) + assertEquals(884_535.sat, amountClaimed) val rcp = aliceClosing.state.nextRemoteCommitPublished assertNotNull(rcp) @@ -1938,12 +1927,12 @@ class NormalTestsCommon : LightningTestSuite() { actions2.hasWatchOutputsSpent(htlcInputs) // two main outputs are 760 000 and 200 000 (minus fees) - assertEquals(798_070.sat, mainTx.txOut[0].amount) - assertEquals(147_585.sat, penaltyTx.txOut[0].amount) - assertEquals(7_100.sat, htlcPenaltyTxs[0].txOut[0].amount) - assertEquals(7_100.sat, htlcPenaltyTxs[1].txOut[0].amount) - assertEquals(7_100.sat, htlcPenaltyTxs[2].txOut[0].amount) - assertEquals(7_100.sat, htlcPenaltyTxs[3].txOut[0].amount) + assertEquals(798_725.sat, mainTx.txOut[0].amount) + assertEquals(147_345.sat, penaltyTx.txOut[0].amount) + assertEquals(8_020.sat, htlcPenaltyTxs[0].txOut[0].amount) + assertEquals(8_020.sat, htlcPenaltyTxs[1].txOut[0].amount) + assertEquals(8_020.sat, htlcPenaltyTxs[2].txOut[0].amount) + assertEquals(8_020.sat, htlcPenaltyTxs[3].txOut[0].amount) } @Test 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 a88baccde..8447374de 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 @@ -23,7 +23,6 @@ class OfflineTestsCommon : LightningTestSuite() { @Test fun `handle disconnect - connect events in WaitForChannelReady -- zeroconf`() { val (alice, aliceCommitSig, bob, _) = WaitForFundingSignedTestsCommon.init( - ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, zeroConf = true, bobUsePeerStorage = false, ) @@ -78,7 +77,7 @@ class OfflineTestsCommon : LightningTestSuite() { ) assertEquals( ChannelReestablish(bob.channelId, 1, 0, PrivateKey(ByteVector32.Zeroes), bobCurrentPerCommitmentPoint), - channelReestablishB + channelReestablishB.copy(tlvStream = TlvStream.empty()) ) val (alice3, actions2) = alice2.process(ChannelCommand.MessageReceived(channelReestablishB)) @@ -125,9 +124,15 @@ class OfflineTestsCommon : LightningTestSuite() { val aliceCurrentPerCommitmentPoint = alice0.channelKeys.commitmentPoint(aliceCommitments.localCommitIndex) // alice didn't receive any update or sig - assertEquals(channelReestablishA, ChannelReestablish(alice0.channelId, 1, 0, PrivateKey(ByteVector32.Zeroes), aliceCurrentPerCommitmentPoint)) + assertEquals(1, channelReestablishA.nextLocalCommitmentNumber) + assertEquals(0, channelReestablishA.nextRemoteRevocationNumber) + assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishA.yourLastCommitmentSecret) + assertEquals(aliceCurrentPerCommitmentPoint, channelReestablishA.myCurrentPerCommitmentPoint) // bob did not receive alice's sig - assertEquals(channelReestablishB, ChannelReestablish(bob0.channelId, 1, 0, PrivateKey(ByteVector32.Zeroes), bobCurrentPerCommitmentPoint)) + assertEquals(1, channelReestablishB.nextLocalCommitmentNumber) + assertEquals(0, channelReestablishB.nextRemoteRevocationNumber) + assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishB.yourLastCommitmentSecret) + assertEquals(bobCurrentPerCommitmentPoint, channelReestablishB.myCurrentPerCommitmentPoint) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishB)) // alice sends ChannelReady again @@ -198,9 +203,15 @@ class OfflineTestsCommon : LightningTestSuite() { val aliceCurrentPerCommitmentPoint = alice0.channelKeys.commitmentPoint(aliceCommitments.localCommitIndex) // alice didn't receive any update or sig - assertEquals(channelReestablishA, ChannelReestablish(alice0.channelId, 1, 0, PrivateKey(ByteVector32.Zeroes), aliceCurrentPerCommitmentPoint)) + assertEquals(1, channelReestablishA.nextLocalCommitmentNumber) + assertEquals(0, channelReestablishA.nextRemoteRevocationNumber) + assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishA.yourLastCommitmentSecret) + assertEquals(aliceCurrentPerCommitmentPoint, channelReestablishA.myCurrentPerCommitmentPoint) // bob did receive alice's sig - assertEquals(channelReestablishB, ChannelReestablish(bob0.channelId, 2, 0, PrivateKey(ByteVector32.Zeroes), bobCurrentPerCommitmentPoint)) + assertEquals(2, channelReestablishB.nextLocalCommitmentNumber) + assertEquals(0, channelReestablishB.nextRemoteRevocationNumber) + assertEquals(PrivateKey(ByteVector32.Zeroes), channelReestablishB.yourLastCommitmentSecret) + assertEquals(bobCurrentPerCommitmentPoint, channelReestablishB.myCurrentPerCommitmentPoint) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishB)) // alice does not re-send messages bob already received @@ -564,7 +575,7 @@ class OfflineTestsCommon : LightningTestSuite() { @Test fun `republish unconfirmed funding tx after restart`() { - val (alice, bob, fundingTx) = WaitForFundingConfirmedTestsCommon.init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, fundingTx) = WaitForFundingConfirmedTestsCommon.init() // Alice restarts: val (alice1, actionsAlice1) = LNChannel(alice.ctx, WaitForInit).process(ChannelCommand.Init.Restore(alice.state)) assertEquals(alice1.state, Offline(alice.state)) @@ -581,7 +592,7 @@ class OfflineTestsCommon : LightningTestSuite() { @Test fun `republish unconfirmed funding tx with previous funding txs after restart`() { - val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init() val (alice1, bob1, fundingTx) = WaitForFundingConfirmedTestsCommon.rbf(alice, bob, walletAlice) assertEquals(alice1.commitments.active.size, 2) assertNotEquals(previousFundingTx.txid, fundingTx.txid) @@ -603,7 +614,7 @@ class OfflineTestsCommon : LightningTestSuite() { @Test fun `recv BITCOIN_FUNDING_DEPTHOK`() { - val (alice, bob, _) = WaitForFundingConfirmedTestsCommon.init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, _) = WaitForFundingConfirmedTestsCommon.init() val fundingTx = alice.state.latestFundingTx.sharedTx.tx.buildUnsignedTx() val (alice1, bob1) = disconnect(alice, bob) // outer state is Offline, we check the inner states @@ -629,7 +640,7 @@ class OfflineTestsCommon : LightningTestSuite() { @Test fun `recv BITCOIN_FUNDING_DEPTHOK -- previous funding tx`() { - val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init() val (alice1, bob1) = WaitForFundingConfirmedTestsCommon.rbf(alice, bob, walletAlice) val (alice2, bob2) = disconnect(alice1, bob1) assertIs(alice2.state.state) diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/ShutdownTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/ShutdownTestsCommon.kt index 74c65c300..011ba6d19 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/ShutdownTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/ShutdownTestsCommon.kt @@ -28,19 +28,9 @@ import kotlin.test.* class ShutdownTestsCommon : LightningTestSuite() { - @Test - fun `recv ChannelCommand_Htlc_Add`() { - val (_, bob) = init() - val add = ChannelCommand.Htlc.Add(500000000.msat, r1, cltvExpiry = CltvExpiry(300000), TestConstants.emptyOnionPacket, UUID.randomUUID()) - val (bob1, actions1) = bob.process(add) - assertIs>(bob1) - assertTrue(actions1.any { it is ChannelAction.ProcessCmdRes.AddFailed && it.error == ChannelUnavailable(bob.channelId) }) - assertEquals(bob1.commitments.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) - } - @Test fun `recv ChannelCommand_Htlc_Add -- zero-reserve`() { - val (_, bob) = init(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) + val (_, bob) = init() val add = ChannelCommand.Htlc.Add(500000000.msat, r1, cltvExpiry = CltvExpiry(300000), TestConstants.emptyOnionPacket, UUID.randomUUID()) val (bob1, actions1) = bob.process(add) assertIs>(bob1) @@ -277,7 +267,7 @@ class ShutdownTestsCommon : LightningTestSuite() { val sig = actionsBob2.hasOutgoingMessage() val (alice1, _) = alice0.process(ChannelCommand.MessageReceived(fulfill)) assertIs>(alice1) - val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(sig.copy(signature = ChannelSpendSignature.IndividualSignature(ByteVector64.Zeroes)))) + val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(sig.copy(tlvStream = TlvStream.empty()))) assertIs>(alice2) actionsAlice2.hasOutgoingMessage() assertNotNull(alice2.state.localCommitPublished) @@ -357,12 +347,9 @@ class ShutdownTestsCommon : LightningTestSuite() { @Test fun `recv CheckHtlcTimeout -- no htlc timed out`() { val (alice, _) = init() - - run { - val (alice1, actions1) = alice.process(ChannelCommand.Commitment.CheckHtlcTimeout) - assertEquals(alice, alice1) - assertTrue(actions1.isEmpty()) - } + val (alice1, actions1) = alice.process(ChannelCommand.Commitment.CheckHtlcTimeout) + assertEquals(alice, alice1) + assertTrue(actions1.isEmpty()) } @Test @@ -553,7 +540,7 @@ class ShutdownTestsCommon : LightningTestSuite() { val r2 = randomBytes32() fun init( - channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, + channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, currentBlockHeight: Int = TestConstants.defaultBlockHeight, aliceFeatures: Features = TestConstants.Alice.nodeParams.features, bobFeatures: Features = TestConstants.Bob.nodeParams.features, 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 2bad30934..1e2e2f44f 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 @@ -1,7 +1,6 @@ package fr.acinq.lightning.channel.states import fr.acinq.bitcoin.* -import fr.acinq.lightning.Feature import fr.acinq.lightning.Lightning.randomBytes32 import fr.acinq.lightning.Lightning.randomKey import fr.acinq.lightning.blockchain.WatchConfirmed @@ -32,18 +31,17 @@ import kotlinx.coroutines.runBlocking import kotlin.math.abs import kotlin.test.* -open class SpliceTestsCommon : LightningTestSuite() { - open val defaultChannelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels +class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice funds out`() { - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() spliceOut(alice, bob, 50_000.sat) } @Test fun `splice funds out and upgrade to taproot`() { - val (alice, bob) = reachNormal(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) + val (alice, bob) = reachNormal(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) assertEquals(Transactions.CommitmentFormat.AnchorOutputs, alice.commitments.latest.commitmentFormat) assertEquals(Transactions.CommitmentFormat.AnchorOutputs, bob.commitments.latest.commitmentFormat) @@ -54,24 +52,24 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice funds in`() { - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() spliceIn(alice, bob, listOf(50_000.sat)) } @Test fun `splice funds in and upgrade to taproot`() { - val (alice, bob) = reachNormal(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) + val (alice, bob) = reachNormal(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) assertEquals(Transactions.CommitmentFormat.AnchorOutputs, alice.commitments.latest.commitmentFormat) assertEquals(Transactions.CommitmentFormat.AnchorOutputs, bob.commitments.latest.commitmentFormat) - val (alice1, bob1) = spliceIn(alice, bob, listOf(50_000.sat), ChannelType.SupportedChannelType.SimpleTaprootChannels) + val (alice1, bob1) = spliceIn(alice, bob, listOf(50_000.sat)) assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, alice1.commitments.latest.commitmentFormat) assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, bob1.commitments.latest.commitmentFormat) } @Test fun `splice funds in and out with pending htlcs`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice1, bob1, htlcs) = setupHtlcs(alice, bob) val (alice2, bob2) = spliceInAndOut(alice1, bob1, inAmounts = listOf(50_000.sat), outAmount = 100_000.sat) @@ -106,7 +104,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice funds in and out with pending htlcs -- upgrade to taproot`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) val (alice1, bob1, htlcs) = setupHtlcs(alice, bob) val (alice2, bob2) = spliceInAndOut(alice1, bob1, inAmounts = listOf(50_000.sat), outAmount = 100_000.sat) @@ -141,7 +139,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice funds in and out with pending htlcs resolved after splice locked`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice1, bob1, htlcs) = setupHtlcs(alice, bob) val (alice2, bob2) = spliceInAndOut(alice1, bob1, inAmounts = listOf(50_000.sat), outAmount = 100_000.sat) val spliceTx = alice2.commitments.latest.localFundingStatus.signedTx!! @@ -156,24 +154,13 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice funds in -- non-initiator`() { - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() spliceIn(bob, alice, listOf(50_000.sat)) } - @Test - fun `splice funds in and upgrade to taproot -- non initiator`() { - val (alice, bob) = reachNormal(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, alice.commitments.latest.commitmentFormat) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, bob.commitments.latest.commitmentFormat) - - val (bob1, alice1) = spliceIn(bob, alice, listOf(50_000.sat), ChannelType.SupportedChannelType.SimpleTaprootChannels) - assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, alice1.commitments.latest.commitmentFormat) - assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, bob1.commitments.latest.commitmentFormat) - } - @Test fun `splice funds in -- many utxos`() { - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() spliceIn(alice, bob, listOf(30_000.sat, 40_000.sat, 25_000.sat)) } @@ -181,7 +168,7 @@ open class SpliceTestsCommon : LightningTestSuite() { fun `splice funds in -- local and remote commit index mismatch`() { // Alice and Bob asynchronously exchange HTLCs, which makes their commit indices diverge. val (nodes, preimages) = run { - val (alice0, bob0) = reachNormal(defaultChannelType) + val (alice0, bob0) = reachNormal() // Alice sends an HTLC to Bob and signs it. val (nodes1, preimage1, _) = addHtlc(15_000_000.msat, alice0, bob0) val (alice1, bob1) = nodes1 @@ -226,8 +213,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice funds out -- would go below reserve`() { - if (defaultChannelType.permanentChannelFeatures.contains(Feature.ZeroReserveChannels)) return - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx(channelType = ChannelType.SupportedChannelType.AnchorOutputs) val (alice1, bob1, _) = setupHtlcs(alice, bob) val cmd = createSpliceOutRequest(810_000.sat) val (alice2, actionsAlice2) = alice1.process(cmd) @@ -245,7 +231,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice cpfp`() { - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() val (nodes, preimage, _) = addHtlc(15_000_000.msat, alice, bob) val (alice0, bob0) = crossSign(nodes.first, nodes.second) val (alice1, bob1) = spliceIn(alice0, bob0, listOf(50_000.sat)) @@ -262,7 +248,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice cpfp -- not enough funds`() { - val (alice, bob) = reachNormal(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, aliceFundingAmount = 75_000.sat, bobFundingAmount = 25_000.sat) + val (alice, bob) = reachNormal(aliceFundingAmount = 75_000.sat, bobFundingAmount = 25_000.sat) val (alice1, bob1) = spliceOut(alice, bob, 65_000.sat) // After the splice-out, Alice doesn't have enough funds to pay the mining fees to CPFP. val spliceCpfp = ChannelCommand.Commitment.Splice.Request( @@ -290,7 +276,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice to purchase inbound liquidity`() { - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() val fundingRates = LiquidityAds.WillFundRates( fundingRates = listOf(LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 0, 250 /* 2.5% */, 0.sat, 1000.sat)), paymentTypes = setOf(LiquidityAds.PaymentType.FromChannelBalance), @@ -305,11 +291,11 @@ open class SpliceTestsCommon : LightningTestSuite() { val (_, actionsBob2) = bob1.process(ChannelCommand.MessageReceived(spliceInit)) val defaultSpliceAck = actionsBob2.findOutgoingMessage() assertNull(defaultSpliceAck.willFund) - val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubkey, defaultSpliceAck.fundingPubkey, defaultChannelType.commitmentFormat).pubkeyScript + val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubkey, defaultSpliceAck.fundingPubkey, Transactions.CommitmentFormat.SimpleTaprootChannels).pubkeyScript run { val willFund = fundingRates.validateRequest(bob.staticParams.nodeParams.nodePrivateKey, fundingScript, cmd.feerate, spliceInit.requestFunding!!, isChannelCreation = false, 0.msat)?.willFund assertNotNull(willFund) - val spliceAck = SpliceAck(alice.channelId, liquidityRequest.requestedAmount, defaultSpliceAck.fundingPubkey, willFund) + val spliceAck = SpliceAck(alice.channelId, liquidityRequest.requestedAmount, defaultSpliceAck.fundingPubkey, willFund, channelType = null) val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(spliceAck)) assertIs(alice2.state) assertIs(alice2.state.spliceStatus) @@ -328,7 +314,7 @@ open class SpliceTestsCommon : LightningTestSuite() { // Bob uses a different funding script than what Alice expects. val willFund = fundingRates.validateRequest(bob.staticParams.nodeParams.nodePrivateKey, ByteVector("deadbeef"), cmd.feerate, spliceInit.requestFunding!!, isChannelCreation = false, 0.msat)?.willFund assertNotNull(willFund) - val spliceAck = SpliceAck(alice.channelId, liquidityRequest.requestedAmount, defaultSpliceAck.fundingPubkey, willFund) + val spliceAck = SpliceAck(alice.channelId, liquidityRequest.requestedAmount, defaultSpliceAck.fundingPubkey, willFund, channelType = null) val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(spliceAck)) assertIs(alice2.state) assertIs(alice2.state.spliceStatus) @@ -336,7 +322,7 @@ open class SpliceTestsCommon : LightningTestSuite() { } run { // Bob doesn't fund the splice. - val spliceAck = SpliceAck(alice.channelId, liquidityRequest.requestedAmount, defaultSpliceAck.fundingPubkey, willFund = null) + val spliceAck = SpliceAck(alice.channelId, liquidityRequest.requestedAmount, defaultSpliceAck.fundingPubkey, willFund = null, channelType = null) val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(spliceAck)) assertIs(alice2.state) assertIs(alice2.state.spliceStatus) @@ -346,7 +332,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice to purchase inbound liquidity -- not enough funds`() { - val (alice, bob) = reachNormal(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, aliceFundingAmount = 100_000.sat, bobFundingAmount = 10_000.sat) + val (alice, bob) = reachNormal(aliceFundingAmount = 100_000.sat, bobFundingAmount = 10_000.sat) val fundingRate = LiquidityAds.FundingRate(100_000.sat, 10_000_000.sat, 0, 100 /* 1% */, 0.sat, 1000.sat) val fundingRates = LiquidityAds.WillFundRates(listOf(fundingRate), setOf(LiquidityAds.PaymentType.FromChannelBalance, LiquidityAds.PaymentType.FromFutureHtlc)) run { @@ -385,7 +371,7 @@ open class SpliceTestsCommon : LightningTestSuite() { val spliceAck = actionsAlice2.hasOutgoingMessage() // We don't implement the liquidity provider side, so we must fake it. assertNull(spliceAck.willFund) - val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubkey, spliceAck.fundingPubkey, Transactions.CommitmentFormat.AnchorOutputs).pubkeyScript + val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubkey, spliceAck.fundingPubkey, Transactions.CommitmentFormat.SimpleTaprootChannels).pubkeyScript val willFund = fundingRates.validateRequest(alice.staticParams.nodeParams.nodePrivateKey, fundingScript, cmd.feerate, spliceInit.requestFunding!!, isChannelCreation = false, 0.msat)!!.willFund val (_, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(spliceAck.copy(fundingContribution = liquidityRequest.requestedAmount, tlvStream = TlvStream(ChannelTlv.ProvideFundingTlv(willFund))))) assertEquals(1, actionsBob3.size) @@ -406,7 +392,7 @@ open class SpliceTestsCommon : LightningTestSuite() { val spliceAck = actionsAlice2.hasOutgoingMessage() // We don't implement the liquidity provider side, so we must fake it. assertNull(spliceAck.willFund) - val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubkey, spliceAck.fundingPubkey, Transactions.CommitmentFormat.AnchorOutputs).pubkeyScript + val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubkey, spliceAck.fundingPubkey, Transactions.CommitmentFormat.SimpleTaprootChannels).pubkeyScript val willFund = fundingRates.validateRequest(alice.staticParams.nodeParams.nodePrivateKey, fundingScript, cmd.feerate, spliceInit.requestFunding!!, isChannelCreation = false, 0.msat)!!.willFund val (_, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(spliceAck.copy(fundingContribution = liquidityRequest.requestedAmount, tlvStream = TlvStream(ChannelTlv.ProvideFundingTlv(willFund))))) assertEquals(1, actionsBob3.size) @@ -416,7 +402,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `splice to purchase inbound liquidity -- not enough funds but on-the-fly funding`() { - val (alice, bob) = reachNormal(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, bobFundingAmount = 0.sat) + val (alice, bob) = reachNormal(bobFundingAmount = 0.sat) val fundingRate = LiquidityAds.FundingRate(0.sat, 500_000.sat, 0, 50, 0.sat, 1000.sat) val fundingRates = LiquidityAds.WillFundRates(listOf(fundingRate), setOf(LiquidityAds.PaymentType.FromChannelBalanceForFutureHtlc, LiquidityAds.PaymentType.FromFutureHtlc)) val origin = Origin.OffChainPayment(randomBytes32(), 25_000_000.msat, ChannelManagementFees(0.sat, 500.sat)) @@ -472,7 +458,7 @@ open class SpliceTestsCommon : LightningTestSuite() { val spliceAck = actionsAlice2.hasOutgoingMessage() // We don't implement the liquidity provider side, so we must fake it. assertNull(spliceAck.willFund) - val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubkey, spliceAck.fundingPubkey, Transactions.CommitmentFormat.AnchorOutputs).pubkeyScript + val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubkey, spliceAck.fundingPubkey, Transactions.CommitmentFormat.SimpleTaprootChannels).pubkeyScript val willFund = fundingRates.validateRequest(alice.staticParams.nodeParams.nodePrivateKey, fundingScript, cmd.feerate, spliceInit.requestFunding!!, isChannelCreation = false, 0.msat)!!.willFund val (_, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(spliceAck.copy(fundingContribution = fundingRequest.requestedAmount, tlvStream = TlvStream(ChannelTlv.ProvideFundingTlv(willFund))))) actionsBob3.hasOutgoingMessage() @@ -482,7 +468,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `reject splice_init`() { val cmd = createSpliceOutRequest(25_000.sat) - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() val (nodes, _, _) = addHtlc(15_000_000.msat, alice, bob) val (alice0, bob0) = crossSign(nodes.first, nodes.second) val (alice1, _, _) = reachQuiescent(cmd, alice0, bob0) @@ -496,7 +482,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `reject splice_init -- cancel on-the-fly funding`() { val cmd = createSpliceOutRequest(50_000.sat) - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() val (alice1, _, _) = reachQuiescent(cmd, alice, bob) val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(CancelOnTheFlyFunding(alice.channelId, listOf(randomBytes32()), "cancelling on-the-fly funding"))) assertIs(alice2.state) @@ -507,7 +493,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `reject splice_ack`() { val cmd = createSpliceOutRequest(25_000.sat) - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() val (nodes, _, _) = addHtlc(15_000_000.msat, alice, bob) val (alice0, bob0) = crossSign(nodes.first, nodes.second) val (_, bob1, spliceInit) = reachQuiescent(cmd, alice0, bob0) @@ -524,7 +510,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `abort before tx_complete`() { val cmd = createSpliceOutRequest(20_000.sat) - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() val (nodes, _, _) = addHtlc(15_000_000.msat, alice, bob) val (alice0, bob0) = crossSign(nodes.first, nodes.second) val (alice1, bob1, spliceInit) = reachQuiescent(cmd, alice0, bob0) @@ -553,7 +539,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `abort after tx_complete`() { val cmd = createSpliceOutRequest(31_000.sat) - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() val (nodes, _, _) = addHtlc(15_000_000.msat, alice, bob) val (alice0, bob0) = crossSign(nodes.first, nodes.second) val (alice1, bob1, spliceInit) = reachQuiescent(cmd, alice0, bob0) @@ -590,7 +576,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `abort after tx_complete then receive commit_sig`() { val cmd = createSpliceOutRequest(50_000.sat) - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() val (nodes, _, _) = addHtlc(15_000_000.msat, alice, bob) val (alice0, bob0) = crossSign(nodes.first, nodes.second) val (alice1, bob1, spliceInit) = reachQuiescent(cmd, alice0, bob0) @@ -625,7 +611,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `exchange splice_locked`() { - val (alice, bob) = reachNormal(defaultChannelType) + val (alice, bob) = reachNormal() val (alice1, bob1) = spliceOut(alice, bob, 60_000.sat) val spliceTx = alice1.commitments.latest.localFundingStatus.signedTx!! @@ -806,7 +792,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- commit_sig not received`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) val aliceCommitIndex = alice0.commitments.localCommitIndex val bobCommitIndex = bob0.commitments.localCommitIndex @@ -836,7 +822,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- commit_sig received by alice`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice1, bob1, htlcs) = setupHtlcs(alice, bob) val aliceCommitIndex = alice1.commitments.localCommitIndex val bobCommitIndex = bob1.commitments.localCommitIndex @@ -891,7 +877,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- commit_sig received by bob`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) val aliceCommitIndex = alice0.commitments.localCommitIndex val bobCommitIndex = bob0.commitments.localCommitIndex @@ -1007,7 +993,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- tx_signatures received by alice -- confirms while bob is offline`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) val (alice1, commitSigAlice, bob1, commitSigBob) = spliceInAndOutWithoutSigs(alice0, bob0, inAmounts = listOf(70_000.sat, 60_000.sat), outAmount = 150_000.sat) @@ -1061,7 +1047,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- tx_signatures received by alice`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) val (alice1, commitSigAlice, bob1, commitSigBob) = spliceInAndOutWithoutSigs(alice0, bob0, inAmounts = listOf(315_000.sat), outAmount = 25_000.sat) val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(commitSigBob)) @@ -1100,7 +1086,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- new changes before splice_locked`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice1, bob1) = spliceOut(alice, bob, 70_000.sat) val (nodes2, _, htlc) = addHtlc(50_000_000.msat, alice1, bob1) val (alice3, actionsAlice3) = nodes2.first.process(ChannelCommand.Commitment.Sign) @@ -1166,7 +1152,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- splice_locked sent`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) val (alice1, bob1) = spliceInAndOut(alice0, bob0, inAmounts = listOf(150_000.sat, 25_000.sat, 15_000.sat), outAmount = 250_000.sat) val spliceTx = alice1.commitments.latest.localFundingStatus.signedTx!! @@ -1255,7 +1241,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- latest commitment locked remotely but not locally`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) val (alice1, bob1) = spliceIn(alice0, bob0, listOf(50_000.sat)) val spliceTx1 = alice1.commitments.latest.localFundingStatus.signedTx!! @@ -1318,7 +1304,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `disconnect -- splice tx published`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, _) = setupHtlcs(alice, bob) val (alice1, bob1) = spliceOut(alice0, bob0, 40_000.sat) val spliceTx = alice1.commitments.latest.localFundingStatus.signedTx!! @@ -1338,7 +1324,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `force-close -- latest active commitment`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, _) = setupHtlcs(alice, bob) val (alice1, bob1) = spliceOut(alice0, bob0, 75_000.sat) @@ -1365,15 +1351,12 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `force-close -- latest active commitment -- alternative feerate`() { - // README: we skip alternative feerate tests for taproot channels - if (defaultChannelType.commitmentFormat is Transactions.CommitmentFormat.SimpleTaprootChannels) return - - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) val (alice1, commitSigAlice, bob1, commitSigBob) = spliceOutWithoutSigs(alice, bob, 75_000.sat) val (alice2, bob2) = exchangeSpliceSigs(alice1, commitSigAlice, bob1, commitSigBob) // Bob force-closes using the latest active commitment and an optional feerate. - val bobCommitTx = useAlternativeCommitSig(bob2, bob2.commitments.active.first(), commitSigAlice.alternativeFeerateSigs.last()) + val bobCommitTx = useAlternativeCommitSig(bob2, bob2.commitments.active.first(), Commitments.alternativeFeerates.last()) val commitment = alice1.commitments.active.first() val (alice3, actionsAlice3) = alice2.process(ChannelCommand.WatchReceived(WatchSpentTriggered(alice.channelId, WatchSpent.ChannelSpent(TestConstants.fundingAmount), bobCommitTx))) assertIs>(alice3) @@ -1383,7 +1366,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `force-close -- previous active commitment`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, _) = setupHtlcs(alice, bob) val (alice1, bob1) = spliceOut(alice0, bob0, 75_000.sat) @@ -1395,10 +1378,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `force-close -- previous active commitment -- alternative feerate`() { - // README: we skip alternative feerate tests for taproot channels - if (defaultChannelType.commitmentFormat is Transactions.CommitmentFormat.SimpleTaprootChannels) return - - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) val (alice1, commitSigAlice1, bob1, commitSigBob1) = spliceOutWithoutSigs(alice, bob, 75_000.sat) val (alice2, bob2) = exchangeSpliceSigs(alice1, commitSigAlice1, bob1, commitSigBob1) val (alice3, commitSigAlice3, bob3, commitSigBob3) = spliceOutWithoutSigs(alice2, bob2, 75_000.sat) @@ -1406,7 +1386,7 @@ open class SpliceTestsCommon : LightningTestSuite() { // Bob force-closes using an older active commitment with an alternative feerate. assertEquals(bob4.commitments.active.map { it.localCommit.txId }.toSet().size, 3) - val bobCommitTx = useAlternativeCommitSig(bob4, bob4.commitments.active[1], commitSigAlice1.alternativeFeerateSigs.first()) + val bobCommitTx = useAlternativeCommitSig(bob4, bob4.commitments.active[1], Commitments.alternativeFeerates.first()) handlePreviousRemoteClose(alice4, bobCommitTx) } @@ -1431,7 +1411,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `force-close -- revoked latest active commitment`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, _) = setupHtlcs(alice, bob) val (alice1, bob1) = spliceOut(alice0, bob0, 50_000.sat) val bobCommitTx = bob1.commitments.active.first().fullySignedCommitTx(bob.commitments.channelParams, bob.channelKeys) @@ -1448,13 +1428,10 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `force-close -- revoked latest active commitment -- alternative feerate`() { - // README: we skip alternative feerate tests for taproot channels - if (defaultChannelType.commitmentFormat is Transactions.CommitmentFormat.SimpleTaprootChannels) return - - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) val (alice1, commitSigAlice, bob1, commitSigBob) = spliceOutWithoutSigs(alice, bob, 50_000.sat) val (alice2, bob2) = exchangeSpliceSigs(alice1, commitSigAlice, bob1, commitSigBob) - val bobCommitTx = useAlternativeCommitSig(bob2, bob2.commitments.active.first(), commitSigAlice.alternativeFeerateSigs.first()) + val bobCommitTx = useAlternativeCommitSig(bob2, bob2.commitments.active.first(), Commitments.alternativeFeerates.first()) // Alice sends an HTLC to Bob, which revokes the previous commitment. val (nodes3, _, _) = addHtlc(25_000_000.msat, alice2, bob2) @@ -1468,7 +1445,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `force-close -- revoked previous active commitment`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) // We make a first splice transaction, but don't exchange splice_locked. val (alice1, bob1) = spliceOut(alice0, bob0, 50_000.sat) @@ -1548,7 +1525,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `force-close -- revoked previous active commitment -- after taproot upgrade`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) + val (alice, bob) = reachNormalWithConfirmedFundingTx(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve) val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) assertEquals(alice0.commitments.latest.commitmentFormat, Transactions.CommitmentFormat.AnchorOutputs) assertEquals(bob0.commitments.latest.commitmentFormat, Transactions.CommitmentFormat.AnchorOutputs) @@ -1738,7 +1715,7 @@ open class SpliceTestsCommon : LightningTestSuite() { @Test fun `recv invalid htlc signatures during splice`() { - val (alice, bob) = reachNormalWithConfirmedFundingTx(defaultChannelType) + val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice1, bob1, htlcs) = setupHtlcs(alice, bob) val (alice2, commitSigAlice, bob2, commitSigBob) = spliceInAndOutWithoutSigs(alice1, bob1, inAmounts = listOf(50_000.sat), outAmount = 100_000.sat) assertEquals(commitSigAlice.htlcSignatures.size, 4) @@ -1759,7 +1736,7 @@ open class SpliceTestsCommon : LightningTestSuite() { companion object { private val spliceFeerate = FeeratePerKw(253.sat) - private fun reachNormalWithConfirmedFundingTx(channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, zeroConf: Boolean = false): Pair, LNChannel> { + private fun reachNormalWithConfirmedFundingTx(channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, zeroConf: Boolean = false): Pair, LNChannel> { val (alice, bob) = reachNormal(channelType = channelType, zeroConf = zeroConf) return when (val fundingStatus = alice.commitments.latest.localFundingStatus) { is LocalFundingStatus.UnconfirmedFundingTx -> { @@ -1819,7 +1796,7 @@ open class SpliceTestsCommon : LightningTestSuite() { return UnsignedSpliceFixture(alice5, commitSigAlice, bob6, commitSigBob) } - fun spliceIn(alice: LNChannel, bob: LNChannel, amounts: List, channelType: ChannelType? = null): Pair, LNChannel> { + fun spliceIn(alice: LNChannel, bob: LNChannel, amounts: List): Pair, LNChannel> { val parentCommitment = alice.commitments.active.first() val cmd = ChannelCommand.Commitment.Splice.Request( replyTo = CompletableDeferred(), @@ -2213,7 +2190,3 @@ open class SpliceTestsCommon : LightningTestSuite() { } } - -class SpliceWithTaprootChannelsTestsCommon : SpliceTestsCommon() { - override val defaultChannelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels -} \ No newline at end of file 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 fddf45f9d..c61872fe9 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 @@ -55,7 +55,7 @@ class SyncingTestsCommon : LightningTestSuite() { @Test fun `reestablish channel with previous funding txs`() { - val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init() val (alice1, bob1, fundingTx) = WaitForFundingConfirmedTestsCommon.rbf(alice, bob, walletAlice) assertNotEquals(previousFundingTx.txid, fundingTx.txid) val (alice2, bob2, channelReestablishAlice, channelReestablishBob0) = disconnectWithBackup(alice1, bob1) @@ -63,13 +63,13 @@ class SyncingTestsCommon : LightningTestSuite() { assertNull(channelReestablishAlice.nextFundingTxId) val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(channelReestablishAlice)) - assertEquals(bob1, bob3) + assertEquals(bob1.commitments, bob3.commitments) assertEquals(1, actionsBob3.size) val channelReestablishBob = actionsBob3.hasOutgoingMessage() assertNull(channelReestablishBob.nextFundingTxId) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishBob)) - assertEquals(alice1, alice3) + assertEquals(alice1.commitments, alice3.commitments) assertTrue(actionsAlice3.isEmpty()) } @@ -350,7 +350,7 @@ class SyncingTestsCommon : LightningTestSuite() { @Test fun `recv ChannelFundingDepthOk`() { - val (alice, bob, _) = WaitForFundingConfirmedTestsCommon.init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, _) = WaitForFundingConfirmedTestsCommon.init() val fundingTx = alice.state.latestFundingTx.sharedTx.tx.buildUnsignedTx() val (alice1, bob1, _) = disconnectWithBackup(alice, bob) assertIs(alice1.state.state) @@ -375,7 +375,7 @@ class SyncingTestsCommon : LightningTestSuite() { @Test fun `recv ChannelFundingDepthOk -- previous funding tx`() { - val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, previousFundingTx, walletAlice) = WaitForFundingConfirmedTestsCommon.init() val (alice1, bob1) = WaitForFundingConfirmedTestsCommon.rbf(alice, bob, walletAlice) val (alice2, bob2, _) = disconnectWithBackup(alice1, bob1) assertIs(alice2.state.state) diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannelTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannelTestsCommon.kt index a098cfc8b..292209d52 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannelTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannelTestsCommon.kt @@ -30,8 +30,8 @@ class WaitForAcceptChannelTestsCommon : LightningTestSuite() { val txAddInput = actions1.findOutgoingMessage() assertNotEquals(txAddInput.channelId, accept.temporaryChannelId) assertEquals(alice1.channelId, txAddInput.channelId) - assertEquals(alice1.state.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, alice1.state.interactiveTxSession.fundingParams.commitmentFormat) + assertEquals(alice1.state.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) + assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, alice1.state.interactiveTxSession.fundingParams.commitmentFormat) } @Test @@ -53,14 +53,14 @@ class WaitForAcceptChannelTestsCommon : LightningTestSuite() { assertEquals(3, actions1.size) actions1.find() actions1.findOutgoingMessage() - assertEquals(alice1.state.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, alice1.state.interactiveTxSession.fundingParams.commitmentFormat) + assertEquals(alice1.state.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) + assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, alice1.state.interactiveTxSession.fundingParams.commitmentFormat) assertEquals(ChannelEvents.Creating(alice1.state), actions1.find().event) } @Test fun `recv AcceptChannel -- zero conf`() { - val (alice, _, accept) = init(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, zeroConf = true) + val (alice, _, accept) = init(zeroConf = true) assertEquals(0, accept.minimumDepth) val (alice1, actions1) = alice.process(ChannelCommand.MessageReceived(accept)) assertIs>(alice1) @@ -69,7 +69,7 @@ class WaitForAcceptChannelTestsCommon : LightningTestSuite() { assertEquals(ChannelEvents.Creating(alice1.state), actions1.find().event) actions1.findOutgoingMessage() assertEquals(alice1.state.channelFeatures, ChannelFeatures(setOf(Feature.ZeroReserveChannels, Feature.DualFunding))) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, alice1.state.interactiveTxSession.fundingParams.commitmentFormat) + assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, alice1.state.interactiveTxSession.fundingParams.commitmentFormat) } @Test @@ -88,7 +88,7 @@ class WaitForAcceptChannelTestsCommon : LightningTestSuite() { val (alice1, actions1) = alice.process(ChannelCommand.MessageReceived(accept.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.UnsupportedChannelType(Features.empty)))))) assertIs>(alice1) val error = actions1.hasOutgoingMessage() - assertEquals(error, Error(accept.temporaryChannelId, InvalidChannelType(accept.temporaryChannelId, ChannelType.SupportedChannelType.AnchorOutputs, ChannelType.UnsupportedChannelType(Features.empty)).message)) + assertEquals(error, Error(accept.temporaryChannelId, InvalidChannelType(accept.temporaryChannelId, ChannelType.SupportedChannelType.SimpleTaprootChannels, ChannelType.UnsupportedChannelType(Features.empty)).message)) } @Test @@ -189,7 +189,7 @@ class WaitForAcceptChannelTestsCommon : LightningTestSuite() { companion object { fun init( - channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, + channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, aliceFeatures: Features = TestConstants.Alice.nodeParams.features, bobFeatures: Features = TestConstants.Bob.nodeParams.features, currentHeight: Int = TestConstants.defaultBlockHeight, diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForChannelReadyTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForChannelReadyTestsCommon.kt index 787a7b711..a82cdc16b 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForChannelReadyTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForChannelReadyTestsCommon.kt @@ -21,7 +21,7 @@ class WaitForChannelReadyTestsCommon : LightningTestSuite() { @Test fun `recv TxSignatures -- zero conf`() { - val (alice, _, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, zeroConf = true) + val (alice, _, bob, _) = init(zeroConf = true) val txSigsAlice = getFundingSigs(alice) val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(txSigsAlice)) assertIs(bob1.state) @@ -32,7 +32,7 @@ class WaitForChannelReadyTestsCommon : LightningTestSuite() { @Test fun `recv TxSignatures and restart -- zero conf`() { - val (alice, _, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, zeroConf = true) + val (alice, _, bob, _) = init(zeroConf = true) val txSigsAlice = getFundingSigs(alice) val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(txSigsAlice)) val fundingTx = actionsBob1.find().tx @@ -53,7 +53,7 @@ class WaitForChannelReadyTestsCommon : LightningTestSuite() { @Test fun `recv TxSignatures -- invalid`() { - val (alice, _, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, zeroConf = true) + val (alice, _, bob, _) = init(zeroConf = true) val invalidTxSigsAlice = getFundingSigs(alice).copy(tlvs = TlvStream.empty()) val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(invalidTxSigsAlice)) assertEquals(bob, bob1) @@ -170,7 +170,7 @@ class WaitForChannelReadyTestsCommon : LightningTestSuite() { data class Fixture(val alice: LNChannel, val channelReadyAlice: ChannelReady, val bob: LNChannel, val channelReadyBob: ChannelReady) fun init( - channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, + channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, aliceFeatures: Features = TestConstants.Alice.nodeParams.features, bobFeatures: Features = TestConstants.Bob.nodeParams.features, bobUsePeerStorage: Boolean = true, diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmedTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmedTestsCommon.kt index 142663938..fb0408e79 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmedTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmedTestsCommon.kt @@ -23,7 +23,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv TxSignatures -- duplicate`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, _) = init() val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(bob.state.latestFundingTx.sharedTx.localSigs)) assertIs(alice1.state) assertEquals(alice1.state.rbfStatus, RbfStatus.RbfAborted) @@ -33,7 +33,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelFundingDepthOk`() { - val (alice, bob, fundingTx) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, fundingTx) = init() run { val (alice1, actionsAlice1) = alice.process(ChannelCommand.WatchReceived(WatchConfirmedTriggered(alice.state.channelId, WatchConfirmed.ChannelFundingDepthOk, 42, 0, fundingTx))) assertIs(alice1.state) @@ -60,7 +60,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelFundingDepthOk -- rbf in progress`() { - val (alice, bob, fundingTx) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, fundingTx) = init() val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(TxInitRbf(alice.state.channelId, 0, FeeratePerKw(6000.sat), TestConstants.aliceFundingAmount))) assertIs(bob1.state) assertIs(bob1.state.rbfStatus) @@ -77,7 +77,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelFundingDepthOk -- previous funding tx`() { - val (alice, bob, previousFundingTx, walletAlice) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, previousFundingTx, walletAlice) = init() val (alice1, bob1, fundingTx) = rbf(alice, bob, walletAlice) assertNotEquals(previousFundingTx.txid, fundingTx.txid) run { @@ -106,7 +106,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelFundingDepthOk -- after restart`() { - val (alice, bob, fundingTx) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, fundingTx) = init() run { val (alice1, _) = LNChannel(alice.ctx, WaitForInit).process(ChannelCommand.Init.Restore(alice.state)) .also { (state, actions) -> @@ -143,7 +143,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelFundingDepthOk -- after restart -- previous funding tx`() { - val (alice, bob, fundingTx1, walletAlice) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, fundingTx1, walletAlice) = init() val (alice1, bob1, fundingTx2) = rbf(alice, bob, walletAlice) run { val (alice2, _) = LNChannel(alice.ctx, WaitForInit).process(ChannelCommand.Init.Restore(alice1.state)) @@ -189,7 +189,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv TxInitRbf`() { - val (alice, bob, _, walletAlice) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, _, walletAlice) = init() val (alice1, bob1) = rbf(alice, bob, walletAlice) assertEquals(alice1.state.previousFundingTxs.size, 1) assertEquals(bob1.state.previousFundingTxs.size, 1) @@ -200,7 +200,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv TxInitRbf -- invalid feerate`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, _) = init() val (bob1, actions1) = bob.process(ChannelCommand.MessageReceived(TxInitRbf(alice.state.channelId, 0, TestConstants.feeratePerKw, alice.state.latestFundingTx.fundingParams.localContribution))) assertEquals(actions1.size, 1) assertEquals(actions1.hasOutgoingMessage().toAscii(), InvalidRbfFeerate(alice.state.channelId, TestConstants.feeratePerKw, TestConstants.feeratePerKw * 25 / 24).message) @@ -211,7 +211,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv TxInitRbf -- failed rbf attempt`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, _) = init() val (bob1, actions1) = bob.process(ChannelCommand.MessageReceived(TxInitRbf(alice.state.channelId, 0, TestConstants.feeratePerKw * 1.25, alice.state.latestFundingTx.fundingParams.localContribution))) assertIs(bob1.state) assertIs(bob1.state.rbfStatus) @@ -230,7 +230,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelReady`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, _) = init() val channelReadyAlice = ChannelReady(alice.state.channelId, randomKey().publicKey()) val channelReadyBob = ChannelReady(bob.state.channelId, randomKey().publicKey()) val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(channelReadyBob)) @@ -245,7 +245,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelReady -- no remote contribution`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (alice, bob, _) = init(bobFundingAmount = 0.sat) val channelReadyAlice = ChannelReady(alice.state.channelId, randomKey().publicKey()) val channelReadyBob = ChannelReady(bob.state.channelId, randomKey().publicKey()) val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(channelReadyBob)) @@ -260,7 +260,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv Error`() { - val (_, bob) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (_, bob) = init() val (bob1, actions1) = bob.process(ChannelCommand.MessageReceived(Error(bob.state.channelId, "oops"))) assertIs(bob1.state) assertNotNull(bob1.state.localCommitPublished) @@ -274,7 +274,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv Error -- previous funding tx confirms`() { - val (alice, bob, previousFundingTx, walletAlice) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, previousFundingTx, walletAlice) = init() val commitTxAlice1 = alice.signCommitTx() Transaction.correctlySpends(commitTxAlice1, listOf(previousFundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val commitTxBob1 = bob.signCommitTx() @@ -341,7 +341,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelCommand_Close_MutualClose`() = runSuspendTest { - val (alice, bob) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob) = init() listOf(alice, bob).forEach { state -> val cmd = ChannelCommand.Close.MutualClose(CompletableDeferred(), null, TestConstants.feeratePerKw) val (state1, actions1) = state.process(cmd) @@ -353,7 +353,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelCommand_Close_ForceClose`() { - val (alice, bob) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob) = init() listOf(alice, bob).forEach { state -> val (state1, actions1) = state.process(ChannelCommand.Close.ForceClose) assertIs(state1.state) @@ -369,7 +369,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv CheckHtlcTimeout`() { - val (alice, bob) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob) = init() listOf(alice, bob).forEach { state -> run { val (state1, actions1) = state.process(ChannelCommand.Commitment.CheckHtlcTimeout) @@ -381,7 +381,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { @Test fun `recv Disconnected`() { - val (alice, bob) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob) = init() val (alice1, actionsAlice1) = alice.process(ChannelCommand.Disconnected) assertIs(alice1.state) assertTrue(actionsAlice1.isEmpty()) @@ -394,7 +394,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { data class Fixture(val alice: LNChannel, val bob: LNChannel, val fundingTx: Transaction, val walletAlice: List) fun init( - channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, + channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, aliceFeatures: Features = TestConstants.Alice.nodeParams.features.initFeatures(), bobFeatures: Features = TestConstants.Bob.nodeParams.features.initFeatures(), bobUsePeerStorage: Boolean = true, diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreatedTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreatedTestsCommon.kt index 93869269c..d8e14eb60 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreatedTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreatedTestsCommon.kt @@ -40,7 +40,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `complete interactive-tx protocol`() = runSuspendTest { - val (alice, bob, inputAlice) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (alice, bob, inputAlice) = init(bobFundingAmount = 0.sat) // Alice ---- tx_add_input ----> Bob val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(inputAlice)) // Alice <--- tx_complete ----- Bob @@ -60,8 +60,8 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { actionsBob3.has() assertIs(alice2.state) assertIs(bob3.state) - assertEquals(alice2.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) - assertEquals(bob3.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) + assertEquals(alice2.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) + assertEquals(bob3.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) assertIs(alice.state.replyTo.await()).also { assertEquals(0, it.fundingTxIndex) } assertIs(bob.state.replyTo.await()).also { assertEquals(0, it.fundingTxIndex) } verifyCommits(alice2.state.signingSession, bob3.state.signingSession, TestConstants.aliceFundingAmount.toMilliSatoshi(), 0.msat) @@ -69,7 +69,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `complete interactive-tx protocol -- with non-initiator contributions`() { - val (alice, bob, inputAlice) = init(ChannelType.SupportedChannelType.AnchorOutputs) + val (alice, bob, inputAlice) = init() // Alice ---- tx_add_input ----> Bob val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(inputAlice)) // Alice <--- tx_add_input ----- Bob @@ -87,8 +87,8 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { actionsBob3.has() assertIs(alice2.state) assertIs(bob3.state) - assertEquals(alice2.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) - assertEquals(bob3.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) + assertEquals(alice2.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) + assertEquals(bob3.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) verifyCommits( alice2.state.signingSession, bob3.state.signingSession, @@ -100,7 +100,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `complete interactive-tx protocol -- with large non-initiator contributions`() { // Alice's funding amount is below the channel reserve: this is ok as long as she can pay the commit tx fees. - val (alice, bob, inputAlice) = init(ChannelType.SupportedChannelType.AnchorOutputs, aliceFundingAmount = 10_000.sat, bobFundingAmount = 1_500_000.sat) + val (alice, bob, inputAlice) = init(aliceFundingAmount = 10_000.sat, bobFundingAmount = 1_500_000.sat) // Alice ---- tx_add_input ----> Bob val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(inputAlice)) // Alice <--- tx_add_input ----- Bob @@ -118,14 +118,14 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { actionsBob3.has() assertIs(alice2.state) assertIs(bob3.state) - assertEquals(alice2.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) - assertEquals(bob3.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) + assertEquals(alice2.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) + assertEquals(bob3.state.channelParams.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) verifyCommits(alice2.state.signingSession, bob3.state.signingSession, balanceAlice = 10_000_000.msat, balanceBob = 1_500_000_000.msat) } @Test fun `complete interactive-tx protocol -- zero conf -- zero reserve`() { - val (alice, bob, inputAlice) = init(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, zeroConf = true) + val (alice, bob, inputAlice) = init(zeroConf = true) // Alice ---- tx_add_input ----> Bob val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(inputAlice)) // Alice <--- tx_add_input ----- Bob @@ -150,7 +150,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `recv invalid interactive-tx message`() { - val (_, bob, inputAlice) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (_, bob, inputAlice) = init(bobFundingAmount = 0.sat) run { // Invalid serial_id. val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(inputAlice.copy(serialId = 1))) @@ -168,7 +168,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `recv CommitSig`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (alice, bob, _) = init(bobFundingAmount = 0.sat) run { val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(CommitSig(alice.channelId, ChannelSpendSignature.IndividualSignature(ByteVector64.Zeroes), listOf()))) assertEquals(actionsAlice1.findOutgoingMessage().toAscii(), UnexpectedCommitSig(alice.channelId).message) @@ -183,7 +183,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `recv TxSignatures`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (alice, bob, _) = init(bobFundingAmount = 0.sat) run { val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(TxSignatures(alice.channelId, TxId(randomBytes32()), listOf()))) assertEquals(actionsAlice1.findOutgoingMessage().toAscii(), UnexpectedFundingSignatures(alice.channelId).message) @@ -198,7 +198,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `recv TxAbort`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (alice, bob, _) = init(bobFundingAmount = 0.sat) run { val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(TxAbort(alice.channelId, "changed my mind"))) assertEquals(actionsAlice1.size, 1) @@ -215,7 +215,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `recv TxInitRbf`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (alice, bob, _) = init(bobFundingAmount = 0.sat) run { val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(TxInitRbf(alice.channelId, 0, FeeratePerKw(7500.sat)))) assertEquals(actionsAlice1.size, 1) @@ -232,7 +232,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `recv TxAckRbf`() { - val (alice, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (alice, bob, _) = init(bobFundingAmount = 0.sat) run { val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(TxAckRbf(alice.channelId))) assertEquals(actionsAlice1.size, 1) @@ -249,7 +249,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `recv Error`() = runSuspendTest { - val (_, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (_, bob, _) = init(bobFundingAmount = 0.sat) val (bob1, actions1) = bob.process(ChannelCommand.MessageReceived(Error(ByteVector32.Zeroes, "oops"))) assertIs(bob1.state) assertTrue(actions1.isEmpty()) @@ -258,7 +258,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `recv ChannelCommand_Close_ForceClose`() { - val (_, bob, _) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (_, bob, _) = init(bobFundingAmount = 0.sat) val (bob1, actions1) = bob.process(ChannelCommand.Close.ForceClose) assertEquals(actions1.findOutgoingMessage().toAscii(), ForcedLocalCommit(bob.channelId).message) assertIs(bob1.state) @@ -266,7 +266,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { @Test fun `recv Disconnected`() = runSuspendTest { - val (_, bob, txAddInput) = init(ChannelType.SupportedChannelType.AnchorOutputs, bobFundingAmount = 0.sat) + val (_, bob, txAddInput) = init(bobFundingAmount = 0.sat) val (bob1, _) = bob.process(ChannelCommand.MessageReceived(txAddInput)) assertIs(bob1.state) val (bob2, actions2) = bob1.process(ChannelCommand.Disconnected) @@ -279,7 +279,7 @@ class WaitForFundingCreatedTestsCommon : LightningTestSuite() { data class Fixture(val alice: LNChannel, val bob: LNChannel, val aliceInput: TxAddInput, val aliceWallet: List) fun init( - channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, + channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, aliceFeatures: Features = TestConstants.Alice.nodeParams.features.initFeatures(), bobFeatures: Features = TestConstants.Bob.nodeParams.features.initFeatures(), bobUsePeerStorage: Boolean = true, diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSignedTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSignedTestsCommon.kt index 7c0338e20..d5f5127fa 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSignedTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSignedTestsCommon.kt @@ -1,6 +1,7 @@ package fr.acinq.lightning.channel.states import fr.acinq.bitcoin.* +import fr.acinq.bitcoin.crypto.musig2.IndividualNonce import fr.acinq.lightning.* import fr.acinq.lightning.Lightning.randomBytes import fr.acinq.lightning.Lightning.randomBytes32 @@ -44,7 +45,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() { @Test fun `recv CommitSig -- zero conf`() { - val (alice, commitSigAlice, bob, commitSigBob) = init(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, zeroConf = true) + val (alice, commitSigAlice, bob, commitSigBob) = init(zeroConf = true) run { alice.process(ChannelCommand.MessageReceived(commitSigBob)).also { (state, actions) -> assertIs(state.state) @@ -65,30 +66,6 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() { } } - @Test - fun `recv CommitSig -- simple taproot channels`() { - val (alice, commitSigAlice, bob, commitSigBob) = init(ChannelType.SupportedChannelType.SimpleTaprootChannels) - val commitInput = alice.state.signingSession.commitInput(alice.channelKeys) - run { - alice.process(ChannelCommand.MessageReceived(commitSigBob)).also { (state, actions) -> - assertIs(state.state) - assertTrue(actions.isEmpty()) - } - } - run { - bob.process(ChannelCommand.MessageReceived(commitSigAlice)).also { (state, actions) -> - assertIs(state.state) - assertEquals(actions.size, 5) - actions.hasOutgoingMessage() - actions.findWatch() - .also { assertEquals(WatchConfirmed(state.channelId, commitInput.outPoint.txid, commitInput.txOut.publicKeyScript, bob.staticParams.nodeParams.minDepthBlocks, WatchConfirmed.ChannelFundingDepthOk), it) } - actions.find().also { assertEquals(TestConstants.bobFundingAmount.toMilliSatoshi(), it.amountReceived) } - actions.has() - actions.find().also { assertEquals(ChannelEvents.Created(state.state), it.event) } - } - } - } - @Test fun `recv CommitSig -- liquidity ads`() { val (alice, commitSigAlice, bob, commitSigBob) = init(requestRemoteFunding = TestConstants.bobFundingAmount) @@ -139,15 +116,16 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() { @Test fun `recv CommitSig -- with invalid signature`() { val (alice, commitSigAlice, bob, commitSigBob) = init() + val dummySig = ChannelSpendSignature.PartialSignatureWithNonce(randomBytes32(), IndividualNonce(randomBytes(66))) run { - val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(commitSigBob.copy(signature = ChannelSpendSignature.IndividualSignature(ByteVector64.Zeroes)))) + val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(commitSigBob.copy(tlvStream = TlvStream(CommitSigTlv.PartialSignatureWithNonce(dummySig))))) assertEquals(actionsAlice1.size, 2) actionsAlice1.hasOutgoingMessage() actionsAlice1.find().also { assertEquals(alice.channelId, it.data.channelId) } assertIs(alice1.state) } run { - val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(commitSigAlice.copy(signature = ChannelSpendSignature.IndividualSignature(ByteVector64.Zeroes)))) + val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(commitSigAlice.copy(tlvStream = TlvStream(CommitSigTlv.PartialSignatureWithNonce(dummySig))))) assertEquals(actionsBob1.size, 2) actionsBob1.hasOutgoingMessage() actionsBob1.find().also { assertEquals(bob.channelId, it.data.channelId) } @@ -182,33 +160,6 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() { } } - @Test - fun `recv TxSignatures -- simple taproot channels`() { - val (alice, commitSigAlice, bob, commitSigBob) = init(channelType = ChannelType.SupportedChannelType.SimpleTaprootChannels) - val commitInput = alice.state.signingSession.commitInput(alice.channelKeys) - val txSigsBob = run { - val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(commitSigAlice)) - assertIs(bob1.state) - actionsBob1.hasOutgoingMessage() - } - run { - val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(commitSigBob)) - assertIs(alice1.state) - assertTrue(actionsAlice1.isEmpty()) - val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(txSigsBob)) - assertIs(alice2.state) - assertEquals(6, actionsAlice2.size) - actionsAlice2.hasOutgoingMessage() - actionsAlice2.has() - val watchConfirmedAlice = actionsAlice2.findWatch() - assertEquals(WatchConfirmed(alice2.channelId, commitInput.outPoint.txid, commitInput.txOut.publicKeyScript, alice2.staticParams.nodeParams.minDepthBlocks, WatchConfirmed.ChannelFundingDepthOk), watchConfirmedAlice) - assertEquals(ChannelEvents.Created(alice2.state), actionsAlice2.find().event) - val fundingTx = actionsAlice2.find().tx - assertEquals(fundingTx.txid, txSigsBob.txId) - assertEquals(commitInput.outPoint.txid, fundingTx.txid) - } - } - @Test fun `recv TxSignatures -- liquidity ads`() { val (alice, commitSigAlice, bob, commitSigBob) = init(requestRemoteFunding = TestConstants.bobFundingAmount) @@ -244,7 +195,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() { @Test fun `recv TxSignatures -- zero-conf`() { - val (alice, commitSigAlice, bob, commitSigBob) = init(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, zeroConf = true) + val (alice, commitSigAlice, bob, commitSigBob) = init(zeroConf = true) val txSigsBob = run { val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(commitSigAlice)) assertIs(bob1.state) @@ -376,7 +327,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() { data class Fixture(val alice: LNChannel, val commitSigAlice: CommitSig, val bob: LNChannel, val commitSigBob: CommitSig, val walletAlice: List) fun init( - channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.AnchorOutputs, + channelType: ChannelType.SupportedChannelType = ChannelType.SupportedChannelType.SimpleTaprootChannels, aliceFeatures: Features = TestConstants.Alice.nodeParams.features, bobFeatures: Features = TestConstants.Bob.nodeParams.features, bobUsePeerStorage: Boolean = true, diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannelTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannelTestsCommon.kt index 21400371a..93602f2b2 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannelTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannelTestsCommon.kt @@ -23,13 +23,13 @@ class WaitForOpenChannelTestsCommon : LightningTestSuite() { @Test fun `recv OpenChannel -- without wumbo`() { val (_, bob, open) = TestsHelper.init(aliceFeatures = TestConstants.Alice.nodeParams.features.remove(Feature.Wumbo)) - assertEquals(open.tlvStream.get(), ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs)) + assertEquals(open.tlvStream.get(), ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.SimpleTaprootChannels)) val (bob1, actions) = bob.process(ChannelCommand.MessageReceived(open)) assertIs>(bob1) assertEquals(3, actions.size) assertTrue(bob1.state.channelConfig.hasOption(ChannelConfigOption.FundingPubKeyBasedChannelKeyPath)) - assertEquals(bob1.state.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, bob1.state.interactiveTxSession.fundingParams.commitmentFormat) + assertEquals(bob1.state.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) + assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, bob1.state.interactiveTxSession.fundingParams.commitmentFormat) actions.hasOutgoingMessage() actions.has() assertEquals(ChannelEvents.Creating(bob1.state), actions.find().event) @@ -41,8 +41,8 @@ class WaitForOpenChannelTestsCommon : LightningTestSuite() { val (bob1, actions) = bob.process(ChannelCommand.MessageReceived(open)) assertIs>(bob1) assertEquals(3, actions.size) - assertEquals(bob1.state.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding))) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, bob1.state.interactiveTxSession.fundingParams.commitmentFormat) + assertEquals(bob1.state.channelFeatures, ChannelFeatures(setOf(Feature.DualFunding, Feature.ZeroReserveChannels))) + assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, bob1.state.interactiveTxSession.fundingParams.commitmentFormat) actions.hasOutgoingMessage() actions.has() assertEquals(ChannelEvents.Creating(bob1.state), actions.find().event) @@ -50,13 +50,13 @@ class WaitForOpenChannelTestsCommon : LightningTestSuite() { @Test fun `recv OpenChannel -- zero conf -- zero reserve`() { - val (_, bob, open) = TestsHelper.init(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, zeroConf = true) + val (_, bob, open) = TestsHelper.init(zeroConf = true) val (bob1, actions) = bob.process(ChannelCommand.MessageReceived(open)) assertIs>(bob1) assertEquals(3, actions.size) assertTrue(bob1.state.channelConfig.hasOption(ChannelConfigOption.FundingPubKeyBasedChannelKeyPath)) assertEquals(bob1.state.channelFeatures, ChannelFeatures(setOf(Feature.ZeroReserveChannels, Feature.DualFunding))) - assertEquals(Transactions.CommitmentFormat.AnchorOutputs, bob1.state.interactiveTxSession.fundingParams.commitmentFormat) + assertEquals(Transactions.CommitmentFormat.SimpleTaprootChannels, bob1.state.interactiveTxSession.fundingParams.commitmentFormat) val accept = actions.hasOutgoingMessage() assertEquals(0, accept.minimumDepth) actions.has() @@ -81,7 +81,7 @@ class WaitForOpenChannelTestsCommon : LightningTestSuite() { val open1 = open.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(unsupportedChannelType))) val (bob1, actions) = bob.process(ChannelCommand.MessageReceived(open1)) val error = actions.findOutgoingMessage() - assertEquals(error, Error(open.temporaryChannelId, InvalidChannelType(open.temporaryChannelId, ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, unsupportedChannelType).message)) + assertEquals(error, Error(open.temporaryChannelId, InvalidChannelType(open.temporaryChannelId, ChannelType.SupportedChannelType.SimpleTaprootChannels, unsupportedChannelType).message)) assertIs>(bob1) } 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 14b132774..92c38fbd8 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 @@ -28,7 +28,6 @@ import fr.acinq.lightning.tests.TestConstants import fr.acinq.lightning.tests.io.peer.* import fr.acinq.lightning.tests.utils.LightningTestSuite import fr.acinq.lightning.tests.utils.runSuspendTest -import fr.acinq.lightning.tests.utils.testLoggerFactory import fr.acinq.lightning.utils.UUID import fr.acinq.lightning.utils.msat import fr.acinq.lightning.utils.sat @@ -129,7 +128,7 @@ class PeerTest : LightningTestSuite() { val (alice, bob, alice2bob, bob2alice) = newPeers(this, nodeParams, walletParams, automateMessaging = false) val wallet = createWallet(nodeParams.first.keyManager, 300_000.sat).second - alice.send(OpenChannel(250_000.sat, wallet, FeeratePerKw(3000.sat), FeeratePerKw(2500.sat), ChannelType.SupportedChannelType.AnchorOutputsZeroReserve)) + alice.send(OpenChannel(250_000.sat, wallet, FeeratePerKw(3000.sat), FeeratePerKw(2500.sat))) val open = alice2bob.expect() bob.forward(open) @@ -140,12 +139,12 @@ class PeerTest : LightningTestSuite() { val txAddInput = alice2bob.expect() assertNotEquals(txAddInput.channelId, open.temporaryChannelId) // we now have the final channel_id bob.forward(txAddInput) - val txCompleteBob = bob2alice.expect() - alice.forward(txCompleteBob) + val txCompleteBob1 = bob2alice.expect() + alice.forward(txCompleteBob1) val txAddOutput = alice2bob.expect() bob.forward(txAddOutput) - bob2alice.expect() - alice.forward(txCompleteBob) + val txCompleteBob2 = bob2alice.expect() + alice.forward(txCompleteBob2) val txCompleteAlice = alice2bob.expect() bob.forward(txCompleteAlice) val commitSigBob = bob2alice.expect() @@ -189,7 +188,7 @@ class PeerTest : LightningTestSuite() { val (alice, bob, alice2bob, bob2alice) = newPeers(this, nodeParams, walletParams, automateMessaging = false) val wallet = createWallet(nodeParams.first.keyManager, 300_000.sat).second - alice.send(OpenChannel(250_000.sat, wallet, FeeratePerKw(3000.sat), FeeratePerKw(2500.sat), ChannelType.SupportedChannelType.AnchorOutputsZeroReserve)) + alice.send(OpenChannel(250_000.sat, wallet, FeeratePerKw(3000.sat), FeeratePerKw(2500.sat))) val open = alice2bob.expect() bob.forward(open) @@ -200,12 +199,12 @@ class PeerTest : LightningTestSuite() { val txAddInput = alice2bob.expect() assertNotEquals(txAddInput.channelId, open.temporaryChannelId) // we now have the final channel_id bob.forward(txAddInput) - val txCompleteBob = bob2alice.expect() - alice.forward(txCompleteBob) + val txCompleteBob1 = bob2alice.expect() + alice.forward(txCompleteBob1) val txAddOutput = alice2bob.expect() bob.forward(txAddOutput) - bob2alice.expect() - alice.forward(txCompleteBob) + val txCompleteBob2 = bob2alice.expect() + alice.forward(txCompleteBob2) val txCompleteAlice = alice2bob.expect() bob.forward(txCompleteAlice) val commitSigBob = bob2alice.expect() @@ -367,8 +366,9 @@ class PeerTest : LightningTestSuite() { .first { it.size == 1 } .values .first() + assertIs(restoredChannel) assertEquals(bob1.state, restoredChannel) - assertEquals(peer.db.channels.listLocalChannels(), listOf(restoredChannel)) + assertEquals(peer.db.channels.listLocalChannels(), listOf(restoredChannel.copy(remoteNextCommitNonces = mapOf()))) } @Test @@ -398,8 +398,9 @@ class PeerTest : LightningTestSuite() { .first { it.size == 1 && it.values.first() is Normal } .values .first() + assertIs(restoredChannel) assertEquals(bob1.state, restoredChannel) - assertEquals(peer.db.channels.listLocalChannels(), listOf(restoredChannel)) + assertEquals(peer.db.channels.listLocalChannels(), listOf(restoredChannel.copy(remoteNextCommitNonces = mapOf()))) } @Test 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 5ef6633ce..2170bff67 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 @@ -510,8 +510,8 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { @Test fun `insufficient funds when retrying with higher fees`() = runSuspendTest { val (alice, _) = TestsHelper.reachNormal(aliceFundingAmount = 100_000.sat, bobFundingAmount = 0.sat) - assertTrue(83_500_000.msat < alice.commitments.availableBalanceForSend()) - assertTrue(alice.commitments.availableBalanceForSend() < 84_000_000.msat) + assertTrue(86_000_000.msat < alice.commitments.availableBalanceForSend()) + assertTrue(alice.commitments.availableBalanceForSend() < 86_500_000.msat) val walletParams = defaultWalletParams.copy( trampolineFees = listOf( TrampolineFees(100.sat, 0, CltvExpiryDelta(144)), @@ -520,12 +520,12 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { ) val outgoingPaymentHandler = OutgoingPaymentHandler(TestConstants.Alice.nodeParams, walletParams, InMemoryPaymentsDb()) val invoice = makeInvoice(amount = null, supportsTrampoline = true) - val payment = PayInvoice(UUID.randomUUID(), 83_000_000.msat, LightningOutgoingPayment.Details.Normal(invoice)) + val payment = PayInvoice(UUID.randomUUID(), 86_000_000.msat, LightningOutgoingPayment.Details.Normal(invoice)) val progress = outgoingPaymentHandler.sendPayment(payment, mapOf(alice.channelId to alice.state), TestConstants.defaultBlockHeight) assertIs(progress) val (_, add1) = findAddHtlcCommand(progress) - assertEquals(83_100_000.msat, add1.amount) + assertEquals(86_100_000.msat, add1.amount) val attempt = outgoingPaymentHandler.getPendingPayment(payment.paymentId)!! val fail = outgoingPaymentHandler.processAddSettledFailed(alice.channelId, createRemoteFailure(add1, attempt, TrampolineFeeInsufficient), mapOf(alice.channelId to alice.state), TestConstants.defaultBlockHeight) diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/serialization/channel/StateSerializationTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/serialization/channel/StateSerializationTestsCommon.kt index a66bc9c0e..1ec65224b 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/serialization/channel/StateSerializationTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/serialization/channel/StateSerializationTestsCommon.kt @@ -29,11 +29,11 @@ class StateSerializationTestsCommon : LightningTestSuite() { val (alice, bob) = TestsHelper.reachNormal() val bytes = Serialization.serialize(alice.state) val check = Serialization.deserialize(bytes).value - assertEquals(alice.state, check) + assertEquals(alice.state.copy(remoteNextCommitNonces = mapOf(), localCloseeNonce = null, localCloserNonces = null), check) val bytes1 = Serialization.serialize(bob.state) val check1 = Serialization.deserialize(bytes1).value - assertEquals(bob.state, check1) + assertEquals(bob.state.copy(remoteNextCommitNonces = mapOf(), localCloseeNonce = null, localCloserNonces = null), check1) } @Test 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 fbb1acbc8..57fc10fb9 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 @@ -76,6 +76,7 @@ object TestConstants { Feature.Wumbo to FeatureSupport.Optional, Feature.StaticRemoteKey to FeatureSupport.Mandatory, Feature.AnchorOutputs to FeatureSupport.Mandatory, + Feature.SimpleTaprootChannels to FeatureSupport.Optional, Feature.RouteBlinding to FeatureSupport.Optional, Feature.DualFunding to FeatureSupport.Mandatory, Feature.ShutdownAnySegwit to FeatureSupport.Mandatory, 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 90ae1ce06..e5ef7c88f 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 @@ -451,21 +451,21 @@ class LightningCodecsTestsCommon : LightningTestSuite() { fun `encode - decode commit_sig`() { val channelId = ByteVector32.fromValidHex("2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db25") val signature = ChannelSpendSignature.IndividualSignature(ByteVector64.fromValidHex("05e06d9a8fdfbb3625051ff2e3cdf82679cc2268beee6905941d6dd8a067cd62711e04b119a836aa0eebe07545172cefb228860fea6c797178453a319169bed7")) - val alternateSigs = listOf( - CommitSigTlv.AlternativeFeerateSig(FeeratePerKw(253.sat), ByteVector64.fromValidHex("c49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3")), - CommitSigTlv.AlternativeFeerateSig(FeeratePerKw(500.sat), ByteVector64.fromValidHex("2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5")), - CommitSigTlv.AlternativeFeerateSig(FeeratePerKw(750.sat), ByteVector64.fromValidHex("83a7a1a04141ac8ab2818f4a872ea86716ef9aac0852146bcdbc2cc49aecc985899a63513f41ed2502a321a4945689239d12bdab778c1a2e8bf7c3f19ec53b58")), + val partialSig = ChannelSpendSignature.PartialSignatureWithNonce( + ByteVector32("034ad8ca7bed68a934b633c4beeb7dc493cb0ff70e7aa9c86b895bbf3a3b5f82"), + IndividualNonce("a49ff67b08c720b993c946556cde1be1c3b664bc847c4792135dfd6ef0986e00e9871808c6620b0420567dad525b27431453d4434fd326f8ac56496639b72326eb5d") ) val testCases = listOf( // @formatter:off - CommitSig(channelId, signature, listOf(), TlvStream(CommitSigTlv.AlternativeFeerateSigs(alternateSigs))) to "00842dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db2505e06d9a8fdfbb3625051ff2e3cdf82679cc2268beee6905941d6dd8a067cd62711e04b119a836aa0eebe07545172cefb228860fea6c797178453a319169bed70000fe47010001cd03000000fdc49269a9baa73a5ec44b63bdcaabf9c7c6477f72866b822f8502e5c989aa3562fe69d72bec62025d3474b9c2d947ec6d68f9f577be5fab8ee80503cefd8846c3000001f42dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db252a2f914ea1fcbd580b80cdea60226f63288cd44bd84a8850c9189a24f08c7cc5000002ee83a7a1a04141ac8ab2818f4a872ea86716ef9aac0852146bcdbc2cc49aecc985899a63513f41ed2502a321a4945689239d12bdab778c1a2e8bf7c3f19ec53b58", + CommitSig(channelId, signature, listOf()) to "0084 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db25 05e06d9a8fdfbb3625051ff2e3cdf82679cc2268beee6905941d6dd8a067cd62711e04b119a836aa0eebe07545172cefb228860fea6c797178453a319169bed7 0000", + CommitSig(channelId, partialSig, listOf(), batchSize = 1) to "0084 2dadacd65b585e4061421b5265ff543e2a7bdc4d4a7fea932727426bdc53db25 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0000 0262034ad8ca7bed68a934b633c4beeb7dc493cb0ff70e7aa9c86b895bbf3a3b5f82a49ff67b08c720b993c946556cde1be1c3b664bc847c4792135dfd6ef0986e00e9871808c6620b0420567dad525b27431453d4434fd326f8ac56496639b72326eb5d", // @formatter:on ) testCases.forEach { (commitSig, bin) -> val decoded = LightningMessage.decode(Hex.decode(bin)) assertEquals(decoded, commitSig) val encoded = LightningMessage.encode(commitSig) - assertEquals(Hex.encode(encoded), bin) + assertContentEquals(encoded, Hex.decode(bin)) } } @@ -559,12 +559,12 @@ class LightningCodecsTestsCommon : LightningTestSuite() { 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)) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000186a0 000009c4 00000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b1e00000000000186a0000186a0000186a00190009600000000000000000000"), + 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)) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b5a000186a0000186a00190009600000000000000000004deadbeef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + 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"), 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 16a7c23ce..8892be1e1 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 @@ -139,7 +139,8 @@ }, "txId": "24f0d946a87c12c7fa345d04f2f2e9623074bd093d44229e8d00e1422257e015", "remoteSig": { - "sig": "58555dff0574ce320e281e1ff9d945674f710e5a33aeffcd2c0a105bbe1c2bad7389c84098615ab0f12dd7e9f951ede0000f205fda0f03fe958a42bfb59c9726" + "sig": "58555dff0574ce320e281e1ff9d945674f710e5a33aeffcd2c0a105bbe1c2bad7389c84098615ab0f12dd7e9f951ede0000f205fda0f03fe958a42bfb59c9726", + "nonce": null }, "htlcRemoteSigs": [ "29503e87f9b949d66cdbaef5a011d52d3f01fda29912c342f2e6ed69d74755bd029544a42cae34e45aaf92628b9eca98ae2441ec4433f8bf8714224f82cc852c" 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 a151bbda5..37f0781aa 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 @@ -122,7 +122,8 @@ }, "txId": "0f9ad08d4eddee3c7d91464eb1dcca37548f48d95863ff67a0ef11b5ef8cd312", "remoteSig": { - "sig": "dc0d6153f56f4fb1238325ce8b38de8d2196f015e337502198ffaa43e15c3ff65ac254766dcab049e16bbd40bac3438fc3e0274c853bfc1eb88e7260b0f7fe11" + "sig": "dc0d6153f56f4fb1238325ce8b38de8d2196f015e337502198ffaa43e15c3ff65ac254766dcab049e16bbd40bac3438fc3e0274c853bfc1eb88e7260b0f7fe11", + "nonce": null }, "htlcRemoteSigs": [] }, 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 192b5e573..ab2371186 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 @@ -147,7 +147,8 @@ }, "txId": "fed55c7986d149ca8432c171cd163ab78658e4e0a7b70234218ee74644015f2a", "remoteSig": { - "sig": "125de9d83e8005bd73a01e24aadf5f387d15c4cc40990a3d5ccc42dc446461d11ce47aa07dc35658cca00f1c092c8f8273e6b11eca8f40e360b4f9ee0955f74a" + "sig": "125de9d83e8005bd73a01e24aadf5f387d15c4cc40990a3d5ccc42dc446461d11ce47aa07dc35658cca00f1c092c8f8273e6b11eca8f40e360b4f9ee0955f74a", + "nonce": null }, "htlcRemoteSigs": [ "e8f3c941cc5ec88b6f03ea64085742d99d8c1403f0fc78fc67d56271c07ef6e42776b1100b32062a087cf86acaeb7cd280676e792986dd6fb5b640f64d22694e", 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 7e5416191..d78b7bb4e 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 @@ -122,7 +122,8 @@ }, "txId": "1f369b753124adac5fedce5d6eae5539c08261b29fbc291f4e365c295f68dbb5", "remoteSig": { - "sig": "716bbeadfc5a323082f8c2e4c9b401f2cf07bd71d7d47fa31effa54ef1c6ff5f463be28b9beecba893f0c1df47610a0c201605391d312e8607a647db13938cf6" + "sig": "716bbeadfc5a323082f8c2e4c9b401f2cf07bd71d7d47fa31effa54ef1c6ff5f463be28b9beecba893f0c1df47610a0c201605391d312e8607a647db13938cf6", + "nonce": null }, "htlcRemoteSigs": [] }, 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 99ebc51ae..7d7634ff3 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 @@ -125,7 +125,8 @@ }, "txId": "a3cac82072d07b57f3b2b4246fa4d3e2f903e627b7dd1ee6b4b2a571cc4e34d8", "remoteSig": { - "sig": "1d69ef3d27a4ee4ea170b827e40255df0d335573bd4a856e702f6beefefd0b273dd8c5438bf26f5888efbd8917f4b9d94bc1fd769cd7d5dc844f9bad4c0174f7" + "sig": "1d69ef3d27a4ee4ea170b827e40255df0d335573bd4a856e702f6beefefd0b273dd8c5438bf26f5888efbd8917f4b9d94bc1fd769cd7d5dc844f9bad4c0174f7", + "nonce": null }, "htlcRemoteSigs": [] }, @@ -159,6 +160,7 @@ }, "remotePerCommitmentSecrets": "" }, + "remoteNextCommitNonces": {}, "localScript": "001405e0104aa726e34ff5cd3a6320d05c0862b5b01c", "remoteScript": "001434947cfb2e8f6054ddf12daed4308cbe342580d1", "proposedClosingTxs": [ @@ -216,5 +218,8 @@ "closeCommand": { "scriptPubKey": null, "feerate": 5000 - } + }, + "localCloseeNonce": null, + "remoteCloseeNonce": null, + "localCloserNonces": null } \ No newline at end of file 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 5635afc14..479fc3964 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 @@ -223,7 +223,8 @@ }, "txId": "bb7efa64ea149ca3f74c33e00ca72dbe096971d0ebb3a241c468771c187711ff", "remoteSig": { - "sig": "30b7c66f20b7ac100387047a159c1045364fc1fce5f1996f1230618a768819762f4d20baf053ab8058fcb9b8e81e931a7d4f9c31f5f1cd0b16791af1e54046c0" + "sig": "30b7c66f20b7ac100387047a159c1045364fc1fce5f1996f1230618a768819762f4d20baf053ab8058fcb9b8e81e931a7d4f9c31f5f1cd0b16791af1e54046c0", + "nonce": null }, "htlcRemoteSigs": [ "700471e0f8758cb5fde4c66a365feb2864ee80cb7946abfc2c1a15c94c9f6e9d00796b8f818a4e263112b4a04c6a904e8db076960549c22c8e9eb728367c24fb", @@ -490,7 +491,8 @@ }, "txId": "dcfb0ef853173058809a407afd88a3d99164bdbe6961194256fd944146872266", "remoteSig": { - "sig": "182390e02cd28be5673c229bc99c39033e9dbd1942616d30b69efa9cd4fc25694df27bd1aa9c2fe5e2901e6ecab2479417ddb277d201880968c0f46212e16c74" + "sig": "182390e02cd28be5673c229bc99c39033e9dbd1942616d30b69efa9cd4fc25694df27bd1aa9c2fe5e2901e6ecab2479417ddb277d201880968c0f46212e16c74", + "nonce": null }, "htlcRemoteSigs": [ "3229c56a2efa5d18d72805abfd61bbc0f6dfcd3cbbbaa1bf9e64061468518af86959a247ae8e3e60f964cc5e9d824bc7c35f24c8e0e40aabd36e0642806615e1", @@ -642,6 +644,7 @@ }, "remotePerCommitmentSecrets": "" }, + "remoteNextCommitNonces": {}, "shortChannelId": "42x0x0", "channelUpdate": { "signature": "6e9bd75886e3aa18389c5f5419bb2acf003c949be1b72c47c725e6705cf3b68e6387bab8a47da2bdf9e2f530debc375a0dc9f3d4700a7cde1ddc1cd3eb393fcd", @@ -662,5 +665,7 @@ }, "localShutdown": null, "remoteShutdown": null, - "closeCommand": null + "closeCommand": null, + "localCloseeNonce": null, + "localCloserNonces": null } \ No newline at end of file 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 f85cc2c72..ae473e352 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 @@ -141,7 +141,8 @@ }, "txId": "fd49c1c1b8a84d2b00a0a1210c822cbbac8fc8fbf1afbbd69908c9b7e5df7cc4", "remoteSig": { - "sig": "4c028ec20ee39c107bc73033becdbb1cf6588e309e6e25f5a036c8b6504e04ea4a7d08b9739426c1bbbb4d15ff7984651506bbfb0a8af8b5acf6013d8eedcd67" + "sig": "4c028ec20ee39c107bc73033becdbb1cf6588e309e6e25f5a036c8b6504e04ea4a7d08b9739426c1bbbb4d15ff7984651506bbfb0a8af8b5acf6013d8eedcd67", + "nonce": null }, "htlcRemoteSigs": [ "6cdad11404a129da5b7b41869b8d3e319e27ceffb590214c730c8b2afa8f18ed0495867aa19597357ae3bd0c8c9e5addcc850008a8f66395b8bee5c78af6f031" @@ -230,7 +231,8 @@ }, "txId": "c9dfa8a557f54092d5f72d6ec6236e6ee71246a14f0bd79c6c4804443c483f4c", "remoteSig": { - "sig": "9dc287c5a78ad3480738aab05a14e8b36bdf1d3e97fa11af7e75edefcd6e5cbf404e33cc770464604896c65acbc31d7c403b90cf94c2c8a0e4ccd56af7ef68a5" + "sig": "9dc287c5a78ad3480738aab05a14e8b36bdf1d3e97fa11af7e75edefcd6e5cbf404e33cc770464604896c65acbc31d7c403b90cf94c2c8a0e4ccd56af7ef68a5", + "nonce": null }, "htlcRemoteSigs": [ "df602798b869e7f8176bea759bf5da249d4c00dfb47ebe14177e5c8634c9ca987c7a6895e46ed12aa0f3e6016dbccdff92c3d16c001b1a00958ab0987d1ae4ff" @@ -288,6 +290,7 @@ }, "remotePerCommitmentSecrets": "" }, + "remoteNextCommitNonces": {}, "shortChannelId": "0x8022898x0", "channelUpdate": { "signature": "df9689bc36fc0633fc8df8a8bedd183eb85b80f129ac56cb42c9f080ae61156339fe89171b01534633328ae9bba8d3ccd44629b7922b821ee1f96cef29672fea", @@ -308,5 +311,7 @@ }, "localShutdown": null, "remoteShutdown": null, - "closeCommand": null + "closeCommand": null, + "localCloseeNonce": null, + "localCloserNonces": null } \ No newline at end of file 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 e99c03a0c..3319166ad 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 @@ -223,7 +223,8 @@ }, "txId": "0c74f8e6e0749a7815952ceedf0ee33f8f6e3298cf86eb83e45bf0f875a73044", "remoteSig": { - "sig": "bd71081f466344fb018eceabab814bdaf9d34df35327a43560677a13b96bbc3f1669df9f5075854a415a6ebd32bcf1e6fe482510930da7abfc15f319f29e7f00" + "sig": "bd71081f466344fb018eceabab814bdaf9d34df35327a43560677a13b96bbc3f1669df9f5075854a415a6ebd32bcf1e6fe482510930da7abfc15f319f29e7f00", + "nonce": null }, "htlcRemoteSigs": [ "47f187dab4904e505d7eecb6fa0ab9d1dd3ae4194a215d73c0ed3310bba02e3455dc93c903bee48ed0613431dd771030e0ba43007300570463934cdda090b7c4", @@ -375,6 +376,7 @@ }, "remotePerCommitmentSecrets": "" }, + "remoteNextCommitNonces": {}, "shortChannelId": "42x0x0", "channelUpdate": { "signature": "6f5237d843822495a739a69f8dc75755940490826264170a707523c5dbee8f423d14ea56f7b2d2a07dfbc37b18d27fe301550d047fc109a3638e183c05b1edc7", @@ -647,5 +649,7 @@ }, "localShutdown": null, "remoteShutdown": null, - "closeCommand": null + "closeCommand": null, + "localCloseeNonce": null, + "localCloserNonces": null } \ No newline at end of file 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 5545e005b..a82e98d61 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 @@ -143,7 +143,8 @@ }, "txId": "46e675be5b612cef94dbf2fe5d82dbfaa9f2f2f1542c941a711324f3f479ec66", "remoteSig": { - "sig": "4fbc9db98fa48f937fb60e364bbb8e32e8f7c6419745cf0f16b04b75d46e6cbe587cffe2a58b5ad40659ec171991b6a80fb63fbcf41b3757f511615dc3ba4703" + "sig": "4fbc9db98fa48f937fb60e364bbb8e32e8f7c6419745cf0f16b04b75d46e6cbe587cffe2a58b5ad40659ec171991b6a80fb63fbcf41b3757f511615dc3ba4703", + "nonce": null }, "htlcRemoteSigs": [ "b93c1ee4c31a629d64e8ec83f229db9cd9ea67dccf2fffc1f08c5443cdc548bf0ac41485cda6c3c40b019db559094ee0e7e2a7deb47f73d6d50ec0a7cd60685d", @@ -200,6 +201,7 @@ }, "remotePerCommitmentSecrets": "" }, + "remoteNextCommitNonces": {}, "localShutdown": { "channelId": "85ad5df602e4b1517db06754a5c1f3aa68d59973bd19e29798a825f3fb22babf", "scriptPubKey": "0014571c5ecb495ec4aeb6bd6f532af6817d70b8bc98" @@ -211,5 +213,6 @@ "closeCommand": { "scriptPubKey": "0014571c5ecb495ec4aeb6bd6f532af6817d70b8bc98", "feerate": 10000 - } + }, + "localCloseeNonce": null } \ No newline at end of file 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 b3ff939b5..b2c81be9f 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 @@ -126,7 +126,8 @@ }, "txId": "e2ad5c195fd0ecb839650322bf4d14543a2f60d1c3f04cca670c3187a7ae2894", "remoteSig": { - "sig": "434a16a3d3092ce8dae91aca3b863501499c6b6a862d7e18fd580623294f65710334414fbbd1f54c87c6a52294feb09d9a76cdb10a74e890ec0b6cfb70f0e067" + "sig": "434a16a3d3092ce8dae91aca3b863501499c6b6a862d7e18fd580623294f65710334414fbbd1f54c87c6a52294feb09d9a76cdb10a74e890ec0b6cfb70f0e067", + "nonce": null }, "htlcRemoteSigs": [] }, @@ -160,6 +161,7 @@ }, "remotePerCommitmentSecrets": "" }, + "remoteNextCommitNonces": {}, "shortChannelId": "42x0x0", "lastSent": { "channelId": "1c9c6492fc038dd610071d689aff8a57f88e1163ad006643b9491bd0e6fcf8b1", 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 662f442fe..03a1bc23c 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 @@ -125,7 +125,8 @@ }, "txId": "75adc69132706111bfaa36188f158d5c967a831be2d154cda1e901666bf05f63", "remoteSig": { - "sig": "b717883c313d0e73ed638ac1e8bdd4b205138e260a321fe92e7c4ac80073b0955ecbb02b165d9c8201f4da4b92a298fd0dc8a35f878651319f8eacaef468640a" + "sig": "b717883c313d0e73ed638ac1e8bdd4b205138e260a321fe92e7c4ac80073b0955ecbb02b165d9c8201f4da4b92a298fd0dc8a35f878651319f8eacaef468640a", + "nonce": null }, "htlcRemoteSigs": [] }, @@ -159,6 +160,7 @@ }, "remotePerCommitmentSecrets": "" }, + "remoteNextCommitNonces": {}, "waitingSinceBlock": 400000, "deferred": { "channelId": "2a6bf35f6378987185051faa8cc4ca745d380181bad61ba770056e4911aab062", 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 ee1332f74..be7519e9d 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 @@ -121,7 +121,8 @@ }, "txId": "adf80ca3cdb8625fc144f8087766475abbf8331955825e597b2bca77169056c7", "remoteSig": { - "sig": "1d87fbb35b15229da1da1a4d10018e7b9b510b57ea41ee3e32d1b0fa72562bea4521bd19dac13089cacde07debb5950560b75a2a74ac9e45017babe136bdd719" + "sig": "1d87fbb35b15229da1da1a4d10018e7b9b510b57ea41ee3e32d1b0fa72562bea4521bd19dac13089cacde07debb5950560b75a2a74ac9e45017babe136bdd719", + "nonce": null }, "htlcRemoteSigs": [] }, diff --git a/modules/core/src/jvmTest/kotlin/fr/acinq/lightning/db/sqlite/SqliteChannelsDbTestsJvm.kt b/modules/core/src/jvmTest/kotlin/fr/acinq/lightning/db/sqlite/SqliteChannelsDbTestsJvm.kt index 6b36a7824..42c62f793 100644 --- a/modules/core/src/jvmTest/kotlin/fr/acinq/lightning/db/sqlite/SqliteChannelsDbTestsJvm.kt +++ b/modules/core/src/jvmTest/kotlin/fr/acinq/lightning/db/sqlite/SqliteChannelsDbTestsJvm.kt @@ -18,9 +18,11 @@ class SqliteChannelsDbTestsJvm : LightningTestSuite() { val db = SqliteChannelsDb(sqliteInMemory()) val (alice, _) = TestsHelper.reachNormal(currentHeight = 1, aliceFundingAmount = 1_000_000.sat) db.addOrUpdateChannel(alice.state) + val aliceWithoutNonces = alice.state.copy(remoteNextCommitNonces = mapOf(), localCloseeNonce = null, localCloserNonces = null) val (bob, _) = TestsHelper.reachNormal(currentHeight = 2, aliceFundingAmount = 2_000_000.sat) db.addOrUpdateChannel(bob.state) - assertEquals(db.listLocalChannels(), listOf(alice.state, bob.state)) + val bobWithoutNonces = bob.state.copy(remoteNextCommitNonces = mapOf(), localCloseeNonce = null, localCloserNonces = null) + assertEquals(db.listLocalChannels(), listOf(aliceWithoutNonces, bobWithoutNonces)) } } } \ No newline at end of file