diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index 8d0249a..f32ca99 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -125,8 +125,10 @@ void LEZWalletBackend::openIfPathsConfigured() if (err == WALLET_FFI_SUCCESS) { qDebug() << "LEZWalletBackend: wallet opened successfully"; setIsWalletOpen(true); - refreshAccounts(); - refreshBlockHeights(); + QJsonArray arr = m_logos->logos_execution_zone.list_accounts(); + m_accountModel->replaceFromJsonArray(arr); + fetchAndUpdateBlockHeights(); + startChunkedSync(); refreshSequencerAddr(); } else { qWarning() << "LEZWalletBackend: failed to open wallet, error" << err @@ -138,21 +140,66 @@ void LEZWalletBackend::refreshAccounts() { QJsonArray arr = m_logos->logos_execution_zone.list_accounts(); m_accountModel->replaceFromJsonArray(arr); - refreshBalances(); + fetchAndUpdateBlockHeights(); + if (!m_syncing) + startChunkedSync(); } void LEZWalletBackend::refreshBalances() { - refreshBlockHeights(); - syncToBlock(currentBlockHeight()); + fetchAndUpdateBlockHeights(); + if (!m_syncing) + startChunkedSync(); +} + +void LEZWalletBackend::startChunkedSync() +{ + m_syncTarget = static_cast(currentBlockHeight()); + if (m_syncTarget == 0 || static_cast(lastSyncedBlock()) >= m_syncTarget) { + updateBalances(); + return; + } + m_syncing = true; + syncNextChunk(); +} + +void LEZWalletBackend::syncNextChunk() +{ + const quint64 synced = static_cast(lastSyncedBlock()); + if (synced >= m_syncTarget) { + m_syncing = false; + fetchAndUpdateBlockHeights(); + updateBalances(); + return; + } + const quint64 next = qMin(synced + SYNC_CHUNK_SIZE, m_syncTarget); + m_logos->logos_execution_zone.sync_to_block(next); + // Only read lastSyncedBlock between chunks — avoids a sequencer network + // call (get_current_block_height) on every iteration. + const int lastVal = m_logos->logos_execution_zone.get_last_synced_block(); + if (lastSyncedBlock() != lastVal) + setLastSyncedBlock(lastVal); + QTimer::singleShot(0, this, &LEZWalletBackend::syncNextChunk); +} + +void LEZWalletBackend::updateBalances() +{ if (!m_accountModel) return; + bool anyFailed = false; for (int i = 0; i < m_accountModel->count(); ++i) { const QModelIndex idx = m_accountModel->index(i, 0); const QString addr = m_accountModel->data(idx, LEZWalletAccountModel::AddressRole).toString(); const bool isPub = m_accountModel->data(idx, LEZWalletAccountModel::IsPublicRole).toBool(); - m_accountModel->setBalanceByAddress(addr, getBalance(addr, isPub)); + const QString bal = getBalance(addr, isPub); + if (!bal.isEmpty()) + m_accountModel->setBalanceByAddress(addr, bal); + else + anyFailed = true; } - saveWallet(); + if (anyFailed) + QTimer::singleShot(3000, this, &LEZWalletBackend::updateBalances); + else + saveWallet(); } void LEZWalletBackend::fetchAndUpdateBlockHeights() @@ -165,16 +212,6 @@ void LEZWalletBackend::fetchAndUpdateBlockHeights() setCurrentBlockHeight(currentVal); } -void LEZWalletBackend::refreshBlockHeights() -{ - fetchAndUpdateBlockHeights(); - if (currentBlockHeight() > 0 - && lastSyncedBlock() < currentBlockHeight() - && syncToBlock(currentBlockHeight())) - { - fetchAndUpdateBlockHeights(); - } -} void LEZWalletBackend::refreshSequencerAddr() { @@ -331,7 +368,6 @@ bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QStrin persistStoragePath(localStoragePath); setIsWalletOpen(true); refreshAccounts(); - refreshBlockHeights(); refreshSequencerAddr(); return true; } diff --git a/src/LEZWalletBackend.h b/src/LEZWalletBackend.h index 2d20cc7..7706d8d 100644 --- a/src/LEZWalletBackend.h +++ b/src/LEZWalletBackend.h @@ -48,16 +48,25 @@ public slots: bool createNew(QString configPath, QString storagePath, QString password, QString sequencerAddr) override; void copyToClipboard(QString text) override; +private slots: + void syncNextChunk(); + private: void persistConfigPath(const QString& path); void persistStoragePath(const QString& path); void applySequencerAddrToConfig(const QString& configPath, const QString& sequencerAddr); - void refreshBlockHeights(); + void fetchAndUpdateBlockHeights(); + void startChunkedSync(); + + void updateBalances(); void refreshSequencerAddr(); void saveWallet(); - void fetchAndUpdateBlockHeights(); void openIfPathsConfigured(); + bool m_syncing = false; + quint64 m_syncTarget = 0; + static constexpr quint64 SYNC_CHUNK_SIZE = 100; + LEZWalletAccountModel* m_accountModel; LEZAccountFilterModel* m_filteredAccountModel; LEZAccountFilterModel* m_privateAccountModel; diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index ebf6152..efb7136 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -137,6 +137,8 @@ Rectangle { accountModel: root.accountModel publicAccountModel: root.publicAccountModel privateAccountModel: root.privateAccountModel + lastSyncedBlock: backend ? backend.lastSyncedBlock : 0 + currentBlockHeight: backend ? backend.currentBlockHeight : 0 onCreatePublicAccountRequested: { if (!backend) { console.warn("backend is null"); return } diff --git a/src/qml/views/AccountsPanel.qml b/src/qml/views/AccountsPanel.qml index f6ff14a..957bccb 100644 --- a/src/qml/views/AccountsPanel.qml +++ b/src/qml/views/AccountsPanel.qml @@ -13,6 +13,8 @@ Rectangle { // --- Public API: data in --- property var accountModel: null + property int lastSyncedBlock: 0 + property int currentBlockHeight: 0 // --- Public API: signals out --- signal createPublicAccountRequested() @@ -57,6 +59,34 @@ Rectangle { } } + // Sync progress + ColumnLayout { + Layout.fillWidth: true + spacing: Theme.spacing.xsmall + visible: root.currentBlockHeight > 0 && root.lastSyncedBlock < root.currentBlockHeight + + RowLayout { + Layout.fillWidth: true + LogosText { + text: qsTr("Syncing") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + Item { Layout.fillWidth: true } + LogosText { + text: root.lastSyncedBlock + " / " + root.currentBlockHeight + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + } + ProgressBar { + Layout.fillWidth: true + from: 0 + to: root.currentBlockHeight + value: root.lastSyncedBlock + } + } + // Empty state (when no real model and we don't show showcase) LogosText { Layout.fillWidth: true diff --git a/src/qml/views/DashboardView.qml b/src/qml/views/DashboardView.qml index 84ebb12..c3d70cf 100644 --- a/src/qml/views/DashboardView.qml +++ b/src/qml/views/DashboardView.qml @@ -15,6 +15,8 @@ Rectangle { property string transferResult: "" property bool transferResultIsError: false property bool transferPending: false + property int lastSyncedBlock: 0 + property int currentBlockHeight: 0 // --- Public API: output signals (parent connects and calls backend) --- signal createPublicAccountRequested() @@ -41,6 +43,8 @@ Rectangle { Layout.fillHeight: true accountModel: root.accountModel + lastSyncedBlock: root.lastSyncedBlock + currentBlockHeight: root.currentBlockHeight onCreatePublicAccountRequested: root.createPublicAccountRequested() onCreatePrivateAccountRequested: root.createPrivateAccountRequested()