diff --git a/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWallet.kt b/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWallet.kt index ff8574493..4adb771c6 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWallet.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/blockchain/electrum/ElectrumMiniWallet.kt @@ -32,7 +32,6 @@ data class WalletState(val addresses: Map>) { data class Utxo(val txId: TxId, val outputIndex: Int, val blockHeight: Long, val previousTx: Transaction) { val outPoint = OutPoint(previousTx, outputIndex.toLong()) val amount = previousTx.txOut[outputIndex].amount - val txOut = previousTx.txOut[outputIndex] } /** diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt index 7d801eab9..b380513f2 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt @@ -106,15 +106,15 @@ sealed class InteractiveTxInput { sealed interface Incoming sealed class Local : InteractiveTxInput(), Outgoing { - abstract val previousTx: Transaction? + abstract val previousTx: Transaction abstract val previousTxOutput: Long + override val txOut: TxOut + get() = previousTx.txOut[previousTxOutput.toInt()] } /** A local-only input that funds the interactive transaction. */ data class LocalOnly(override val serialId: Long, override val previousTx: Transaction, override val previousTxOutput: Long, override val sequence: UInt) : Local() { override val outPoint: OutPoint = OutPoint(previousTx, previousTxOutput) - override val txOut: TxOut - get() = previousTx.txOut[previousTxOutput.toInt()] } /** A local input that funds the interactive transaction, coming from a 2-of-2 swap-in transaction. */ @@ -125,18 +125,12 @@ sealed class InteractiveTxInput { data class LocalSwapIn( override val serialId: Long, + override val previousTx: Transaction, + override val previousTxOutput: Long, override val sequence: UInt, val swapInParams: TxAddInputTlv.SwapInParams ) : Local() { - override val outPoint: OutPoint - get() = swapInParams.outPoint - override val previousTx: Transaction? - get() = null - override val txOut: TxOut - get() = swapInParams.txOut - - override val previousTxOutput: Long - get() = outPoint.index + override val outPoint: OutPoint = OutPoint(previousTx, previousTxOutput) } /** @@ -153,15 +147,11 @@ sealed class InteractiveTxInput { data class RemoteSwapIn( override val serialId: Long, + override val outPoint: OutPoint, + override val txOut: TxOut, override val sequence: UInt, val swapInParams: TxAddInputTlv.SwapInParams - ) : Remote() { - override val txOut: TxOut - get() = swapInParams.txOut - - override val outPoint: OutPoint - get() = swapInParams.outPoint - } + ) : Remote() /** The shared input can be added by us or by our peer, depending on who initiated the protocol. */ data class Shared( @@ -300,8 +290,10 @@ data class FundingContributions(val inputs: List, v else -> InteractiveTxInput.LocalSwapIn( 0, + i.previousTx.stripInputWitnesses(), + i.outputIndex.toLong(), 0xfffffffdU, - TxAddInputTlv.SwapInParams(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.userRefundPublicKey, swapInKeys.refundDelay, i.outPoint, i.txOut), + TxAddInputTlv.SwapInParams(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.userRefundPublicKey, swapInKeys.refundDelay), ) } } @@ -681,7 +673,7 @@ data class InteractiveTxSession( } is InteractiveTxInput.LocalSwapIn -> { - val swapInParams = TxAddInputTlv.SwapInParams(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.userRefundPublicKey, swapInKeys.refundDelay, msg.value.outPoint, msg.value.txOut) + val swapInParams = TxAddInputTlv.SwapInParams(swapInKeys.userPublicKey, swapInKeys.remoteServerPublicKey, swapInKeys.userRefundPublicKey, swapInKeys.refundDelay) TxAddInput(fundingParams.channelId, msg.value.serialId, msg.value.previousTx, msg.value.previousTxOutput, msg.value.sequence, TlvStream(swapInParams)) } @@ -721,16 +713,14 @@ data class InteractiveTxSession( return Either.Left(InteractiveTxSessionAction.DuplicateSerialId(message.channelId, message.serialId)) } // We check whether this is the shared input or a remote input. - val input = when { - message.previousTx == null && message.swapInParams != null -> { - InteractiveTxInput.RemoteSwapIn(message.serialId, message.sequence, message.swapInParams) - } - message.previousTx == null -> { + val input = when (message.previousTx) { + null -> { 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, fundingParams.sharedInput.info.txOut.publicKeyScript, message.sequence, previousFunding.toLocal, previousFunding.toRemote) } + else -> { if (message.previousTx.txOut.size <= message.previousTxOutput) { return Either.Left(InteractiveTxSessionAction.InputOutOfBounds(message.channelId, message.serialId, message.previousTx.txid, message.previousTxOutput)) @@ -747,7 +737,7 @@ data class InteractiveTxSession( val txOut = message.previousTx.txOut[message.previousTxOutput.toInt()] when { message.swapInParams != null -> { - InteractiveTxInput.RemoteSwapIn(message.serialId, message.sequence, message.swapInParams) + InteractiveTxInput.RemoteSwapIn(message.serialId, outpoint, txOut, message.sequence, message.swapInParams) } message.swapInParamsLegacy != null -> InteractiveTxInput.RemoteLegacySwapIn(message.serialId, outpoint, txOut, message.sequence, message.swapInParamsLegacy.userKey, message.swapInParamsLegacy.serverKey, message.swapInParamsLegacy.refundDelay) 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 8ea826343..3da39f371 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Deserialization.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Deserialization.kt @@ -246,6 +246,8 @@ object Deserialization { ) 0x03 -> InteractiveTxInput.LocalSwapIn( serialId = readNumber(), + previousTx = readTransaction(), + previousTxOutput = readNumber(), sequence = readNumber().toUInt(), swapInParams = TxAddInputTlv.SwapInParams.read(this) ) @@ -270,6 +272,8 @@ object Deserialization { ) 0x03 -> InteractiveTxInput.RemoteSwapIn( serialId = readNumber(), + outPoint = readOutPoint(), + txOut = TxOut.read(readDelimitedByteArray()), sequence = readNumber().toUInt(), swapInParams = TxAddInputTlv.SwapInParams.read(this) ) 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 aa523605a..72f7fb560 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Serialization.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/serialization/v4/Serialization.kt @@ -285,6 +285,8 @@ object Serialization { is InteractiveTxInput.LocalSwapIn -> i.run { write(0x03) writeNumber(serialId) + writeBtcObject(previousTx) + writeNumber(previousTxOutput) writeNumber(sequence.toLong()) swapInParams.write(this@writeLocalInteractiveTxInput) } @@ -311,6 +313,8 @@ object Serialization { is InteractiveTxInput.RemoteSwapIn -> i.run { write(0x03) writeNumber(serialId) + writeBtcObject(outPoint) + writeBtcObject(txOut) writeNumber(sequence.toLong()) swapInParams.write(this@writeRemoteInteractiveTxInput) } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt b/src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt index db7717ada..b9697b26a 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt @@ -44,19 +44,13 @@ sealed class TxAddInputTlv : Tlv { } /** When adding a swap-in input to an interactive-tx, the user needs to provide the corresponding script parameters. */ - data class SwapInParams(val userKey: PublicKey, val serverKey: PublicKey, val userRefundKey: PublicKey, val refundDelay: Int, val outPoint: OutPoint, val txOut: TxOut) : TxAddInputTlv() { + data class SwapInParams(val userKey: PublicKey, val serverKey: PublicKey, val userRefundKey: PublicKey, val refundDelay: Int) : TxAddInputTlv() { override val tag: Long get() = SwapInParams.tag override fun write(out: Output) { LightningCodecs.writeBytes(userKey.value, out) LightningCodecs.writeBytes(serverKey.value, out) LightningCodecs.writeBytes(userRefundKey.value, out) LightningCodecs.writeU32(refundDelay, out) - val blob1 = OutPoint.write(outPoint) - LightningCodecs.writeU16(blob1.size, out) - LightningCodecs.writeBytes(blob1, out) - val blob2 = TxOut.write(txOut) - LightningCodecs.writeU16(blob2.size, out) - LightningCodecs.writeBytes(blob2, out) } companion object : TlvValueReader { @@ -65,9 +59,7 @@ sealed class TxAddInputTlv : Tlv { PublicKey(LightningCodecs.bytes(input, 33)), PublicKey(LightningCodecs.bytes(input, 33)), PublicKey(LightningCodecs.bytes(input, 33)), - LightningCodecs.u32(input), - OutPoint.read(LightningCodecs.bytes(input, LightningCodecs.u16(input))), - TxOut.read(LightningCodecs.bytes(input, LightningCodecs.u16(input))) + LightningCodecs.u32(input) ) } } diff --git a/src/commonTest/kotlin/fr/acinq/lightning/blockchain/electrum/SwapInManagerTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/blockchain/electrum/SwapInManagerTestsCommon.kt index 5480bf68b..eedc34b1a 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/blockchain/electrum/SwapInManagerTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/blockchain/electrum/SwapInManagerTestsCommon.kt @@ -135,10 +135,8 @@ class SwapInManagerTestsCommon : LightningTestSuite() { @Ignore // FIXME fun `swap funds -- ignore inputs from pending channel`() { val (waitForFundingSigned, _) = WaitForFundingSignedTestsCommon.init() - val inputs = waitForFundingSigned.state.signingSession.fundingTx.tx.localInputs val wallet = run { - val parentTxs = inputs.associate { it.outPoint.txid to Transaction(version = 2, txIn = listOf(), txOut = listOf(it.txOut), lockTime = 0) } - val utxos = inputs.map { i -> WalletState.Utxo(i.outPoint.txid, i.outPoint.index.toInt(), 100, parentTxs[i.outPoint.txid]!!) } + val utxos = waitForFundingSigned.state.signingSession.fundingTx.tx.localInputs.map { i -> WalletState.Utxo(i.outPoint.txid, i.outPoint.index.toInt(), 100, i.previousTx) } WalletState(mapOf(dummyAddress to utxos)) } val mgr = SwapInManager(listOf(waitForFundingSigned.state), logger) @@ -146,7 +144,7 @@ class SwapInManagerTestsCommon : LightningTestSuite() { mgr.process(cmd).also { assertNull(it) } // The pending channel is aborted: we can reuse those inputs. - mgr.process(SwapInCommand.UnlockWalletInputs(inputs.map { it.outPoint }.toSet())) + mgr.process(SwapInCommand.UnlockWalletInputs(wallet.utxos.map { it.outPoint }.toSet())) mgr.process(cmd).also { assertNotNull(it) } } @@ -158,8 +156,7 @@ class SwapInManagerTestsCommon : LightningTestSuite() { val inputs = alice1.commitments.active.map { it.localFundingStatus }.filterIsInstance().flatMap { it.sharedTx.tx.localInputs } assertEquals(3, inputs.size) // 1 initial funding input and 2 splice inputs val wallet = run { - val parentTxs = inputs.associate { it.outPoint.txid to Transaction(version = 2, txIn = listOf(), txOut = listOf(it.txOut), lockTime = 0) } - val utxos = inputs.map { i -> WalletState.Utxo(i.outPoint.txid, i.outPoint.index.toInt(), 100, parentTxs[i.outPoint.txid]!!) } + val utxos = inputs.map { i -> WalletState.Utxo(i.outPoint.txid, i.outPoint.index.toInt(), 100, i.previousTx) } WalletState(mapOf(dummyAddress to utxos)) } val mgr = SwapInManager(listOf(alice1.state), logger) @@ -167,7 +164,7 @@ class SwapInManagerTestsCommon : LightningTestSuite() { mgr.process(cmd).also { assertNull(it) } // The channel is aborted: we can reuse those inputs. - mgr.process(SwapInCommand.UnlockWalletInputs(inputs.map { it.outPoint }.toSet())) + mgr.process(SwapInCommand.UnlockWalletInputs(wallet.utxos.map { it.outPoint }.toSet())) mgr.process(cmd).also { result -> assertNotNull(result) assertEquals(3, result.walletInputs.size) @@ -189,8 +186,7 @@ class SwapInManagerTestsCommon : LightningTestSuite() { assertEquals(1, alice3.commitments.all.size) assertIs(alice3.commitments.latest.localFundingStatus) val wallet = run { - val parentTxs = inputs.associate { it.outPoint.txid to Transaction(version = 2, txIn = listOf(), txOut = listOf(it.txOut), lockTime = 0) } - val utxos = inputs.map { i -> WalletState.Utxo(i.outPoint.txid, i.outPoint.index.toInt(), 100, parentTxs[i.outPoint.txid]!!) } + val utxos = inputs.map { i -> WalletState.Utxo(i.outPoint.txid, i.outPoint.index.toInt(), 100, i.previousTx) } WalletState(mapOf(dummyAddress to utxos)) } val mgr = SwapInManager(listOf(alice3.state), logger) diff --git a/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt index 41ebb796c..c389a060e 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/channel/InteractiveTxTestsCommon.kt @@ -101,7 +101,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(signedTx.lockTime, 42) assertEquals(signedTx.txIn.size, 4) assertEquals(signedTx.txOut.size, 3) - Transaction.correctlySpends(signedTx, (sharedTxA.sharedTx.localInputs + sharedTxB.sharedTx.localInputs).associate { it.outPoint to it.txOut }, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(signedTx, (sharedTxA.sharedTx.localInputs + sharedTxB.sharedTx.localInputs).map { it.previousTx }, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val feerate = Transactions.fee2rate(signedTxA.tx.fees, signedTx.weight()) assertTrue(targetFeerate <= feerate && feerate <= targetFeerate * 1.25, "unexpected feerate (target=$targetFeerate actual=$feerate)") } @@ -162,7 +162,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(signedTx.lockTime, 0) assertEquals(signedTx.txIn.size, 2) assertEquals(signedTx.txOut.size, 3) - Transaction.correctlySpends(signedTx, (sharedTxA.sharedTx.localInputs + sharedTxB.sharedTx.localInputs).associate { it.outPoint to it.txOut }, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(signedTx, (sharedTxA.sharedTx.localInputs + sharedTxB.sharedTx.localInputs).map { it.previousTx }, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val feerate = Transactions.fee2rate(signedTxB.tx.fees, signedTx.weight()) assertTrue(targetFeerate <= feerate && feerate <= targetFeerate * 1.25, "unexpected feerate (target=$targetFeerate actual=$feerate)") } @@ -214,7 +214,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { // The resulting transaction is valid and has the right feerate. val signedTxB = sharedTxB.sharedTx.sign(bob3, f.keyManagerB, f.fundingParamsB, f.localParamsB, f.localParamsA.nodeId).addRemoteSigs(f.channelKeysB, f.fundingParamsB, signedTxA.localSigs) assertNotNull(signedTxB) - Transaction.correctlySpends(signedTxB.signedTx, (sharedTxA.sharedTx.localInputs + sharedTxB.sharedTx.localInputs).associate { it.outPoint to it.txOut }, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + 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()) assertTrue(targetFeerate <= feerate && feerate <= targetFeerate * 1.25, "unexpected feerate (target=$targetFeerate actual=$feerate)") } @@ -279,7 +279,7 @@ class InteractiveTxTestsCommon : LightningTestSuite() { assertEquals(signedTx.lockTime, 0) assertEquals(signedTx.txIn.size, 2) assertEquals(signedTx.txOut.size, 2) - Transaction.correctlySpends(signedTx, sharedTxA.sharedTx.localInputs.associate { it.outPoint to it.txOut }, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(signedTx, sharedTxA.sharedTx.localInputs.map { it.previousTx }, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val feerate = Transactions.fee2rate(signedTxA.tx.fees, signedTx.weight()) assertTrue(targetFeerate <= feerate && feerate <= targetFeerate * 1.25, "unexpected feerate (target=$targetFeerate actual=$feerate)") } diff --git a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmedTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmedTestsCommon.kt index 650a21502..02fb630d8 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmedTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingConfirmedTestsCommon.kt @@ -226,13 +226,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() { assertIs(bob1.state.rbfStatus) assertEquals(actions1.size, 1) actions1.hasOutgoingMessage() - val input = alice.state.latestFundingTx.sharedTx.tx.localInputs.first() - val tlvs = when (input) { - is InteractiveTxInput.LocalSwapIn -> TlvStream(input.swapInParams) - is InteractiveTxInput.LocalLegacySwapIn -> TlvStream(TxAddInputTlv.SwapInParamsLegacy(input.userKey, input.serverKey, input.refundDelay)) - is InteractiveTxInput.LocalOnly -> TlvStream.empty() - } - val txAddInput = input.run { TxAddInput(alice.channelId, serialId, previousTx, previousTxOutput, sequence, tlvs) } + val txAddInput = alice.state.latestFundingTx.sharedTx.tx.localInputs.first().run { TxAddInput(alice.channelId, serialId, previousTx, previousTxOutput, sequence) } val (bob2, actions2) = bob1.process(ChannelCommand.MessageReceived(txAddInput)) assertEquals(actions2.size, 1) actions2.hasOutgoingMessage()