From 5b1d74f237a3d8832021fdbc1ab8ef2fd1b9ee7e Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Thu, 2 Apr 2026 22:36:51 +0300 Subject: [PATCH] feat: live block streaming --- src/ExplorerWidget.cpp | 4 ++ src/pages/MainPage.cpp | 13 ++++++ src/pages/MainPage.h | 1 + src/services/IndexerService.h | 11 +++++- src/services/MockIndexerService.cpp | 61 ++++++++++++++++++++++++++++- src/services/MockIndexerService.h | 10 ++++- 6 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/ExplorerWidget.cpp b/src/ExplorerWidget.cpp index 91e002f..0d96a83 100644 --- a/src/ExplorerWidget.cpp +++ b/src/ExplorerWidget.cpp @@ -47,6 +47,10 @@ ExplorerWidget::ExplorerWidget(QWidget* parent) connect(m_navBar, &NavigationBar::homeClicked, this, &ExplorerWidget::navigateHome); connect(m_searchBar, &SearchBar::searchRequested, this, &ExplorerWidget::onSearch); + connect(m_indexer.get(), &IndexerService::newBlockAdded, this, [this](const Block& block) { + m_mainPage->onNewBlock(block); + }); + updateNavButtons(); } diff --git a/src/pages/MainPage.cpp b/src/pages/MainPage.cpp index 516eac9..e976f12 100644 --- a/src/pages/MainPage.cpp +++ b/src/pages/MainPage.cpp @@ -315,6 +315,19 @@ void MainPage::showSearchResults(const SearchResults& results) m_contentLayout->insertWidget(0, m_searchResultsWidget); } +void MainPage::onNewBlock(const Block& block) +{ + if (!m_blocksLayout) return; + + m_healthLabel->setText(QString("Chain height: %1").arg(block.blockId)); + + // Append to layout then move to top (index 0) + addBlockRow(m_blocksLayout, block); + auto* newRow = m_blocksLayout->itemAt(m_blocksLayout->count() - 1)->widget(); + m_blocksLayout->removeWidget(newRow); + m_blocksLayout->insertWidget(0, newRow); +} + void MainPage::clearSearchResults() { if (m_searchResultsWidget) { diff --git a/src/pages/MainPage.h b/src/pages/MainPage.h index 92504e7..38926d2 100644 --- a/src/pages/MainPage.h +++ b/src/pages/MainPage.h @@ -18,6 +18,7 @@ public: void refresh(); void showSearchResults(const SearchResults& results); void clearSearchResults(); + void onNewBlock(const Block& block); signals: void blockClicked(quint64 blockId); diff --git a/src/services/IndexerService.h b/src/services/IndexerService.h index ac27ba4..4e43325 100644 --- a/src/services/IndexerService.h +++ b/src/services/IndexerService.h @@ -4,6 +4,7 @@ #include "models/Transaction.h" #include "models/Account.h" +#include #include #include @@ -13,9 +14,12 @@ struct SearchResults { QVector accounts; }; -class IndexerService { +class IndexerService : public QObject { + Q_OBJECT + public: - virtual ~IndexerService() = default; + explicit IndexerService(QObject* parent = nullptr) : QObject(parent) {} + ~IndexerService() override = default; virtual std::optional getAccount(const QString& accountId) = 0; virtual std::optional getBlockById(quint64 blockId) = 0; @@ -25,4 +29,7 @@ public: virtual quint64 getLatestBlockId() = 0; virtual QVector getTransactionsByAccount(const QString& accountId, int offset, int limit) = 0; virtual SearchResults search(const QString& query) = 0; + +signals: + void newBlockAdded(Block block); }; diff --git a/src/services/MockIndexerService.cpp b/src/services/MockIndexerService.cpp index f4262cb..96b6d40 100644 --- a/src/services/MockIndexerService.cpp +++ b/src/services/MockIndexerService.cpp @@ -31,9 +31,68 @@ QString randomBase58String(int length) } // namespace -MockIndexerService::MockIndexerService() +MockIndexerService::MockIndexerService(QObject* parent) + : IndexerService(parent) { generateData(); + + connect(&m_blockTimer, &QTimer::timeout, this, &MockIndexerService::onGenerateBlock); + m_blockTimer.start(30000); // 30 seconds +} + +Block MockIndexerService::generateBlock(quint64 blockId, const QString& prevHash) +{ + auto* rng = QRandomGenerator::global(); + + Block block; + block.blockId = blockId; + block.prevBlockHash = prevHash; + block.hash = randomHash(); + block.timestamp = QDateTime::currentDateTimeUtc(); + block.signature = randomHexString(64); + block.bedrockParentId = randomHexString(32); + block.bedrockStatus = BedrockStatus::Pending; + + int txCount = rng->bounded(1, 6); + for (int t = 0; t < txCount; ++t) { + Transaction tx; + int typeRoll = rng->bounded(100); + if (typeRoll < 60) { + tx = generatePublicTransaction(); + } else if (typeRoll < 85) { + tx = generatePrivacyPreservingTransaction(); + } else { + tx = generateProgramDeploymentTransaction(); + } + block.transactions.append(tx); + m_transactionsByHash[tx.hash] = tx; + + for (const auto& accRef : tx.accounts) { + if (!m_accounts.contains(accRef.accountId)) { + Account acc; + acc.accountId = accRef.accountId; + acc.programOwner = randomBase58String(44); + acc.balance = QString::number(rng->bounded(0, 1000000)); + acc.nonce = accRef.nonce; + acc.dataSizeBytes = rng->bounded(0, 4096); + m_accounts[acc.accountId] = acc; + } + } + } + + return block; +} + +void MockIndexerService::onGenerateBlock() +{ + quint64 newId = m_blocks.isEmpty() ? 1 : m_blocks.last().blockId + 1; + QString prevHash = m_blocks.isEmpty() ? QString(64, '0') : m_blocks.last().hash; + + Block block = generateBlock(newId, prevHash); + m_blocks.append(block); + m_blocksByHash[block.hash] = block; + + emit newBlockAdded(block); } QString MockIndexerService::randomHash() diff --git a/src/services/MockIndexerService.h b/src/services/MockIndexerService.h index 9b6b1b2..a833ffe 100644 --- a/src/services/MockIndexerService.h +++ b/src/services/MockIndexerService.h @@ -3,10 +3,13 @@ #include "IndexerService.h" #include +#include class MockIndexerService : public IndexerService { + Q_OBJECT + public: - MockIndexerService(); + explicit MockIndexerService(QObject* parent = nullptr); std::optional getAccount(const QString& accountId) override; std::optional getBlockById(quint64 blockId) override; @@ -17,6 +20,9 @@ public: QVector getTransactionsByAccount(const QString& accountId, int offset, int limit) override; SearchResults search(const QString& query) override; +private slots: + void onGenerateBlock(); + private: void generateData(); QString randomHash(); @@ -24,9 +30,11 @@ private: Transaction generatePublicTransaction(); Transaction generatePrivacyPreservingTransaction(); Transaction generateProgramDeploymentTransaction(); + Block generateBlock(quint64 blockId, const QString& prevHash); QVector m_blocks; QMap m_blocksByHash; QMap m_transactionsByHash; QMap m_accounts; + QTimer m_blockTimer; };