Skip to content

Commit 14916f6

Browse files
piotr-iohkpwltr
authored andcommitted
Merge pull request #1005 from synonymdev/feat/probe-reset-scores
feat: add resetScores devtools command
1 parent cffac07 commit 14916f6

3 files changed

Lines changed: 66 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ suspend fun getData(): Result<Data> = withContext(Dispatchers.IO) {
178178
- ALWAYS check existing code patterns before implementing new features
179179
- USE existing extensions and utilities rather than creating new ones
180180
- ALWAYS use or create `Context` extension properties in `ext/Context.kt` instead of raw `context.getSystemService()` casts
181+
- NEVER use `System.currentTimeMillis()`, use time helpers from `ext/DateTime.kt` instead (e.g. `nowMillis()`, `Clock.nowMs()`) — they accept a `Clock` and are unit-testable
181182
- ALWAYS apply the YAGNI (You Ain't Gonna Need It) principle for new code
182183
- ALWAYS reuse existing constants
183184
- ALWAYS ensure a method exist before calling it

app/src/debug/java/to/bitkit/dev/DevToolsProvider.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ private sealed interface DevCommand {
6464
ProbeInvoice.METHOD -> ProbeInvoice.parse(arg)
6565
ProbeNode.METHOD -> ProbeNode.parse(arg)
6666
ProbeReadiness.METHOD -> ProbeReadiness
67+
ResetScores.METHOD -> ResetScores
6768
else -> null
6869
}
6970
}
@@ -172,6 +173,21 @@ private sealed interface DevCommand {
172173
override suspend fun execute(deps: DevToolsProvider.Dependencies): DevResult =
173174
DevResult.ProbeReadiness.from(deps.lightningRepo().probeReadiness())
174175
}
176+
177+
data object ResetScores : DevCommand {
178+
const val METHOD = "resetScores"
179+
180+
override suspend fun execute(deps: DevToolsProvider.Dependencies): DevResult {
181+
Logger.info("Resetting pathfinding scores via devtools", context = TAG)
182+
return deps.lightningRepo().resetPathfindingScores().fold(
183+
onSuccess = { DevResult.Ack(timestamp = it) },
184+
onFailure = {
185+
Logger.error("Failed to reset pathfinding scores", it, context = TAG)
186+
DevResult.Error(it.message)
187+
},
188+
)
189+
}
190+
}
175191
}
176192

