diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt index aac5c7240..b6d47c199 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt @@ -23,16 +23,14 @@ import kotlinx.coroutines.CompletableDeferred sealed class SharedFundingInput { abstract val info: Transactions.InputInfo abstract val weight: Int - abstract val localHtlcs: Set abstract fun sign(channelKeys: KeyManager.ChannelKeys, tx: Transaction): ByteVector64 - data class Multisig2of2(override val info: Transactions.InputInfo, val fundingTxIndex: Long, val remoteFundingPubkey: PublicKey, override val localHtlcs: Set) : SharedFundingInput() { + data class Multisig2of2(override val info: Transactions.InputInfo, val fundingTxIndex: Long, val remoteFundingPubkey: PublicKey) : SharedFundingInput() { constructor(commitment: Commitment) : this( info = commitment.commitInput, fundingTxIndex = commitment.fundingTxIndex, - remoteFundingPubkey = commitment.remoteFundingPubkey, - localHtlcs = commitment.localCommit.spec.htlcs + remoteFundingPubkey = commitment.remoteFundingPubkey ) // This value was computed assuming 73 bytes signatures (worst-case scenario). @@ -136,7 +134,7 @@ sealed class InteractiveTxInput { data class RemoteSwapIn(override val serialId: Long, override val outPoint: OutPoint, override val txOut: TxOut, override val sequence: UInt, val userKey: PublicKey, val serverKey: PublicKey, val refundDelay: Int) : Remote() /** The shared input can be added by us or by our peer, depending on who initiated the protocol. */ - data class Shared(override val serialId: Long, override val outPoint: OutPoint, override val sequence: UInt, val localAmount: MilliSatoshi, val remoteAmount: MilliSatoshi, val localHtlcs: Set) : InteractiveTxInput(), Incoming, Outgoing + data class Shared(override val serialId: Long, override val outPoint: OutPoint, override val sequence: UInt, val localAmount: MilliSatoshi, val remoteAmount: MilliSatoshi) : InteractiveTxInput(), Incoming, Outgoing } sealed class InteractiveTxOutput { @@ -251,7 +249,7 @@ data class FundingContributions(val inputs: List, v } } } - val sharedInput = sharedUtxo?.let { (i, balances) -> listOf(InteractiveTxInput.Shared(0, i.info.outPoint, 0xfffffffdU, balances.toLocal, balances.toRemote, i.localHtlcs)) } ?: listOf() + val sharedInput = sharedUtxo?.let { (i, balances) -> listOf(InteractiveTxInput.Shared(0, i.info.outPoint, 0xfffffffdU, balances.toLocal, balances.toRemote)) } ?: listOf() val localInputs = walletInputs.map { i -> InteractiveTxInput.LocalSwapIn(0, i.previousTx.stripInputWitnesses(), i.outputIndex.toLong(), 0xfffffffdU, swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.refundDelay) } return if (params.isInitiator) { Either.Right(sortFundingContributions(params, sharedInput + localInputs, sharedOutput + nonChangeOutputs + changeOutput)) @@ -281,7 +279,7 @@ data class FundingContributions(val inputs: List, v fun computeWeightPaid(isInitiator: Boolean, commitment: Commitment, walletInputs: List, localOutputs: List): Int = computeWeightPaid( isInitiator, - SharedFundingInput.Multisig2of2(commitment.commitInput, commitment.fundingTxIndex, Transactions.PlaceHolderPubKey, commitment.localCommit.spec.htlcs), + SharedFundingInput.Multisig2of2(commitment.commitInput, commitment.fundingTxIndex, Transactions.PlaceHolderPubKey), commitment.commitInput.txOut.publicKeyScript, walletInputs, localOutputs @@ -468,6 +466,7 @@ data class InteractiveTxSession( val previousFunding: SharedFundingInputBalances, val toSend: List>, val previousTxs: List = listOf(), + val previousHtlcs: Set, val localInputs: List = listOf(), val remoteInputs: List = listOf(), val localOutputs: List = listOf(), @@ -507,7 +506,8 @@ data class InteractiveTxSession( fundingParams, SharedFundingInputBalances(previousLocalBalance, previousRemoteBalance, previousHtlcs), fundingContributions.inputs.map { i -> Either.Left(i) } + fundingContributions.outputs.map { o -> Either.Right(o) }, - previousTxs + previousTxs, + previousHtlcs ) val isComplete: Boolean = txCompleteSent && txCompleteReceived @@ -557,7 +557,7 @@ data class InteractiveTxSession( val expectedSharedOutpoint = fundingParams.sharedInput?.info?.outPoint ?: return Either.Left(InteractiveTxSessionAction.PreviousTxMissing(message.channelId, message.serialId)) val receivedSharedOutpoint = message.sharedInput ?: return Either.Left(InteractiveTxSessionAction.PreviousTxMissing(message.channelId, message.serialId)) if (expectedSharedOutpoint != receivedSharedOutpoint) return Either.Left(InteractiveTxSessionAction.PreviousTxMissing(message.channelId, message.serialId)) - InteractiveTxInput.Shared(message.serialId, receivedSharedOutpoint, message.sequence, previousFunding.toLocal, previousFunding.toRemote, previousFunding.htlcs) + InteractiveTxInput.Shared(message.serialId, receivedSharedOutpoint, message.sequence, previousFunding.toLocal, previousFunding.toRemote) } else -> { if (message.previousTx.txOut.size <= message.previousTxOutput) { @@ -837,7 +837,8 @@ data class InteractiveTxSigningSession( localCommitmentIndex: Long, remoteCommitmentIndex: Long, commitTxFeerate: FeeratePerKw, - remotePerCommitmentPoint: PublicKey + remotePerCommitmentPoint: PublicKey, + previousHtlcs: Set ): Either> { val channelKeys = channelParams.localParams.channelKeys(keyManager) val unsignedTx = sharedTx.buildUnsignedTx() @@ -849,7 +850,7 @@ data class InteractiveTxSigningSession( fundingAmount = sharedTx.sharedOutput.amount, toLocal = sharedTx.sharedOutput.localAmount - localPushAmount + remotePushAmount, toRemote = sharedTx.sharedOutput.remoteAmount - remotePushAmount + localPushAmount, - localHtlcs = sharedTx.sharedInput?.localHtlcs ?: setOf(), + localHtlcs = previousHtlcs, localCommitmentIndex = localCommitmentIndex, remoteCommitmentIndex = remoteCommitmentIndex, commitTxFeerate, diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt index 683584672..717fa6525 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt @@ -577,7 +577,8 @@ data class Normal( localCommitmentIndex = parentCommitment.localCommit.index, remoteCommitmentIndex = parentCommitment.remoteCommit.index, parentCommitment.localCommit.spec.feerate, - parentCommitment.remoteCommit.remotePerCommitmentPoint + parentCommitment.remoteCommit.remotePerCommitmentPoint, + previousHtlcs = parentCommitment.localCommit.spec.htlcs ) when (signingSession) { is Either.Left -> { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmed.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmed.kt index e015c46d7..d26033727 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmed.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmed.kt @@ -107,7 +107,7 @@ data class WaitForFundingConfirmed( addAll(latestFundingTx.sharedTx.tx.localInputs.map { Either.Left(it) }) addAll(latestFundingTx.sharedTx.tx.localOutputs.map { Either.Right(it) }) } - val session = InteractiveTxSession(channelKeys(), keyManager.swapInOnChainWallet, fundingParams, SharedFundingInputBalances(0.msat, 0.msat, emptySet()), toSend, previousFundingTxs.map { it.sharedTx }) + val session = InteractiveTxSession(channelKeys(), keyManager.swapInOnChainWallet, fundingParams, SharedFundingInputBalances(0.msat, 0.msat, emptySet()), toSend, previousFundingTxs.map { it.sharedTx }, commitments.latest.localCommit.spec.htlcs) val nextState = this@WaitForFundingConfirmed.copy(rbfStatus = RbfStatus.InProgress(session)) Pair(nextState, listOf(ChannelAction.Message.Send(TxAckRbf(channelId, fundingParams.localContribution)))) } @@ -179,7 +179,8 @@ data class WaitForFundingConfirmed( localCommitmentIndex = replacedCommitment.localCommit.index, remoteCommitmentIndex = replacedCommitment.remoteCommit.index, replacedCommitment.localCommit.spec.feerate, - replacedCommitment.remoteCommit.remotePerCommitmentPoint + replacedCommitment.remoteCommit.remotePerCommitmentPoint, + replacedCommitment.localCommit.spec.htlcs ) when (signingSession) { is Either.Left -> { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreated.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreated.kt index 209848c21..ccab03240 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreated.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingCreated.kt @@ -65,7 +65,8 @@ data class WaitForFundingCreated( localCommitmentIndex = 0, remoteCommitmentIndex = 0, commitTxFeerate, - remoteFirstPerCommitmentPoint + remoteFirstPerCommitmentPoint, + emptySet() ) when (signingSession) { is Either.Left -> { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Deserialization.kt b/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Deserialization.kt index 51ddd4365..1c71f288e 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Deserialization.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Deserialization.kt @@ -181,8 +181,7 @@ object Deserialization { 0x01 -> SharedFundingInput.Multisig2of2( info = readInputInfo(), fundingTxIndex = readNumber(), - remoteFundingPubkey = readPublicKey(), - localHtlcs = readCollection { readDirectedHtlc() }.toSet() + remoteFundingPubkey = readPublicKey() ) else -> error("unknown discriminator $discriminator for class ${SharedFundingInput::class}") } @@ -206,8 +205,7 @@ object Deserialization { outPoint = readOutPoint(), sequence = readNumber().toUInt(), localAmount = readNumber().msat, - remoteAmount = readNumber().msat, - localHtlcs = readCollection { readDirectedHtlc() }.toSet() + remoteAmount = readNumber().msat ) else -> error("unknown discriminator $discriminator for class ${InteractiveTxInput.Shared::class}") } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Serialization.kt b/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Serialization.kt index f817f529b..c16791d33 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Serialization.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Serialization.kt @@ -234,7 +234,6 @@ object Serialization { writeInputInfo(i.info) writeNumber(i.fundingTxIndex) writePublicKey(i.remoteFundingPubkey) - writeCollection(i.localHtlcs) { writeDirectedHtlc(it) } } } @@ -258,7 +257,6 @@ object Serialization { writeNumber(sequence.toLong()) writeNumber(localAmount.toLong()) writeNumber(remoteAmount.toLong()) - writeCollection(localHtlcs) { writeDirectedHtlc(it) } } private fun Output.writeLocalInteractiveTxInput(i: InteractiveTxInput.Local) = when (i) { diff --git a/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt index 7dec76ff3..8916285d0 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt @@ -1137,7 +1137,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { val previousFundingAmount = (balanceA + balanceB).truncateToSatoshi() val previousFundingTx = Transaction(2, listOf(TxIn(OutPoint(randomBytes32(), 0), 0)), listOf(TxOut(previousFundingAmount, fundingScript)), 0) val inputInfo = Transactions.InputInfo(OutPoint(previousFundingTx, 0), previousFundingTx.txOut[0], redeemScript) - val sharedInputA = SharedFundingInput.Multisig2of2(inputInfo, fundingTxIndex, channelKeysB.fundingPubKey(fundingTxIndex), emptySet()) + val sharedInputA = SharedFundingInput.Multisig2of2(inputInfo, fundingTxIndex, channelKeysB.fundingPubKey(fundingTxIndex)) val nextFundingPubkeyB = channelKeysB.fundingPubKey(fundingTxIndex + 1) val fundingParamsA = InteractiveTxParams(channelId, true, fundingContributionA, fundingContributionB, sharedInputA, nextFundingPubkeyB, outputsA, lockTime, dustLimit, targetFeerate) return FundingContributions.create(channelKeysA, swapInKeysA, fundingParamsA, Pair(sharedInputA, SharedFundingInputBalances(balanceA, balanceB, emptySet())), listOf(), outputsA, randomKey().publicKey()) @@ -1171,8 +1171,8 @@ class InteractiveTxTestsCommon : LightningTestSuite() { val previousFundingAmount = (balanceA + balanceB).truncateToSatoshi() val previousFundingTx = Transaction(2, listOf(TxIn(OutPoint(randomBytes32(), 0), 0)), listOf(TxOut(previousFundingAmount, fundingScript)), 0) val inputInfo = Transactions.InputInfo(OutPoint(previousFundingTx, 0), previousFundingTx.txOut[0], redeemScript) - val sharedInputA = SharedFundingInput.Multisig2of2(inputInfo, fundingTxIndex, channelKeysB.fundingPubKey(fundingTxIndex), emptySet()) - val sharedInputB = SharedFundingInput.Multisig2of2(inputInfo, fundingTxIndex, channelKeysA.fundingPubKey(fundingTxIndex), emptySet()) + val sharedInputA = SharedFundingInput.Multisig2of2(inputInfo, fundingTxIndex, channelKeysB.fundingPubKey(fundingTxIndex)) + val sharedInputB = SharedFundingInput.Multisig2of2(inputInfo, fundingTxIndex, channelKeysA.fundingPubKey(fundingTxIndex)) val nextFundingPubkeyA = channelKeysA.fundingPubKey(fundingTxIndex + 1) val nextFundingPubkeyB = channelKeysB.fundingPubKey(fundingTxIndex + 1) val fundingParamsA = InteractiveTxParams(channelId, true, fundingContributionA, fundingContributionB, sharedInputA, nextFundingPubkeyB, outputsA, lockTime, dustLimit, targetFeerate) diff --git a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt index 5db8685a3..db83e2f66 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt @@ -1295,7 +1295,7 @@ class SpliceTestsCommon : LightningTestSuite() { return exchangeSpliceSigs(alice4, commitSigAlice, bob4, commitSigBob) } - private fun initiateSpliceWithoutSigs(alice: LNChannel, bob: LNChannel, inAmounts: List = emptyList(), pushAmount: MilliSatoshi = 0.msat, outAmount: Satoshi? = null): UnsignedSpliceFixture { + fun initiateSpliceWithoutSigs(alice: LNChannel, bob: LNChannel, inAmounts: List = emptyList(), pushAmount: MilliSatoshi = 0.msat, outAmount: Satoshi? = null): UnsignedSpliceFixture { val spliceIn_opt = if (inAmounts.isNotEmpty()) { ChannelCommand.Commitment.Splice.Request.SpliceIn(createWalletWithFunds(alice.staticParams.nodeParams.keyManager, inAmounts), pushAmount) } else { diff --git a/src/commonTest/kotlin/fr/acinq/lightning/serialization/StateSerializationTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/serialization/StateSerializationTestsCommon.kt index 0f7117262..255e70497 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/serialization/StateSerializationTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/serialization/StateSerializationTestsCommon.kt @@ -4,13 +4,14 @@ import fr.acinq.lightning.Feature import fr.acinq.lightning.Lightning.randomKey import fr.acinq.lightning.MilliSatoshi import fr.acinq.lightning.channel.* -import fr.acinq.lightning.channel.ChannelCommand +import fr.acinq.lightning.channel.TestsHelper.crossSign import fr.acinq.lightning.channel.states.Normal import fr.acinq.lightning.channel.states.PersistedChannelState +import fr.acinq.lightning.channel.states.SpliceTestsCommon.Companion.initiateSpliceWithoutSigs import fr.acinq.lightning.serialization.Encryption.from import fr.acinq.lightning.tests.utils.LightningTestSuite +import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.value -import fr.acinq.lightning.wire.CommitSig import fr.acinq.lightning.wire.EncryptedChannelData import fr.acinq.lightning.wire.LightningMessage import fr.acinq.secp256k1.Hex @@ -80,21 +81,20 @@ class StateSerializationTestsCommon : LightningTestSuite() { fun commitSigSize(maxIncoming: Int, maxOutgoing: Int): Int { val (alice1, bob1) = addHtlcs(alice, bob, MilliSatoshi(6000_000), maxOutgoing) val (bob2, alice2) = addHtlcs(bob1, alice1, MilliSatoshi(6000_000), maxIncoming) - val (_, actions) = alice2.process(ChannelCommand.Commitment.Sign) - val commitSig0 = actions.findOutgoingMessage() + val (alice3, bob3) = crossSign(alice2, bob2) - val (bob3, actions1) = bob2.process(ChannelCommand.MessageReceived(commitSig0)) - val commandSign0 = actions1.findCommand() - - val (_, actions2) = bob3.process(commandSign0) - val commitSig1 = actions2.findOutgoingMessage() + assertIs>(alice3) + assertIs>(bob3) + val (_, commitSig0, _, commitSig1) = initiateSpliceWithoutSigs(alice3, bob3, listOf(50_000.sat)) + assertFalse(commitSig1.channelData.isEmpty()) val bina = LightningMessage.encode(commitSig0) val binb = LightningMessage.encode(commitSig1) return max(bina.size, binb.size) } - // with 6 incoming payments and 6 outgoing payments, we can still add our encrypted backup to commig_sig messages - assertTrue(commitSigSize(6, 6) < 65000) + // with 5 incoming payments and 5 outgoing payments, we can still add our encrypted backup to commig_sig messages + // and stay below the 65k limit for a future channel_reestablish message of unknown size + assertTrue(commitSigSize(5, 5) < 60_000) } }