Skip to content

Commit

Permalink
Don't double-spend unspendable inputs
Browse files Browse the repository at this point in the history
When filtering inputs that can be used for swaps, we filter out all the
inputs that are already used in pending funding transactions. However,
we ignored previously confirmed funding transactions, assuming that our
electrum server would have already filtered them from our wallet state.

Unfortunately, we cannot assume that our electrum server is up-to-date
(especially if we connect to a different one after a restart) and behaving
correctly, so we must also exclude inputs use in the latest confirmed
funding transaction.
  • Loading branch information
t-bast committed Nov 20, 2023
1 parent 79b7477 commit 0a455f0
Showing 1 changed file with 13 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,31 +57,34 @@ class SwapInManager(private var reservedUtxos: Set<OutPoint>, private val logger

companion object {
/**
* Return the list of wallet inputs used in pending unconfirmed channel funding attempts.
* Return the list of wallet inputs already used in channel funding attempts.
* These inputs should not be reused in other funding attempts, otherwise we would double-spend ourselves.
*/
fun reservedWalletInputs(channels: List<PersistedChannelState>): Set<OutPoint> {
val unconfirmedFundingTxs: List<SignedSharedTransaction> = buildList {
return buildSet {
for (channel in channels) {
// Add all unsigned inputs currently used to build a funding tx that isn't broadcast yet (creation, rbf, splice).
when {
channel is WaitForFundingSigned -> add(channel.signingSession.fundingTx)
channel is WaitForFundingConfirmed && channel.rbfStatus is RbfStatus.WaitingForSigs -> add(channel.rbfStatus.session.fundingTx)
channel is Normal && channel.spliceStatus is SpliceStatus.WaitingForSigs -> add(channel.spliceStatus.session.fundingTx)
channel is WaitForFundingSigned -> addAll(channel.signingSession.fundingTx.tx.localInputs.map { it.outPoint })
channel is WaitForFundingConfirmed && channel.rbfStatus is RbfStatus.WaitingForSigs -> addAll(channel.rbfStatus.session.fundingTx.tx.localInputs.map { it.outPoint })
channel is Normal && channel.spliceStatus is SpliceStatus.WaitingForSigs -> addAll(channel.spliceStatus.session.fundingTx.tx.localInputs.map { it.outPoint })
else -> {}
}
// Add all inputs in unconfirmed funding txs (utxos spent by confirmed transactions will never appear in our wallet).
// Add all inputs from previously broadcast funding txs.
// We include confirmed transactions as well, in case our electrum server (and thus our wallet) isn't up-to-date.
when (channel) {
is ChannelStateWithCommitments -> channel.commitments.all
.map { it.localFundingStatus }
.filterIsInstance<LocalFundingStatus.UnconfirmedFundingTx>()
.forEach { add(it.sharedTx) }
.forEach { fundingStatus ->
when (fundingStatus) {
is LocalFundingStatus.UnconfirmedFundingTx -> addAll(fundingStatus.sharedTx.tx.localInputs.map { it.outPoint })
is LocalFundingStatus.ConfirmedFundingTx -> addAll(fundingStatus.signedTx.txIn.map { it.outPoint })
}
}
else -> {}
}
}
}
val localInputs = unconfirmedFundingTxs.flatMap { fundingTx -> fundingTx.tx.localInputs.map { it.outPoint } }
return localInputs.toSet()
}
}
}

0 comments on commit 0a455f0

Please sign in to comment.