Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,13 @@ class BlocktankRepo @Inject constructor(
}

suspend fun getCjitEntry(channel: ChannelDetails): IcJitEntry? = withContext(bgDispatcher) {
return@withContext _blocktankState.value.cjitEntries.firstOrNull { order ->
order.channelSizeSat == channel.channelValueSats &&
order.lspNode.pubkey == channel.counterpartyNodeId
}
val fundingTxId = channel.fundingTxo?.txid ?: return@withContext null

// Refresh from the server so a freshly opened CJIT channel association is up to date before matching.
val entries = runCatching { coreService.blocktank.cjitEntries(refresh = true) }
.getOrElse { _blocktankState.value.cjitEntries }
Comment thread
jvsena42 marked this conversation as resolved.
Outdated

return@withContext entries.firstOrNull { it.channel?.fundingTx?.id == fundingTxId }
Comment thread
jvsena42 marked this conversation as resolved.
Outdated
Comment thread
jvsena42 marked this conversation as resolved.
Outdated
}

suspend fun refreshInfo() = withContext(bgDispatcher) {
Expand Down
64 changes: 64 additions & 0 deletions app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package to.bitkit.repositories

import app.cash.turbine.test
import com.synonym.bitkitcore.FundingTx
import com.synonym.bitkitcore.IBtChannel
import com.synonym.bitkitcore.IBtInfo
import com.synonym.bitkitcore.IBtOrder
import com.synonym.bitkitcore.IcJitEntry
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import org.junit.Before
import org.junit.Test
import org.lightningdevkit.ldknode.ChannelDetails
import org.lightningdevkit.ldknode.OutPoint
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
Expand Down Expand Up @@ -192,4 +197,63 @@ class BlocktankRepoTest : BaseUnitTest() {
assertTrue(result.isFailure)
}
}

@Test
fun `getCjitEntry returns null when channel has no funding txo`() = test {
sut = createSut()
val channelDetails = mock<ChannelDetails>()
whenever(channelDetails.fundingTxo).thenReturn(null)

assertNull(sut.getCjitEntry(channelDetails))
}

@Test
fun `getCjitEntry does not match a stale unpaid CJIT entry without an opened channel`() = test {
sut = createSut()
// A leftover CJIT entry that was never paid: same size & LSP as a transfer-flow channel order,
// but it never opened a channel. It must not be mistaken for the freshly opened channel.
val staleEntry = mock<IcJitEntry>()
whenever(staleEntry.channel).thenReturn(null)
whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(staleEntry))

val channelDetails = mock<ChannelDetails>()
whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = "channel-order-funding-tx", vout = 0u))

assertNull(sut.getCjitEntry(channelDetails))
}

@Test
fun `getCjitEntry matches the entry whose channel funding tx matches`() = test {
sut = createSut()
val fundingTxId = "cjit-funding-tx"
val matchingChannel = mock<IBtChannel>()
whenever(matchingChannel.fundingTx).thenReturn(FundingTx(id = fundingTxId, vout = 0u))
val otherChannel = mock<IBtChannel>()
whenever(otherChannel.fundingTx).thenReturn(FundingTx(id = "other-funding-tx", vout = 0u))
val matchingEntry = mock<IcJitEntry>()
whenever(matchingEntry.channel).thenReturn(matchingChannel)
val otherEntry = mock<IcJitEntry>()
whenever(otherEntry.channel).thenReturn(otherChannel)
whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(otherEntry, matchingEntry))

val channelDetails = mock<ChannelDetails>()
whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = fundingTxId, vout = 0u))

assertEquals(matchingEntry, sut.getCjitEntry(channelDetails))
}

@Test
fun `getCjitEntry returns null when no CJIT channel funding tx matches`() = test {
sut = createSut()
val channel = mock<IBtChannel>()
whenever(channel.fundingTx).thenReturn(FundingTx(id = "cjit-funding-tx", vout = 0u))
val entry = mock<IcJitEntry>()
whenever(entry.channel).thenReturn(channel)
whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(entry))

val channelDetails = mock<ChannelDetails>()
whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = "different-funding-tx", vout = 0u))

assertNull(sut.getCjitEntry(channelDetails))
}
}
1 change: 1 addition & 0 deletions changelog.d/next/1017.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Transferring to your spending balance now reliably shows the "Spending Balance Ready" confirmation instead of sometimes being mistaken for an incoming payment when an unused instant-payment invoice is still pending.
Loading