diff --git a/flake.lock b/flake.lock index f6f9f10..1d4fdc5 100644 --- a/flake.lock +++ b/flake.lock @@ -154,11 +154,11 @@ "logos-module-builder": "logos-module-builder_4" }, "locked": { - "lastModified": 1782982957, - "narHash": "sha256-L1W94vlMtKK5w6S9np2qFEY8tJ5M5g6Aryfrb3Bf4lo=", + "lastModified": 1782991756, + "narHash": "sha256-MhRCtwOctGTZ2+CMF93AAPV6nIO/X2uO4qcTxzLsgbw=", "ref": "erhant/bump-lez-and-fix-logging", - "rev": "69dbf45a57f94345d46ceca3c9bc563ce5fab0ae", - "revCount": 59, + "rev": "b2fde3c000d00c04da374b2c31719e29a237b2a4", + "revCount": 62, "type": "git", "url": "https://github.com/logos-blockchain/lez-indexer-module" }, diff --git a/src/lez_explorer_ui.rep b/src/lez_explorer_ui.rep index 84f3188..8f4aacd 100644 --- a/src/lez_explorer_ui.rep +++ b/src/lez_explorer_ui.rep @@ -33,6 +33,11 @@ class LezExplorerUi // refresh the feed. The backend writes the JSON to a file itself — the UI // never deals with paths. Returns true on success. SLOT(bool applyConfigJson(QString json)) + // Delete the indexer's RocksDB cache and restart from the saved config: the + // recovery path when the local store is stale against a different/reset chain + // (otherwise the indexer can sit at "caught up" on a mismatched store). + // Returns true on success. + SLOT(bool resetIndexerCache()) // Reload the latest page into recentBlocks and re-baseline the poller. SLOT(void refreshBlocks()) // Append the next older page of blocks to recentBlocks. diff --git a/src/lez_explorer_ui_backend.cpp b/src/lez_explorer_ui_backend.cpp index 53ea9c6..b5f0a1b 100644 --- a/src/lez_explorer_ui_backend.cpp +++ b/src/lez_explorer_ui_backend.cpp @@ -399,6 +399,24 @@ bool LezExplorerUiBackend::applyConfigJson(QString json) { return ok; } +bool LezExplorerUiBackend::resetIndexerCache() { + // reset_storage stops the indexer and deletes its RocksDB store; then we + // restart from the saved config so it re-indexes the current chain from + // scratch. The recovery path for a store left stale against a reset chain. + const qlonglong code = modules().lez_indexer_module.reset_storage(configFilePath()); + if (code != 0) { + emit error(QStringLiteral("Failed to delete the indexer cache (code %1).").arg(code)); + return false; + } + + // Store is empty now; re-baseline the poller and restart ingestion. + m_lastPolledTip = 0; + m_polledOnce = false; + const bool ok = startIndexerFromFile(); + refreshBlocks(); + return ok; +} + void LezExplorerUiBackend::refreshBlocks() { const QString json = modules().lez_indexer_module.getBlocks(QString(), QStringLiteral("10")); m_recentBlocks = parseBlockArrayJson(json); diff --git a/src/lez_explorer_ui_backend.h b/src/lez_explorer_ui_backend.h index d0c4aa9..66134fe 100644 --- a/src/lez_explorer_ui_backend.h +++ b/src/lez_explorer_ui_backend.h @@ -40,6 +40,7 @@ public: // .rep SLOTs. bool applyConfigJson(QString json) override; + bool resetIndexerCache() override; void refreshBlocks() override; void loadMoreBlocks() override; QVariantMap getBlockById(QString id) override; diff --git a/src/qml/pages/SettingsPage.qml b/src/qml/pages/SettingsPage.qml index 80f0daa..d987b69 100644 --- a/src/qml/pages/SettingsPage.qml +++ b/src/qml/pages/SettingsPage.qml @@ -11,6 +11,8 @@ Item { property var explorer readonly property var backend: explorer ? explorer.backend : null property bool statusIsError: false + // Armed by the first "Delete Cache" tap; a second tap actually wipes. + property bool confirmingReset: false Component.onCompleted: { // Seed the editor with the saved config, falling back to the template. @@ -83,6 +85,25 @@ Item { font.pixelSize: Theme.typography.secondaryText } + // Delete the indexer's RocksDB cache and re-sync (two-tap confirm). + Rectangle { + Layout.preferredHeight: 38 + Layout.preferredWidth: 150 + radius: Theme.spacing.radiusMedium + color: resetCacheHover.hovered ? Qt.lighter(Theme.palette.error, 1.1) : Theme.palette.error + + HoverHandler { id: resetCacheHover } + TapHandler { onTapped: page.resetCache() } + + LogosText { + anchors.centerIn: parent + text: page.confirmingReset ? "Confirm delete" : "Delete Cache" + color: Theme.palette.background + font.pixelSize: Theme.typography.secondaryText + font.weight: Theme.typography.weightBold + } + } + // Reset to the built-in template. Rectangle { Layout.preferredHeight: 38 @@ -93,7 +114,7 @@ Item { border.color: Theme.palette.borderSecondary HoverHandler { id: resetHover } - TapHandler { onTapped: if (page.backend) { editor.text = page.backend.defaultConfig; page.setStatus("", false); } } + TapHandler { onTapped: if (page.backend) { page.confirmingReset = false; editor.text = page.backend.defaultConfig; page.setStatus("", false); } } LogosText { anchors.centerIn: parent @@ -129,9 +150,36 @@ Item { statusLabel.text = message; } + // Delete the indexer's RocksDB cache and re-sync. First tap arms; a second + // tap confirms (it's destructive — wipes the local index). + function resetCache() { + if (!page.backend) + return; + if (!page.confirmingReset) { + page.confirmingReset = true; + page.setStatus("Click “Confirm delete” to wipe the local index and re-sync from scratch.", true); + return; + } + page.confirmingReset = false; + page.setStatus("Deleting cache…", false); + logos.watch(page.backend.resetIndexerCache(), + function (ok) { + if (ok) { + page.setStatus("Cache deleted. Indexer restarting…", false); + page.explorer.goHome(); + } + // On failure the backend emits error() (shown by onError above). + }, + function (err) { + page.setStatus("Error: " + err, true); + }); + } + function save() { if (!page.backend) return; + // A save cancels a pending cache-delete confirmation. + page.confirmingReset = false; // Validate locally first for an immediate, precise error. try { JSON.parse(editor.text);