Skip to content

Commit fa58393

Browse files
committed
fix: peer connection parsing and verification
1 parent 604f6ba commit fa58393

File tree

4 files changed

+119
-22
lines changed

4 files changed

+119
-22
lines changed

app/src/main/java/to/bitkit/ext/PeerDetails.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ fun PeerDetails.Companion.from(nodeId: String, host: String, port: String) = Pee
3838
)
3939

4040
fun ILspNode.toPeerDetails(): PeerDetails? {
41-
val address = connectionStrings.firstOrNull() ?: return null
41+
val connectionString = connectionStrings.firstOrNull() ?: return null
42+
val address = connectionString.substringAfter("@", "")
43+
if (address.isEmpty()) return null
4244
return PeerDetails(
4345
nodeId = pubkey,
4446
address = address,

app/src/main/java/to/bitkit/services/LightningService.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,27 @@ class LightningService @Inject constructor(
298298
Logger.error("Peer connect error: $peer", LdkError(e))
299299
}
300300
}
301+
302+
verifyTrustedPeersOrFallback(node)
303+
}
304+
}
305+
306+
private fun verifyTrustedPeersOrFallback(node: Node) {
307+
val connectedPeerIds = node.listPeers().map { it.nodeId }.toSet()
308+
val trustedConnected = trustedPeers.count { it.nodeId in connectedPeerIds }
309+
310+
if (trustedConnected == 0 && trustedPeers.isNotEmpty()) {
311+
Logger.warn("No trusted peers connected, falling back to Env peers", context = TAG)
312+
for (peer in Env.trustedLnPeers) {
313+
try {
314+
node.connect(peer.nodeId, peer.address, persist = true)
315+
Logger.info("Connected to fallback peer: $peer")
316+
} catch (e: NodeException) {
317+
Logger.error("Fallback peer connect error: $peer", LdkError(e))
318+
}
319+
}
320+
} else {
321+
Logger.info("Connected to $trustedConnected/${trustedPeers.size} trusted peers", context = TAG)
301322
}
302323
}
303324

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package to.bitkit.ext
2+
3+
import com.synonym.bitkitcore.ILspNode
4+
import org.junit.Test
5+
import to.bitkit.test.BaseUnitTest
6+
import kotlin.test.assertEquals
7+
import kotlin.test.assertNull
8+
9+
class PeerDetailsExtTest : BaseUnitTest() {
10+
11+
// Exact format from https://api.stag0.blocktank.to/blocktank/api/v2/info
12+
@Test
13+
fun `toPeerDetails extracts address from Blocktank API connection string`() {
14+
val node = ILspNode(
15+
alias = "Synonym-Own-Regtest-0",
16+
pubkey = "028a8910b0048630d4eb17af25668cdd7ea6f2d8ae20956e7a06e2ae46ebcb69fc",
17+
connectionStrings = listOf(
18+
"028a8910b0048630d4eb17af25668cdd7ea6f2d8ae20956e7a06e2ae46ebcb69fc@34.65.86.104:9400"
19+
),
20+
readonly = false,
21+
)
22+
23+
val peerDetails = node.toPeerDetails()
24+
25+
assertEquals("028a8910b0048630d4eb17af25668cdd7ea6f2d8ae20956e7a06e2ae46ebcb69fc", peerDetails?.nodeId)
26+
assertEquals("34.65.86.104:9400", peerDetails?.address)
27+
}
28+
29+
@Test
30+
fun `toPeerDetails returns null when connectionStrings is empty`() {
31+
val node = ILspNode(
32+
alias = "LSP",
33+
pubkey = "somepubkey",
34+
connectionStrings = emptyList(),
35+
readonly = null,
36+
)
37+
38+
assertNull(node.toPeerDetails())
39+
}
40+
41+
@Test
42+
fun `toPeerDetails returns null when connection string has no @ separator`() {
43+
val node = ILspNode(
44+
alias = "LSP",
45+
pubkey = "somePubkey",
46+
connectionStrings = listOf("invalid-connection-string"),
47+
readonly = null,
48+
)
49+
50+
assertNull(node.toPeerDetails())
51+
}
52+
53+
@Test
54+
fun `toPeerDetailsList maps list of nodes`() {
55+
val nodes = listOf(
56+
ILspNode(
57+
alias = "LSP1",
58+
pubkey = "pubkey1",
59+
connectionStrings = listOf("[email protected]:9735"),
60+
readonly = null,
61+
),
62+
ILspNode(
63+
alias = "LSP2",
64+
pubkey = "pubkey2",
65+
connectionStrings = listOf("[email protected]:9735"),
66+
readonly = null,
67+
),
68+
)
69+
70+
val peerDetailsList = nodes.toPeerDetailsList()
71+
72+
assertEquals(2, peerDetailsList.size)
73+
assertEquals("pubkey1", peerDetailsList[0].nodeId)
74+
assertEquals("host1.com:9735", peerDetailsList[0].address)
75+
assertEquals("pubkey2", peerDetailsList[1].nodeId)
76+
assertEquals("host2.com:9735", peerDetailsList[1].address)
77+
}
78+
}

