feat(explorer): indexed block search with a bounded cache
The explorer block cache searched with a LIKE query that had to escape wildcards, grew without any limit, and retried a failed open on every frame, including a two-second busy wait on the UI thread. - Search now uses an instr() substring test on a lowercased needle, and only checks the height text when the query is all digits, since a non-numeric needle can never appear inside a decimal height. - The table is pruned to the highest 20000 blocks, once per open and periodically as blocks are stored, so the file stops growing at its high-water mark. - A failed open is negative-cached (30 s by default) instead of retried every frame, and the busy timeout drops to 250 ms. - When the cache database is unavailable or refuses a write, the block spills to a small bounded in-memory map that both the list and search read, so a spilled block never appears in one but not the other; the spill is cleared when the chain tip regresses. - A batch of blocks is written under one savepoint instead of one commit per block. Tests: case-insensitive hash and height search, the negative-cache and spill paths with tip-regression eviction, and batch store plus prune. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -45,9 +47,21 @@ public:
|
||||
// Fuzzy search over cached blocks: matches when the query is a substring of the height (as text)
|
||||
// or the block hash (case-insensitive). Returns newest-first, capped at `limit`.
|
||||
std::vector<ExplorerBlockSummary> searchBlocks(const std::string& query, int limit);
|
||||
// Always returns the DB result; a block the DB refuses (unopenable file, failed insert) is kept in a small
|
||||
// in-memory spill so the Explorer still shows it instead of refetching it forever.
|
||||
bool storeBlock(const ExplorerBlockSummary& block);
|
||||
// storeBlock for a batch inside one SAVEPOINT (one WAL commit instead of one per block). True if every block hit the DB.
|
||||
bool storeBlocks(const std::vector<ExplorerBlockSummary>& blocks);
|
||||
int cachedBlockCount();
|
||||
void clearBlocks();
|
||||
// Keep only the `maxRows` highest blocks (data-safe: the table is a re-fetchable RPC cache).
|
||||
void pruneTo(int maxRows);
|
||||
static constexpr int kMaxCachedBlocks = 20000;
|
||||
|
||||
// A failed open is not retried until this interval has passed (default 30 s). openAttempts() counts real attempts.
|
||||
void setOpenRetryInterval(std::chrono::milliseconds interval) { open_retry_interval_ = interval; }
|
||||
int openAttempts() const { return open_attempts_; }
|
||||
std::size_t spillCount() const { return spill_.size(); }
|
||||
|
||||
SavedTipValidation prepareValidation(int currentHeight, const std::string& currentBestHash);
|
||||
void applySavedTipValidation(const SavedTipValidation& validation,
|
||||
@@ -68,6 +82,9 @@ private:
|
||||
bool createSchema();
|
||||
void migrateLegacyJsonIfNeeded();
|
||||
void close();
|
||||
bool storeBlockRow(const ExplorerBlockSummary& block); // DB write only
|
||||
void spillBlock(const ExplorerBlockSummary& block);
|
||||
bool failOpen(const char* why);
|
||||
|
||||
sqlite3* db_ = nullptr;
|
||||
std::string database_path_;
|
||||
@@ -78,6 +95,19 @@ private:
|
||||
bool tip_known_ = false;
|
||||
int tip_height_ = 0;
|
||||
std::string tip_hash_;
|
||||
|
||||
// Failure latch for ensureOpen(): after a failed open nothing retries until retry_not_before_.
|
||||
std::chrono::steady_clock::time_point retry_not_before_{};
|
||||
std::chrono::milliseconds open_retry_interval_{30000};
|
||||
int open_attempts_ = 0;
|
||||
int stores_since_prune_ = 0;
|
||||
|
||||
// Blocks the DB refused, bounded and UI-thread only. Evicts the entry farthest from the last spilled height
|
||||
// (the Explorer fetches around the visible window). Cleared with clearBlocks() and when the chain tip regresses.
|
||||
static constexpr std::size_t kMaxSpillBlocks = 512;
|
||||
std::map<int, ExplorerBlockSummary> spill_;
|
||||
int spill_tip_height_ = 0;
|
||||
std::string spill_tip_hash_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
Reference in New Issue
Block a user