Skip to content

Commit e4a2271

Browse files
committed
feat: support restore from RN app
1 parent 5594163 commit e4a2271

File tree

11 files changed

+1031
-54
lines changed

11 files changed

+1031
-54
lines changed

app/src/main/java/to/bitkit/env/Env.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,18 @@ internal object Env {
187187
const val BITREFILL_APP = "Bitkit"
188188
const val BITREFILL_REF = "AL6dyZYt"
189189

190+
val rnBackupServerHost: String
191+
get() = when (network) {
192+
Network.BITCOIN -> "https://blocktank.synonym.to/backups-ldk"
193+
else -> "https://bitkit.stag0.blocktank.to/backups-ldk"
194+
}
195+
196+
val rnBackupServerPubKey: String
197+
get() = when (network) {
198+
Network.BITCOIN -> "0236efd76e37f96cf2dced9d52ff84c97e5b3d4a75e7d494807291971783f38377"
199+
else -> "02c03b8b8c1b5500b622646867d99bf91676fac0f38e2182c91a9ff0d053a21d6d"
200+
}
201+
190202
// endregion
191203
}
192204

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import kotlinx.coroutines.CoroutineScope
77
import kotlinx.coroutines.FlowPreview
88
import kotlinx.coroutines.Job
99
import kotlinx.coroutines.SupervisorJob
10+
import kotlinx.coroutines.async
11+
import kotlinx.coroutines.coroutineScope
1012
import kotlinx.coroutines.currentCoroutineContext
1113
import kotlinx.coroutines.delay
1214
import kotlinx.coroutines.flow.MutableStateFlow
@@ -21,6 +23,8 @@ import kotlinx.coroutines.flow.update
2123
import kotlinx.coroutines.isActive
2224
import kotlinx.coroutines.launch
2325
import kotlinx.coroutines.withContext
26+
import kotlinx.coroutines.withTimeout
27+
import kotlinx.serialization.Serializable
2428
import to.bitkit.R
2529
import to.bitkit.data.AppDb
2630
import to.bitkit.data.CacheStore
@@ -49,6 +53,7 @@ import java.util.concurrent.ConcurrentHashMap
4953
import javax.inject.Inject
5054
import javax.inject.Singleton
5155
import kotlin.time.Clock
56+
import kotlin.time.Duration.Companion.seconds
5257
import kotlin.time.ExperimentalTime
5358