177193
@Serializable
@@ -183,6 +199,8 @@ private sealed interface DevResult {
183199

184200
@Serializable data class Invoice(val bolt11: String) : DevResult
185201

202+
@Serializable data class Ack(val success: Boolean = true, val timestamp: Long? = null) : DevResult
203+
186204
@Serializable
187205
data class ProbeSuccess(
188206
val success: Boolean = true,
@@ -224,6 +242,7 @@ private sealed interface DevResult {
224242
val graphNodeCount: Int? = null,
225243
val graphChannelCount: Int? = null,
226244
val latestRgsSyncTimestamp: ULong? = null,
245+
val latestPathfindingScoresSyncTimestamp: ULong? = null,
227246
) : DevResult {
228247
companion object {
229248
fun from(readiness: NodeProbeReadiness) = ProbeReadiness(
@@ -241,6 +260,7 @@ private sealed interface DevResult {
241260
graphNodeCount = readiness.graphNodeCount,
242261
graphChannelCount = readiness.graphChannelCount,
243262
latestRgsSyncTimestamp = readiness.latestRgsSyncTimestamp,
263+
latestPathfindingScoresSyncTimestamp = readiness.latestPathfindingScoresSyncTimestamp,
244264
)
245265
}
246266
}

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import to.bitkit.di.BgDispatcher
6363
import to.bitkit.env.Defaults
6464
import to.bitkit.env.Env
6565
import to.bitkit.ext.getSatsPerVByteFor
66+
import to.bitkit.ext.nowMillis
6667
import to.bitkit.ext.nowTimestamp
6768
import to.bitkit.ext.toPeerDetailsList
6869
import to.bitkit.ext.totalNextOutboundHtlcLimitSats
@@ -101,6 +102,7 @@ import kotlin.time.Duration
101102
import kotlin.time.Duration.Companion.milliseconds
102103
import kotlin.time.Duration.Companion.minutes
103104
import kotlin.time.Duration.Companion.seconds
105+
import kotlin.time.ExperimentalTime
104106

105107
@Singleton
106108
@Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
@@ -1622,9 +1624,49 @@ class LightningRepo @Inject constructor(
16221624
graphNodeCount = graph?.nodeCount,
16231625
graphChannelCount = graph?.channelCount,
16241626
latestRgsSyncTimestamp = graph?.latestRgsSyncTimestamp,
1627+
latestPathfindingScoresSyncTimestamp = state.nodeStatus?.latestPathfindingScoresSyncTimestamp,
16251628
syncHealthy = state.isSyncHealthy,
16261629
)
16271630
}
1631+
1632+
/**
1633+
* Returns the device epoch seconds captured after the VSS deletes and before the node restart,
1634+
* so callers can require any scores sync timestamp to be strictly newer to prove a post-reset download.
1635+
*/
1636+
@OptIn(ExperimentalTime::class)
1637+
suspend fun resetPathfindingScores(walletIndex: Int = 0): Result<Long> = withContext(bgDispatcher) {
1638+
Logger.info("Resetting pathfinding scores", context = TAG)
1639+
1640+
waitForNodeToStop().onFailure { return@withContext Result.failure(it) }
1641+
stop().onFailure {
1642+
Logger.error("Failed to stop node during pathfinding scores reset", it, context = TAG)
1643+
return@withContext Result.failure(it)
1644+
}
1645+
1646+
runCatching {
1647+
val lifecycleState = _lightningState.value.nodeLifecycleState
1648+
check(lifecycleState == NodeLifecycleState.Stopped) {
1649+
"Node lifecycle changed to '$lifecycleState' during pathfinding scores reset"
1650+
}
1651+
vssBackupClientLdk.setup(walletIndex).getOrThrow()
1652+
vssBackupClientLdk.deleteObject(VSS_KEY_SCORER).getOrThrow()
1653+
vssBackupClientLdk.deleteObject(VSS_KEY_EXTERNAL_SCORES_CACHE).getOrThrow()
1654+
}.onFailure {
1655+
Logger.error("Failed to delete pathfinding scores from VSS", it, context = TAG)
1656+
start(walletIndex = walletIndex, shouldRetry = false).onFailure { startError ->
1657+
Logger.error("Failed to restart node after pathfinding scores reset failure", startError, context = TAG)
1658+
}
1659+
return@withContext Result.failure(it)
1660+
}
1661+
1662+
val resetAtSecs = nowMillis() / 1000
1663+
1664+
start(walletIndex = walletIndex, shouldRetry = false)
1665+
.map { resetAtSecs }
1666+
.onSuccess {
1667+
Logger.info("Pathfinding scores reset at '$resetAtSecs'", context = TAG)
1668+
}
1669+
}
16281670
// endregion
16291671

16301672
suspend fun restartNode(): Result<Unit> = withContext(bgDispatcher) {
@@ -1644,6 +1686,8 @@ class LightningRepo @Inject constructor(
16441686
companion object {
16451687
private const val TAG = "LightningRepo"
16461688
private const val LENGTH_CHANNEL_ID_PREVIEW = 10
1689+
private const val VSS_KEY_SCORER = "scorer"
1690+
private const val VSS_KEY_EXTERNAL_SCORES_CACHE = "external_pathfinding_scores_cache"
16471691
private const val MS_SYNC_LOOP_DEBOUNCE = 500L
16481692
private const val SYNC_RETRY_DELAY_MS = 15_000L
16491693
private val CHANNELS_USABLE_TIMEOUT = 15.seconds
@@ -1720,6 +1764,7 @@ data class ProbeReadiness(
17201764
val graphNodeCount: Int?,
17211765
val graphChannelCount: Int?,
17221766
val latestRgsSyncTimestamp: ULong?,
1767+
val latestPathfindingScoresSyncTimestamp: ULong?,
17231768
val syncHealthy: Boolean,
17241769
) {
17251770
val ready: Boolean

0 commit comments

Comments
 (0)