app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.synonym.bitkitcore.FeeRates
66
import com.synonym.bitkitcore.IBtInfo
77
import com.synonym.bitkitcore.ILspNode
88
import kotlinx.coroutines.flow.flowOf
9+
import kotlinx.coroutines.runBlocking
910
import org.junit.Before
1011
import org.junit.Test
1112
import org.lightningdevkit.ldknode.ChannelDetails
@@ -25,7 +26,6 @@ import org.mockito.kotlin.spy
2526
import org.mockito.kotlin.verify
2627
import org.mockito.kotlin.verifyBlocking
2728
import org.mockito.kotlin.whenever
28-
import org.mockito.kotlin.wheneverBlocking
2929
import to.bitkit.data.AppCacheData
3030
import to.bitkit.data.CacheStore
3131
import to.bitkit.data.SettingsData
@@ -51,23 +51,21 @@ import kotlin.test.assertNull
5151
import kotlin.test.assertTrue
5252

5353
class LightningRepoTest : BaseUnitTest() {
54-
5554
private lateinit var sut: LightningRepo
5655

57-
private val lightningService: LightningService = mock()
58-
private val settingsStore: SettingsStore = mock()
59-
private val coreService: CoreService = mock()
60-
private val lspNotificationsService: LspNotificationsService = mock()
61-
private val firebaseMessaging: FirebaseMessaging = mock()
62-
private val keychain: Keychain = mock()
63-
private val cacheStore: CacheStore = mock()
64-
private val preActivityMetadataRepo: PreActivityMetadataRepo = mock()
65-
66-
private val lnurlService: LnurlService = mock()
56+
private val lightningService = mock<LightningService>()
57+
private val settingsStore = mock<SettingsStore>()
58+
private val coreService = mock<CoreService>()
59+
private val lspNotificationsService = mock<LspNotificationsService>()
60+
private val firebaseMessaging = mock<FirebaseMessaging>()
61+
private val keychain = mock<Keychain>()
62+
private val cacheStore = mock<CacheStore>()
63+
private val preActivityMetadataRepo = mock<PreActivityMetadataRepo>()
64+
private val lnurlService = mock<LnurlService>()
6765

6866
@Before
69-
fun setUp() {
70-
wheneverBlocking { coreService.isGeoBlocked() }.thenReturn(false)
67+
fun setUp() = runBlocking {
68+
whenever(coreService.isGeoBlocked()).thenReturn(false)
7169
sut = LightningRepo(
7270
bgDispatcher = testDispatcher,
7371
lightningService = lightningService,
@@ -381,9 +379,7 @@ class LightningRepoTest : BaseUnitTest() {
381379
)
382380
whenever(settingsStore.data).thenReturn(flowOf(mockSettingsData))
383381

384-
wheneverBlocking {
385-
preActivityMetadataRepo.addPreActivityMetadata(any())
386-
}.thenReturn(Result.success(Unit))
382+
whenever(preActivityMetadataRepo.addPreActivityMetadata(any())).thenReturn(Result.success(Unit))
387383

388384
whenever(
389385
lightningService.send(
@@ -603,13 +599,13 @@ class LightningRepoTest : BaseUnitTest() {
603599
ILspNode(
604600
alias = "LSP1",
605601
pubkey = "node1pubkey",
606-
connectionStrings = listOf("node1.example.com:9735"),
602+
connectionStrings = listOf("node1pubkey@node1.example.com:9735"),
607603
readonly = null,
608604
),
609605
ILspNode(
610606
alias = "LSP2",
611607
pubkey = "node2pubkey",
612-
connectionStrings = listOf("node2.example.com:9735"),
608+
connectionStrings = listOf("node2pubkey@node2.example.com:9735"),
613609
readonly = null,
614610
),
615611
)
@@ -625,8 +621,8 @@ class LightningRepoTest : BaseUnitTest() {
625621
anyOrNull(),
626622
argThat { peers ->
627623
peers?.size == 2 &&
628-
peers.any { it.nodeId == "node1pubkey" } &&
629-
peers.any { it.nodeId == "node2pubkey" }
624+
peers.any { it.nodeId == "node1pubkey" && it.address == "node1.example.com:9735" } &&
625+
peers.any { it.nodeId == "node2pubkey" && it.address == "node2.example.com:9735" }
630626
}
631627
)
632628
}

0 commit comments

Comments
 (0)