Skip to content

Commit 933b619

Browse files
authored
Merge pull request #371 from synonymdev/fix/improve-geoblocks-checks
Improve geoblock validation for devices connected to external nodes
2 parents 5ee1324 + 62c41f0 commit 933b619

File tree

11 files changed

+83
-72
lines changed

11 files changed

+83
-72
lines changed

app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ class BlocktankRepo @Inject constructor(
179179
description: String = Env.DEFAULT_INVOICE_MESSAGE,
180180
): Result<IcJitEntry> = withContext(bgDispatcher) {
181181
try {
182+
if (coreService.checkGeoBlock().first) throw ServiceError.GeoBlocked
182183
val nodeId = lightningService.nodeId ?: throw ServiceError.NodeNotStarted
183184
val lspBalance = getDefaultLspBalance(clientBalance = amountSats)
184185
val channelSizeSat = amountSats + lspBalance
@@ -207,6 +208,8 @@ class BlocktankRepo @Inject constructor(
207208
channelExpiryWeeks: UInt = DEFAULT_CHANNEL_EXPIRY_WEEKS,
208209
): Result<IBtOrder> = withContext(bgDispatcher) {
209210
try {
211+
if (coreService.checkGeoBlock().first) throw ServiceError.GeoBlocked
212+
210213
val options = defaultCreateOrderOptions(clientBalanceSat = spendingBalanceSats)
211214

212215
Logger.info(

app/src/main/java/to/bitkit/repositories/LightningRepo.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,11 @@ class LightningRepo @Inject constructor(
246246
}
247247

248248
/**Updates the shouldBlockLightning state and returns the current value*/
249-
private suspend fun updateGeoBlockState(): Boolean {
250-
val shouldBlock = coreService.shouldBlockLightning()
249+
suspend fun updateGeoBlockState() {
250+
val (isGeoBlocked, shouldBlockLightning) = coreService.checkGeoBlock()
251251
_lightningState.update {
252-
it.copy(shouldBlockLightning = shouldBlock)
252+
it.copy(shouldBlockLightning = shouldBlockLightning, isGeoBlocked = isGeoBlocked)
253253
}
254-
return shouldBlock
255254
}
256255

257256
fun setInitNodeLifecycleState() {
@@ -409,7 +408,9 @@ class LightningRepo @Inject constructor(
409408
}
410409

411410
suspend fun connectPeer(peer: LnPeer): Result<Unit> = executeWhenNodeRunning("connectPeer") {
412-
lightningService.connectPeer(peer)
411+
lightningService.connectPeer(peer).onFailure { e ->
412+
return@executeWhenNodeRunning Result.failure(e)
413+
}
413414
syncState()
414415
Result.success(Unit)
415416
}
@@ -430,7 +431,8 @@ class LightningRepo @Inject constructor(
430431
description: String,
431432
expirySeconds: UInt = 86_400u,
432433
): Result<String> = executeWhenNodeRunning("Create invoice") {
433-
if (updateGeoBlockState()) {
434+
updateGeoBlockState()
435+
if (lightningState.value.shouldBlockLightning) {
434436
return@executeWhenNodeRunning Result.failure(ServiceError.GeoBlocked)
435437
}
436438

@@ -850,4 +852,5 @@ data class LightningState(
850852
val channels: List<ChannelDetails> = emptyList(),
851853
val isSyncingWallet: Boolean = false,
852854
val shouldBlockLightning: Boolean = false,
855+
val isGeoBlocked: Boolean = false,
853856
)

app/src/main/java/to/bitkit/repositories/WalletRepo.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class WalletRepo @Inject constructor(
102102
suspend fun refreshBip21(force: Boolean = false): Result<Unit> = withContext(bgDispatcher) {
103103
Logger.debug("Refreshing bip21 (force: $force)", context = TAG)
104104

105-
if (coreService.shouldBlockLightning()) {
105+
if (coreService.checkGeoBlock().second) {
106106
_walletState.update {
107107
it.copy(receiveOnSpendingBalance = false)
108108
}
@@ -352,7 +352,7 @@ class WalletRepo @Inject constructor(
352352
}
353353

354354
suspend fun toggleReceiveOnSpendingBalance(): Result<Unit> = withContext(bgDispatcher) {
355-
if (!_walletState.value.receiveOnSpendingBalance && coreService.shouldBlockLightning()) {
355+
if (!_walletState.value.receiveOnSpendingBalance && coreService.checkGeoBlock().second) {
356356
return@withContext Result.failure(ServiceError.GeoBlocked)
357357
}
358358

@@ -424,7 +424,7 @@ class WalletRepo @Inject constructor(
424424
return@withContext try {
425425
if (!_walletState.value.receiveOnSpendingBalance) return@withContext Result.success(false)
426426

427-
if (coreService.checkGeoStatus() == true) return@withContext Result.success(false)
427+
if (coreService.checkGeoBlock().first) return@withContext Result.success(false)
428428

429429
val channels = lightningRepo.lightningState.value.channels
430430
val inboundBalanceSats = channels.sumOf { it.inboundCapacityMsat / 1000u }

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

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -112,28 +112,34 @@ class CoreService @Inject constructor(
112112
}
113113
}
114114

115-
/** Returns true if geo blocked */
116-
suspend fun checkGeoStatus(): Boolean? {
115+
private suspend fun isGeoBlocked(): Boolean {
117116
return ServiceQueue.CORE.background {
118-
Logger.verbose("Checking geo status…", context = "GeoCheck")
119-
val response = httpClient.get(Env.geoCheckUrl)
120-
121-
when (response.status.value) {
122-
HttpStatusCode.OK.value -> {
123-
Logger.verbose("Region allowed", context = "GeoCheck")
124-
false
125-
}
126-
127-
HttpStatusCode.Forbidden.value -> {
128-
Logger.warn("Region blocked", context = "GeoCheck")
129-
true
130-
}
131-
132-
else -> {
133-
Logger.warn("Unexpected status code: ${response.status.value}", context = "GeoCheck")
134-
null
117+
runCatching {
118+
Logger.verbose("Checking geo status…", context = "GeoCheck")
119+
val response = httpClient.get(Env.geoCheckUrl)
120+
121+
when (response.status.value) {
122+
HttpStatusCode.OK.value -> {
123+
Logger.verbose("Region allowed", context = "GeoCheck")
124+
false
125+
}
126+
127+
HttpStatusCode.Forbidden.value -> {
128+
Logger.warn("Region blocked", context = "GeoCheck")
129+
true
130+
}
131+
132+
else -> {
133+
Logger.warn(
134+
"Unexpected status code: ${response.status.value}, defaulting to false",
135+
context = "GeoCheck"
136+
)
137+
false
138+
}
135139
}
136-
}
140+
}.onFailure {
141+
Logger.warn("Error. defaulting isGeoBlocked to false", context = "GeoCheck")
142+
}.getOrDefault(false)
137143
}
138144
}
139145

@@ -149,7 +155,18 @@ class CoreService @Inject constructor(
149155

150156
suspend fun hasExternalNode() = getConnectedPeers().any { connectedPeer -> connectedPeer !in getLspPeers() }
151157

152-
suspend fun shouldBlockLightning() = checkGeoStatus() == true && !hasExternalNode()
158+
/**
159+
* This method checks if the device is geo blocked and if should block lighting features
160+
* @return Pair(isGeoBlocked, shouldBlockLightning)*/
161+
suspend fun checkGeoBlock(): Pair<Boolean, Boolean> {
162+
val geoBlocked = isGeoBlocked()
163+
val shouldBlockLightning = when {
164+
hasExternalNode() -> false
165+
else -> geoBlocked
166+
}
167+
168+
return Pair(geoBlocked, shouldBlockLightning)
169+
}
153170
}
154171

155172
// endregion

app/src/main/java/to/bitkit/ui/ContentView.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,8 @@ fun ContentView(
333333
ReceiveSheet(
334334
walletState = walletUiState,
335335
navigateToExternalConnection = {
336-
navController.navigate(Routes.ExternalConnection)
336+
navController.navigate(Routes.ExternalConnection())
337+
appViewModel.hideSheet()
337338
}
338339
)
339340
}
@@ -515,6 +516,8 @@ private fun RootNavHost(
515516
}
516517
composableWithDefaultTransitions<Routes.Funding> {
517518
val hasSeenSpendingIntro by settingsViewModel.hasSeenSpendingIntro.collectAsState()
519+
val isGeoBlocked by appViewModel.isGeoBlocked.collectAsStateWithLifecycle()
520+
518521
FundingScreen(
519522
onTransfer = {
520523
if (!hasSeenSpendingIntro) {
@@ -534,6 +537,7 @@ private fun RootNavHost(
534537
onAdvanced = { navController.navigate(Routes.FundingAdvanced) },
535538
onBackClick = { navController.popBackStack() },
536539
onCloseClick = { navController.navigateToHome() },
540+
isGeoBlocked = isGeoBlocked
537541
)
538542
}
539543
composableWithDefaultTransitions<Routes.FundingAdvanced> {

app/src/main/java/to/bitkit/ui/MainActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,10 @@ private fun OnboardingNav(
191191
}
192192
composableWithDefaultTransitions<StartupRoutes.Slides> { navBackEntry ->
193193
val route = navBackEntry.toRoute<StartupRoutes.Slides>()
194+
val isGeoBlocked by appViewModel.isGeoBlocked.collectAsStateWithLifecycle()
194195
OnboardingSlidesScreen(
195196
currentTab = route.tab,
196-
isGeoBlocked = appViewModel.isGeoBlocked == true,
197+
isGeoBlocked = isGeoBlocked,
197198
onAdvancedSetupClick = { startupNavController.navigate(StartupRoutes.Advanced) },
198199
onCreateClick = {
199200
scope.launch {

app/src/main/java/to/bitkit/ui/screens/transfer/FundingScreen.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import androidx.compose.ui.unit.dp
2828
import to.bitkit.R
2929
import to.bitkit.env.Env
3030
import to.bitkit.ui.LocalBalances
31-
import to.bitkit.ui.appViewModel
3231
import to.bitkit.ui.components.BodyM
3332
import to.bitkit.ui.components.BodyMB
3433
import to.bitkit.ui.components.Display
@@ -47,13 +46,13 @@ fun FundingScreen(
4746
onAdvanced: () -> Unit = {},
4847
onBackClick: () -> Unit = {},
4948
onCloseClick: () -> Unit = {},
49+
isGeoBlocked: Boolean
5050
) {
5151
val balances = LocalBalances.current
5252
val canTransfer = remember(balances.totalOnchainSats) {
5353
balances.totalOnchainSats >= Env.TransactionDefaults.recommendedBaseFee
5454
}
5555
var showNoFundsAlert by remember { mutableStateOf(false) }
56-
val isGeoBlocked = appViewModel?.isGeoBlocked == true
5756

5857
ScreenColumn {
5958
AppTopBar(
@@ -164,6 +163,6 @@ fun FundingScreen(
164163
@Composable
165164
private fun FundingScreenPreview() {
166165
AppThemeSurface {
167-
FundingScreen()
166+
FundingScreen(isGeoBlocked = false)
168167
}
169168
}

app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ fun ReceiveSheet(
8282
walletState = walletState,
8383
onCjitToggle = { isOn ->
8484
when {
85-
isOn && lightningState.shouldBlockLightning -> {
85+
isOn && lightningState.shouldBlockLightning -> { // TODO SHOULD BLOCK HERE
8686
navController.navigate(ReceiveRoute.GeoBlock)
8787
}
8888

app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ import to.bitkit.repositories.CurrencyRepo
7777
import to.bitkit.repositories.HealthRepo
7878
import to.bitkit.repositories.LightningRepo
7979
import to.bitkit.repositories.WalletRepo
80-
import to.bitkit.services.CoreService
8180
import to.bitkit.services.LdkNodeEventBus
8281
import to.bitkit.ui.Routes
8382
import to.bitkit.ui.components.Sheet
@@ -95,7 +94,6 @@ class AppViewModel @Inject constructor(
9594
private val keychain: Keychain,
9695
private val lightningRepo: LightningRepo,
9796
private val walletRepo: WalletRepo,
98-
private val coreService: CoreService,
9997
private val ldkNodeEventBus: LdkNodeEventBus,
10098
private val settingsStore: SettingsStore,
10199
private val currencyRepo: CurrencyRepo,
@@ -112,8 +110,14 @@ class AppViewModel @Inject constructor(
112110
var splashVisible by mutableStateOf(true)
113111
private set
114112

115-
var isGeoBlocked by mutableStateOf<Boolean?>(null)
116-
private set
113+
val isGeoBlocked = lightningRepo.lightningState.map { it.isGeoBlocked }
114+
.stateIn(
115+
viewModelScope,
116+
SharingStarted.WhileSubscribed(
117+
5000
118+
),
119+
false
120+
)
117121

118122
private val _sendUiState = MutableStateFlow(SendUiState())
119123
val sendUiState = _sendUiState.asStateFlow()
@@ -184,10 +188,12 @@ class AppViewModel @Inject constructor(
184188
delay(500)
185189
splashVisible = false
186190
}
191+
viewModelScope.launch {
192+
lightningRepo.updateGeoBlockState()
193+
}
187194

188195
observeLdkNodeEvents()
189196
observeSendEvents()
190-
checkGeoStatus()
191197
}
192198

193199
private fun observeLdkNodeEvents() {
@@ -271,16 +277,6 @@ class AppViewModel @Inject constructor(
271277
}
272278
}
273279

274-
private fun checkGeoStatus() {
275-
viewModelScope.launch {
276-
try {
277-
isGeoBlocked = coreService.checkGeoStatus()
278-
} catch (e: Throwable) {
279-
Logger.error("Failed to check geo status: ${e.message}", e, context = TAG)
280-
}
281-
}
282-
}
283-
284280
// region send
285281

286282
private fun observeSendEvents() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class LightningRepoTest : BaseUnitTest() {
6363

6464
@Before
6565
fun setUp() {
66-
wheneverBlocking { coreService.shouldBlockLightning() }.thenReturn(false)
66+
wheneverBlocking { coreService.checkGeoBlock() }.thenReturn(Pair(false, false))
6767
sut = LightningRepo(
6868
bgDispatcher = testDispatcher,
6969
lightningService = lightningService,

0 commit comments

Comments
 (0)