From 7d12b5a1dd8d502c6fe59639853ca40dd093d0af Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Apr 2026 11:54:21 +0000 Subject: [PATCH] fix: clear stale Arti Tor cache on each fresh init, retry on failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Arti data directory (/arti/) accumulates stale consensus and relay descriptors over time. When this cached data becomes invalid, TorClient::create_bootstrapped() either bootstraps with stale guards (leading to circuit failures) or fails outright. Since the native TorClient persists for the process lifetime, force-closing and reopening the app re-reads the same corrupted data. Only clearing all app data (which deletes the arti/ dir) would recover — matching the user report. - Clear arti/cache/ on each fresh initialization (new process) so Arti always downloads fresh consensus and relay descriptors - On initialize() failure, clear ALL arti data (state + cache) and retry once, recovering from corrupted state without requiring the user to manually clear app data https://claude.ai/code/session_01VFAypytKGzdmuoJAXrb72G --- .../amethyst/ui/tor/TorService.kt | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt index 04540157a..b74f56753 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt @@ -54,6 +54,34 @@ class TorService( private val _status = MutableStateFlow(TorServiceStatus.Off) val status: StateFlow = _status.asStateFlow() + private fun artiDataDir() = File(context.filesDir, "arti") + + /** + * Clears the Arti cache directory (consensus, relay descriptors) while + * preserving the state directory (guard selection). This forces a fresh + * consensus download on the next bootstrap, preventing stale cached data + * from causing circuit failures. + */ + private fun clearArtiCache() { + val cacheDir = File(artiDataDir(), "cache") + if (cacheDir.exists()) { + cacheDir.deleteRecursively() + Log.d("TorService") { "Cleared Arti cache directory" } + } + } + + /** + * Clears all Arti persistent data (state + cache). Used as a last resort + * when initialization fails, to recover from corrupted state. + */ + private fun clearAllArtiData() { + val dataDir = artiDataDir() + if (dataDir.exists()) { + dataDir.deleteRecursively() + Log.d("TorService") { "Cleared all Arti data" } + } + } + /** * Initialize the TorClient (once) and start the SOCKS proxy. * Must be called from a coroutine on [Dispatchers.IO]. @@ -90,12 +118,21 @@ class TorService( } } - val dataDir = File(context.filesDir, "arti").absolutePath + // Clear cached consensus/descriptors so Arti bootstraps with + // fresh network data, preventing stale guards/circuits. + clearArtiCache() + + val dataDir = artiDataDir().absolutePath Log.d("TorService") { "Initializing Arti with data dir: $dataDir" } - val initResult = ArtiNative.initialize(dataDir) + var initResult = ArtiNative.initialize(dataDir) if (initResult != 0) { - Log.e("TorService") { "Failed to initialize Arti: error $initResult" } + Log.e("TorService") { "Failed to initialize Arti: error $initResult, clearing data and retrying" } + clearAllArtiData() + initResult = ArtiNative.initialize(dataDir) + } + if (initResult != 0) { + Log.e("TorService") { "Failed to initialize Arti on retry: error $initResult" } initialized.set(false) _status.value = TorServiceStatus.Off return@withContext