Skip to content

Commit

Permalink
Revert "Do not send the previous tx for swap-in inputs"
Browse files Browse the repository at this point in the history
This reverts commit c0a6d5a.
  • Loading branch information
sstone committed Jan 30, 2024
1 parent f2e1465 commit 361faa9
Show file tree
Hide file tree
Showing 8 changed files with 37 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ data class WalletState(val addresses: Map<String, List<Utxo>>) {
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]
}

/**
Expand Down
44 changes: 17 additions & 27 deletions src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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)
}

/**
Expand All @@ -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(
Expand Down Expand Up @@ -300,8 +290,10 @@ data class FundingContributions(val inputs: List<InteractiveTxInput.Outgoing>, 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),
)
}
}
Expand Down Expand Up @@ -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))
}

Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ object Deserialization {
)
0x03 -> InteractiveTxInput.LocalSwapIn(
serialId = readNumber(),
previousTx = readTransaction(),
previousTxOutput = readNumber(),
sequence = readNumber().toUInt(),
swapInParams = TxAddInputTlv.SwapInParams.read(this)
)
Expand All @@ -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)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down
12 changes: 2 additions & 10 deletions src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<SwapInParams> {
Expand All @@ -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)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,16 @@ 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)
val cmd = SwapInCommand.TrySwapIn(currentBlockHeight = 150, wallet = wallet, swapInParams = SwapInParams(minConfirmations = 5, maxConfirmations = 720, refundDelay = 900), trustedTxs = emptySet())
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) }
}

Expand All @@ -158,16 +156,15 @@ class SwapInManagerTestsCommon : LightningTestSuite() {
val inputs = alice1.commitments.active.map { it.localFundingStatus }.filterIsInstance<LocalFundingStatus.UnconfirmedFundingTx>().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)
val cmd = SwapInCommand.TrySwapIn(currentBlockHeight = 150, wallet = wallet, swapInParams = SwapInParams(minConfirmations = 5, maxConfirmations = 720, refundDelay = 900), trustedTxs = emptySet())
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)
Expand All @@ -189,8 +186,7 @@ class SwapInManagerTestsCommon : LightningTestSuite() {
assertEquals(1, alice3.commitments.all.size)
assertIs<LocalFundingStatus.ConfirmedFundingTx>(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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
Expand Down Expand Up @@ -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)")
}
Expand Down Expand Up @@ -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)")
}
Expand Down Expand Up @@ -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)")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,7 @@ class WaitForFundingConfirmedTestsCommon : LightningTestSuite() {
assertIs<RbfStatus.InProgress>(bob1.state.rbfStatus)
assertEquals(actions1.size, 1)
actions1.hasOutgoingMessage<TxAckRbf>()
val input = alice.state.latestFundingTx.sharedTx.tx.localInputs.first()
val tlvs = when (input) {
is InteractiveTxInput.LocalSwapIn -> TlvStream<TxAddInputTlv>(input.swapInParams)
is InteractiveTxInput.LocalLegacySwapIn -> TlvStream<TxAddInputTlv>(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<TxAddInput>()
Expand Down

0 comments on commit 361faa9

Please sign in to comment.