5459
/**
@@ -536,6 +541,37 @@ class BackupRepo @Inject constructor(
536541
}
537542
}
538543

544+
suspend fun getLatestBackupTime(): ULong? = withContext(ioDispatcher) {
545+
runCatching {
546+
withTimeout(VSS_TIMESTAMP_TIMEOUT) {
547+
vssBackupClient.setup()
548+
coroutineScope {
549+
BackupCategory.entries
550+
.filter { it != BackupCategory.LIGHTNING_CONNECTIONS }
551+
.map { category -> async { getRemoteBackupTimestamp(category) } }
552+
.mapNotNull { it.await() }
553+
.filter { it > 0uL }
554+
.maxOrNull()
555+
}
556+
}
557+
}.onFailure { e ->
558+
Logger.warn("Failed to get VSS backup timestamp: $e", context = TAG)
559+
}.getOrNull()
560+
}
561+
562+
private suspend fun getRemoteBackupTimestamp(category: BackupCategory): ULong? {
563+
val item = vssBackupClient.getObject(category.name).getOrNull() ?: return null
564+
val data = item.value ?: return null
565+
566+
@Serializable
567+
data class BackupWithCreatedAt(val createdAt: Long? = null)
568+
569+
return runCatching {
570+
val millis = json.decodeFromString<BackupWithCreatedAt>(String(data)).createdAt ?: return@runCatching null
571+
(millis / 1000).toULong()
572+
}.getOrNull()
573+
}
574+
539575
fun scheduleFullBackup() {
540576
Logger.debug("Scheduling backups for all categories", context = TAG)
541577
BackupCategory.entries
@@ -578,5 +614,6 @@ class BackupRepo @Inject constructor(
578614
private const val FAILED_BACKUP_CHECK_TIME = 30 * 60 * 1000L // 30 minutes
579615
private const val FAILED_BACKUP_NOTIFICATION_INTERVAL = 10 * 60 * 1000L // 10 minutes
580616
private const val SYNC_STATUS_DEBOUNCE = 500L // 500ms debounce for sync status updates
617+
private val VSS_TIMESTAMP_TIMEOUT = 60.seconds
581618
}
582619
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import org.lightningdevkit.ldknode.Address
2929
import org.lightningdevkit.ldknode.BalanceDetails
3030
import org.lightningdevkit.ldknode.BestBlock
3131
import org.lightningdevkit.ldknode.ChannelConfig
32+
import org.lightningdevkit.ldknode.ChannelDataMigration
3233
import org.lightningdevkit.ldknode.ChannelDetails
3334
import org.lightningdevkit.ldknode.ClosureReason
3435
import org.lightningdevkit.ldknode.Event
@@ -167,7 +168,7 @@ class LightningRepo @Inject constructor(
167168
walletIndex: Int,
168169
customServerUrl: String? = null,
169170
customRgsServerUrl: String? = null,
170-
channelMigration: org.lightningdevkit.ldknode.ChannelDataMigration? = null,
171+
channelMigration: ChannelDataMigration? = null,
171172
) = withContext(bgDispatcher) {
172173
return@withContext try {
173174
val trustedPeers = getTrustedPeersFromBlocktank()
@@ -197,7 +198,7 @@ class LightningRepo @Inject constructor(
197198
customServerUrl: String? = null,
198199
customRgsServerUrl: String? = null,
199200
eventHandler: NodeEventHandler? = null,
200-
channelMigration: org.lightningdevkit.ldknode.ChannelDataMigration? = null,
201+
channelMigration: ChannelDataMigration? = null,
201202
): Result<Unit> = withContext(bgDispatcher) {
202203
if (_isRecoveryMode.value) {
203204
return@withContext Result.failure(RecoveryModeException())

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -586,19 +586,15 @@ class ActivityService(
586586
timestamp: ULong,
587587
): ConfirmationData {
588588
var isConfirmed = false
589-
var confirmedTimestamp: ULong? = null
589+
var blockTimestamp: ULong? = null
590590

591591
val status = kind.status
592592
if (status is ConfirmationStatus.Confirmed) {
593593
isConfirmed = true
594-
confirmedTimestamp = status.timestamp
594+
blockTimestamp = status.timestamp
595595
}
596596

597-
if (isConfirmed && confirmedTimestamp != null && confirmedTimestamp < timestamp) {
598-
confirmedTimestamp = timestamp
599-
}
600-
601-
return ConfirmationData(isConfirmed, confirmedTimestamp, timestamp)
597+
return ConfirmationData(isConfirmed, blockTimestamp, timestamp)
602598
}
603599

604600
private fun buildUpdatedOnchainActivity(
@@ -636,6 +632,14 @@ class ActivityService(
636632
channelId: String? = null,
637633
): OnchainActivity {
638634
val isTransfer = channelId != null
635+
val paymentTimestamp = confirmationData.timestamp
636+
val blockTimestamp = confirmationData.confirmedTimestamp
637+
638+
val activityTimestamp = if (blockTimestamp != null && blockTimestamp < paymentTimestamp) {
639+
blockTimestamp
640+
} else {
641+
paymentTimestamp
642+
}
639643

640644
return OnchainActivity.create(
641645
id = payment.id,
@@ -644,10 +648,10 @@ class ActivityService(
644648
value = payment.amountSats ?: 0u,
645649
fee = (payment.feePaidMsat ?: 0u) / 1000u,
646650
address = resolvedAddress ?: "Loading...",
647-
timestamp = confirmationData.timestamp,
651+
timestamp = activityTimestamp,
648652
confirmed = confirmationData.isConfirmed,
649653
isTransfer = isTransfer,
650-
confirmTimestamp = confirmationData.confirmedTimestamp,
654+
confirmTimestamp = blockTimestamp,
651655
channelId = channelId,
652656
seenAt = null,
653657
)

0 commit comments

Comments
 (0)