From df222b48440dda86d28d2beffe8ee9b9582e8294 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Wed, 2 Oct 2024 14:41:51 -0700 Subject: [PATCH 1/3] qml: Introduce UI Flow for Loading Snapshot - This introduce the UI flow to load a AssumeUTXO snapshot - It modifies the connection settings - Adds a SnapshotLoadSettings file, Icon, and modified progress bar. - Also it adds error page on snapshotloading failure --- qml/bitcoin_qml.qrc | 7 + qml/components/ConnectionSettings.qml | 31 +++ qml/components/SnapshotLoadSettings.qml | 269 ++++++++++++++++++++ qml/controls/GreenCheckIcon.qml | 11 + qml/controls/Header.qml | 2 + qml/controls/ProgressIndicator.qml | 3 +- qml/controls/Theme.qml | 3 + qml/pages/settings/SettingsConnection.qml | 12 + qml/pages/settings/SettingsSnapshotLoad.qml | 38 +++ qml/res/icons/circle-file.png | Bin 0 -> 1391 bytes qml/res/icons/circle-green-check.png | Bin 0 -> 1503 bytes qml/res/icons/circle-red-cross.png | Bin 0 -> 2406 bytes qml/res/icons/green-check.png | Bin 0 -> 788 bytes qml/res/src/circle-file.svg | 18 ++ qml/res/src/circle-green-check.svg | 18 ++ qml/res/src/green-check.svg | 9 + 16 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 qml/components/SnapshotLoadSettings.qml create mode 100644 qml/controls/GreenCheckIcon.qml create mode 100644 qml/pages/settings/SettingsSnapshotLoad.qml create mode 100644 qml/res/icons/circle-file.png create mode 100644 qml/res/icons/circle-green-check.png create mode 100644 qml/res/icons/circle-red-cross.png create mode 100644 qml/res/icons/green-check.png create mode 100644 qml/res/src/circle-file.svg create mode 100644 qml/res/src/circle-green-check.svg create mode 100644 qml/res/src/green-check.svg diff --git a/qml/bitcoin_qml.qrc b/qml/bitcoin_qml.qrc index 2a1a9c4432..dbdcf885e1 100644 --- a/qml/bitcoin_qml.qrc +++ b/qml/bitcoin_qml.qrc @@ -16,6 +16,7 @@ components/ProxySettings.qml components/StorageLocations.qml components/Separator.qml + components/SnapshotLoadSettings.qml components/StorageOptions.qml components/StorageSettings.qml components/ThemeSettings.qml @@ -29,6 +30,7 @@ controls/CoreTextField.qml controls/ExternalLink.qml controls/FocusBorder.qml + controls/GreenCheckIcon.qml controls/Header.qml controls/Icon.qml controls/IconButton.qml @@ -79,6 +81,7 @@ pages/settings/SettingsDeveloper.qml pages/settings/SettingsDisplay.qml pages/settings/SettingsProxy.qml + pages/settings/SettingsSnapshotLoad.qml pages/settings/SettingsStorage.qml pages/settings/SettingsTheme.qml pages/wallet/Activity.qml @@ -115,6 +118,9 @@ res/icons/check.png res/icons/copy.png res/icons/coinbase.png + res/icons/circle-file.png + res/icons/circle-green-check.png + res/icons/circle-red-cross.png res/icons/cross.png res/icons/ellipsis.png res/icons/error.png @@ -122,6 +128,7 @@ res/icons/flip-vertical.png res/icons/gear.png res/icons/gear-outline.png + res/icons/green-check.png res/icons/hidden.png res/icons/info.png res/icons/lock.png diff --git a/qml/components/ConnectionSettings.qml b/qml/components/ConnectionSettings.qml index 385cb5ab45..8fe7e1a130 100644 --- a/qml/components/ConnectionSettings.qml +++ b/qml/components/ConnectionSettings.qml @@ -10,7 +10,38 @@ import "../controls" ColumnLayout { id: root signal next + signal gotoSnapshot + property bool onboarding: false + property bool snapshotImportCompleted: onboarding ? false : chainModel.isSnapshotActive + property bool isIBDCompleted: nodeModel.isIBDCompleted spacing: 4 + Setting { + id: gotoSnapshot + visible: !snapshotImportCompleted && !root.isIBDCompleted + Layout.fillWidth: true + header: qsTr("Load snapshot") + description: qsTr("Instant use with background sync") + actionItem: Item { + width: 26 + height: 26 + CaretRightIcon { + anchors.centerIn: parent + visible: !snapshotImportCompleted + color: gotoSnapshot.stateColor + } + GreenCheckIcon { + anchors.centerIn: parent + visible: snapshotImportCompleted + color: Theme.color.transparent + size: 30 + } + } + onClicked: root.gotoSnapshot() + } + Separator { + visible: !snapshotImportCompleted && !root.isIBDCompleted + Layout.fillWidth: true + } Setting { Layout.fillWidth: true header: qsTr("Enable listening") diff --git a/qml/components/SnapshotLoadSettings.qml b/qml/components/SnapshotLoadSettings.qml new file mode 100644 index 0000000000..6f6a0fb0a8 --- /dev/null +++ b/qml/components/SnapshotLoadSettings.qml @@ -0,0 +1,269 @@ +// Copyright (c) 2023-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Dialogs + +import "../controls" + +ColumnLayout { + id: columnLayout + signal back + property bool snapshotLoading: nodeModel.snapshotLoading + property bool snapshotLoaded: nodeModel.isSnapshotLoaded + property bool snapshotImportCompleted: onboarding ? false : chainModel.isSnapshotActive + property bool onboarding: false + property bool snapshotVerified: onboarding ? false : chainModel.isSnapshotActive + property string snapshotFileName: "" + property var snapshotInfo: (snapshotVerified || snapshotLoaded) ? chainModel.getSnapshotInfo() : ({}) + property string selectedFile: "" + property bool headersSynced: nodeModel.headersSynced + property bool snapshotError: nodeModel.snapshotError + + width: Math.min(parent.width, 450) + anchors.horizontalCenter: parent.horizontalCenter + + StackLayout { + id: settingsStack + currentIndex: onboarding ? 0 : snapshotLoaded ? 2 : snapshotVerified ? 2 : snapshotLoading ? 1 : snapshotError ? 3 : 0 + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: Math.min(parent.width, 450) + + Image { + Layout.alignment: Qt.AlignCenter + source: "image://images/circle-file" + + sourceSize.width: 200 + sourceSize.height: 200 + } + + Header { + Layout.fillWidth: true + Layout.topMargin: 20 + headerBold: true + header: qsTr("Load snapshot") + descriptionBold: false + descriptionColor: Theme.color.neutral6 + descriptionSize: 17 + descriptionLineHeight: 1.1 + description: qsTr("You can start using the application more quickly by loading a recent transaction snapshot." + + " It will be automatically verified in the background.") + } + + CoreText { + Layout.fillWidth: true + Layout.topMargin: 20 + color: Theme.color.neutral6 + font.pixelSize: 17 + visible: !headersSynced && !onboarding + text: !headersSynced + ? qsTr("Please wait for headers to sync before loading a snapshot.") + : qsTr("") + wrap: true + wrapMode: Text.WordWrap + } + + ContinueButton { + Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin) + Layout.topMargin: 40 + Layout.leftMargin: 20 + Layout.rightMargin: Layout.leftMargin + Layout.bottomMargin: 20 + Layout.alignment: Qt.AlignCenter + text: qsTr("Choose snapshot file") + enabled: headersSynced || onboarding + onClicked: fileDialog.open() + } + + FileDialog { + id: fileDialog + currentFolder: StandardPaths.writableLocation(StandardPaths.HomeLocation) + nameFilters: ["Snapshot files (*.dat)", "All files (*)"] + onAccepted: { + snapshotFileName = selectedFile + console.log("snapshotFileName", snapshotFileName) + if (!onboarding) { + nodeModel.snapshotLoadThread(snapshotFileName) + } else { + nodeModel.setSnapshotFilePath(snapshotFileName) + back() + } + } + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: Math.min(parent.width, 450) + + Image { + Layout.alignment: Qt.AlignCenter + source: "image://images/circle-file" + + sourceSize.width: 200 + sourceSize.height: 200 + } + + Header { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 20 + Layout.rightMargin: 20 + header: qsTr("Loading Snapshot") + description: qsTr("This might take a while...") + } + + ProgressIndicator { + id: progressIndicator + Layout.topMargin: 20 + width: 200 + height: 20 + progress: nodeModel.snapshotProgress + Layout.alignment: Qt.AlignCenter + progressColor: Theme.color.blue + } + } + + ColumnLayout { + id: loadedSnapshotColumn + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: Math.min(parent.width, 450) + + Image { + Layout.alignment: Qt.AlignCenter + source: "image://images/circle-green-check" + + sourceSize.width: 60 + sourceSize.height: 60 + } + + Header { + Layout.fillWidth: true + Layout.topMargin: 20 + headerBold: true + header: qsTr("Snapshot Loaded") + descriptionBold: false + descriptionColor: Theme.color.neutral6 + descriptionSize: 17 + descriptionLineHeight: 1.1 + description: snapshotInfo && snapshotInfo["date"] ? + qsTr("It contains unspent transactions up to %1. Next, transactions will be verified and newer transactions downloaded.").arg(snapshotInfo["date"]) : + qsTr("It contains transactions up to DEBUG. Newer transactions still need to be downloaded." + + " The data will be verified in the background.") + } + + ContinueButton { + Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin) + Layout.topMargin: 40 + Layout.alignment: Qt.AlignCenter + text: qsTr("Done") + onClicked: { + chainModel.isSnapshotActiveChanged() + back() + } + } + + Setting { + id: viewDetails + Layout.alignment: Qt.AlignCenter + header: qsTr("View details") + actionItem: CaretRightIcon { + id: caretIcon + color: viewDetails.stateColor + rotation: viewDetails.expanded ? 90 : 0 + Behavior on rotation { NumberAnimation { duration: 200 } } + } + + property bool expanded: false + + onClicked: { + expanded = !expanded + } + } + + ColumnLayout { + id: detailsContent + visible: viewDetails.expanded + Layout.preferredWidth: Math.min(300, parent.width - 2 * Layout.leftMargin) + Layout.alignment: Qt.AlignCenter + Layout.leftMargin: 80 + Layout.rightMargin: 80 + Layout.topMargin: 10 + spacing: 10 + // TODO: make sure the block height number aligns right + RowLayout { + CoreText { + text: qsTr("Block Height:") + Layout.alignment: Qt.AlignLeft + font.pixelSize: 14 + } + CoreText { + text: snapshotInfo && snapshotInfo["height"] ? + snapshotInfo["height"] : qsTr("DEBUG") + Layout.alignment: Qt.AlignRight + font.pixelSize: 14 + } + } + Separator { Layout.fillWidth: true } + ColumnLayout { + Layout.fillWidth: true + spacing: 5 + CoreText { + text: qsTr("Hash:") + font.pixelSize: 14 + } + CoreText { + text: snapshotInfo && snapshotInfo["hashSerializedFirstHalf"] ? + snapshotInfo["hashSerializedFirstHalf"] + "\n" + snapshotInfo["hashSerializedSecondHalf"] : + qsTr("DEBUG") + Layout.fillWidth: true + font.pixelSize: 14 + textFormat: Text.PlainText + } + } + } + } + + ColumnLayout { + id: snapshotErrorColumn + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: Math.min(parent.width, 450) + + Image { + Layout.alignment: Qt.AlignCenter + source: "image://images/circle-red-cross" + + sourceSize.width: 60 + sourceSize.height: 60 + } + + Header { + Layout.fillWidth: true + Layout.topMargin: 20 + headerBold: true + header: qsTr("Snapshot Could Not Be Verified") + descriptionBold: false + descriptionColor: Theme.color.neutral6 + descriptionSize: 17 + descriptionLineHeight: 1.1 + description: qsTr("There was a problem with the transactions in the snapshot. Please try a different file.") + } + + ContinueButton { + Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin) + Layout.topMargin: 40 + Layout.alignment: Qt.AlignCenter + text: qsTr("OK") + onClicked: { + nodeModel.setSnapshotError(false) + back() + } + } + } + } +} diff --git a/qml/controls/GreenCheckIcon.qml b/qml/controls/GreenCheckIcon.qml new file mode 100644 index 0000000000..02977857b2 --- /dev/null +++ b/qml/controls/GreenCheckIcon.qml @@ -0,0 +1,11 @@ +// Copyright (c) 2023 - present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +Icon { + source: "image://images/green-check" + size: 30 +} diff --git a/qml/controls/Header.qml b/qml/controls/Header.qml index f3c4c0c3e3..ece49234d2 100644 --- a/qml/controls/Header.qml +++ b/qml/controls/Header.qml @@ -25,6 +25,7 @@ ColumnLayout { property int subtextSize: 15 property color subtextColor: Theme.color.neutral9 property bool wrap: true + property real descriptionLineHeight: 1 spacing: 0 Loader { @@ -60,6 +61,7 @@ ColumnLayout { text: root.description horizontalAlignment: root.center ? Text.AlignHCenter : Text.AlignLeft wrapMode: wrap ? Text.WordWrap : Text.NoWrap + lineHeight: root.descriptionLineHeight Behavior on color { ColorAnimation { duration: 150 } diff --git a/qml/controls/ProgressIndicator.qml b/qml/controls/ProgressIndicator.qml index 117a4baebb..9d6d62d329 100644 --- a/qml/controls/ProgressIndicator.qml +++ b/qml/controls/ProgressIndicator.qml @@ -7,6 +7,7 @@ import QtQuick.Controls 2.15 Control { property real progress: 0 + property color progressColor: Theme.color.orange Behavior on progress { NumberAnimation { easing.type: Easing.Bezier @@ -26,7 +27,7 @@ Control { width: contentItem.width height: contentItem.height radius: contentItem.radius - color: Theme.color.orange + color: progressColor } } } diff --git a/qml/controls/Theme.qml b/qml/controls/Theme.qml index f57e152cbd..3c7621c2b5 100644 --- a/qml/controls/Theme.qml +++ b/qml/controls/Theme.qml @@ -27,6 +27,7 @@ Control { required property color blue required property color amber required property color purple + required property color transparent required property color neutral0 required property color neutral1 required property color neutral2 @@ -59,6 +60,7 @@ Control { blue: "#3CA3DE" amber: "#C9B500" purple: "#C075DC" + transparent: "#00000000" neutral0: "#000000" neutral1: "#1A1A1A" neutral2: "#2D2D2D" @@ -91,6 +93,7 @@ Control { blue: "#2D9CDB" amber: "#C9B500" purple: "#BB6BD9" + transparent: "#00000000" neutral0: "#FFFFFF" neutral1: "#F8F8F8" neutral2: "#F4F4F4" diff --git a/qml/pages/settings/SettingsConnection.qml b/qml/pages/settings/SettingsConnection.qml index d180fa2ff2..726a62c7a1 100644 --- a/qml/pages/settings/SettingsConnection.qml +++ b/qml/pages/settings/SettingsConnection.qml @@ -12,6 +12,7 @@ Page { id: root signal back property bool onboarding: false + property bool snapshotImportCompleted: onboarding ? false : chainModel.isSnapshotActive background: null PageStack { id: stack @@ -31,6 +32,9 @@ Page { detailActive: true detailItem: ConnectionSettings { onNext: stack.push(proxySettings) + onGotoSnapshot: stack.push(loadSnapshotSettings) + snapshotImportCompleted: root.snapshotImportCompleted + onboarding: root.onboarding } states: [ @@ -87,5 +91,13 @@ Page { onBack: stack.pop() } } + Component { + id: loadSnapshotSettings + SettingsSnapshotLoad { + onboarding: root.onboarding + snapshotImportCompleted: root.snapshotImportCompleted + onBack: stack.pop() + } + } } } diff --git a/qml/pages/settings/SettingsSnapshotLoad.qml b/qml/pages/settings/SettingsSnapshotLoad.qml new file mode 100644 index 0000000000..cd308ede94 --- /dev/null +++ b/qml/pages/settings/SettingsSnapshotLoad.qml @@ -0,0 +1,38 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import "../../controls" +import "../../components" + +Page { + signal back + property bool onboarding: false + property bool snapshotImportCompleted: onboarding ? false : chainModel.isSnapshotActive + + id: root + + background: null + implicitWidth: 450 + leftPadding: 20 + rightPadding: 20 + topPadding: 30 + + header: NavigationBar2 { + leftItem: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: root.back() + } + } + SnapshotLoadSettings { + width: Math.min(parent.width, 450) + anchors.horizontalCenter: parent.horizontalCenter + onboarding: root.onboarding + snapshotImportCompleted: root.snapshotImportCompleted + onBack: root.back() + } +} diff --git a/qml/res/icons/circle-file.png b/qml/res/icons/circle-file.png new file mode 100644 index 0000000000000000000000000000000000000000..14a776e6d5ba9ff0d34ecd7178169004c6b00147 GIT binary patch literal 1391 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVB7>k44ofvPP)Tsw@I14-?iy0WW zg+Z8+Vb&Z8paQi3pAc7|q|M@cqX>Tp@YL`63=FBHk|4ie21X76DK!HN2VZkeSt}-K zH&+I3T`pleAs#1YJr8$zGiw`DITvMizK*;9Z-nwT-MkjkckwFg?eP1-QI8~Io*Ku! z@J@UknDXv)`Uk&yjY|*JJ^9%zAKdHjy3=T6C?cvanP_TY%D})};_2cTQZZ-i&DY){ zfg%haim%;t-@WVV)}?3Mv+THnG<6nhaB*=}PrSDLMLqW~Ay3DY+71VSCZ!2o9KkN9 z<)*FsaqH_=>$7Lx^?ly){oKsEFKcGaef?+7&As2}Ri9Tl!R*bLl@V=v=rL1dv`pdc z(y+j*JuITlo=12cgL72(y%A2?5v010TUTg?_hrXr3qJ6j@pz=x|D>^W$BUCMH-`Up zWqe)Oc;sc^?l)CJ;t|WwOtx(Z`Lz0*v9|Lo6i z4ByU2{N5>g7fUILv%jg>86T-74qsIGp>&&$UVX3o`Gp+?-au z?7O^|MJ!YQnS%d|B;PU%t3OTpeO&28S#f(z`{!+kEq8WoEMZ)8w&1q)_BrWFKU}q5 z6;EZ`WcqJL_SV|2#IW5;>Y|3p4a@C5&VKvg%0=I+!EdUTH!pI0&ahB--z(K?{#gpE z%a*Y+a@bZC^|l{Bl8|NE`^fJt)5Df|@#U^6NtZ1S?fjl1;P+@x%o|zP9UrFg9mh#b>>0D%T*ONgzqi;6=snu%VcEuMBXU*Z!ig%ov>1~fh|k3Vb+q2 z1@o3TU1V^y?G+NXk>RrlFxY31)yIXa~XB&kLJLU-MTCA@Zt2#cf&|h49 z&VfUBCLQ&CD8%sT@}13{k0wd5JpOr&{oj+@&Hg){pLMPNaJwtP%I6ZR9W5<6w4@IxH z>2$N+evs6<@1-$Y``V-W?uNPZ|MT6m(AQg*S@?gW|ND2$ztgYR2xSQ@k;q$;HutOC zcb2I>Y|%!nRe|BY1^-OvS?|F9Ll+*h~G&19)-=ttob zae5n8<(!JW7#L!DYW=P1Cb!*!Ui&`$dVKFw@cQfW{~2_6dDogo-qQl+b_P#ZKbLh* G2~7YIypH<- literal 0 HcmV?d00001 diff --git a/qml/res/icons/circle-green-check.png b/qml/res/icons/circle-green-check.png new file mode 100644 index 0000000000000000000000000000000000000000..25bb20e00fb07a7e6e6d80cf2474744f35e14d2a GIT binary patch literal 1503 zcmc&!{WsHl9RJSDmT;$WH{_u(Z1#XqtgvWXY#uHNbN9q1hH2_jx|z6C=a$n=r7T@! zPT7-ZDb=~`94YInRGd!54T(JVgyw$zeE)!Z&inm&zu%wN`}Kak&*z8F$tV`h+|Cu7{Ebu7OvlxT&}VO)6QFhE;OiRXh-KQ*ykT(>&Dzf>r?b_%w;IDiGp= zIoRN);@~v`clCPdr4bP|DOm7EokG$GaVoxd^8t4y*F()EH3_jw4%qLd6?v%m;7qbs zk#S;w=joce0p2L&#dJoF17Vc#}yrOkDu*!yS-5MfRQXU zlndLV-wg=n-f$bg@JBPVzh4^s8uj)iE3(5NuUirQAYZ*^@@?O%oE0%|v~GvDmz6ci zMy6-J1^{RQBQzv7ud21OKLZtQRjOww7|g*JEc0OnGcifI_a-daDkL<-Ka1Ugv@FJr zLeV$_ya-X_j)1{nf2xcv*&UVzo;~Q;@YbQBePUncd!J+D0e=VwDbTK`F$_v^^Ze>KWZ$Senic=Dc!njuchFV$2#Rns49e2y9U)b!lJZZC zMA?n%wZrDHb3fL=%0AGF^MMTb!(X#Z_%jj*O5vf}`gnm6-=!2SVN@n|>a3lP?95*P za5`_j7c^F6^8^tW9&+LmWN7W+edyd75d_MJrxbS9!OA}ECd%UO^q7e;xqZ+EL%NZl zA@>oq;e8+Q47L(MB3>tF0sa>BH;|`?6|$+RRVp!QzZJ*i!Xx@y$fUHwBv!F_cE?Q# z7A9WaauSaGRs&5oktbB#v5hhpDoR$Zub!&itniC{azfJ?xs|+Z!|VS=E0~(UvdB$F z-*|s*JkFWlzL@rxlW71G#YbmSimoh|6ed{){jJN0o|d;fR4oVlmhnf9LVm`NCf(|_ zSUT;}CGVI!lNDVWEt~EpL@)owE7~85A5o@b??3mnD>}-_f%@l#bDG4%#)Vx-4*B=t zhW6WbV|Z3Oe16ADWASp#=L7f#k!}FeqYyg{z7pQ`W*buS7oA20evwkUTLOQYcDA-V zhu-yqw)x&n2)cJ0Uu-$Y6tV34+l z5K`Jb^Uj;qh4Z;OpK9;0uGQ3%D%<%y9A)jvpqVCR$W`y3l|Z?5cCzsuAz0F9t#1S1 ztyImnw#n%@K{yNL-1?y3WN&tJ+6a2#it7N|$9m(12YWK_R>Ei5rJdg@o<$@%e|+=I zYeIOrzTe+Ce?q(eQayKC~1-RZOtA@ zi0}9wV2!3nOgKnX8a(ZkyndTwPB(NI7e=5L&)D0SAUPjZJ5iTxBN+_haYKjI2ktS$ ZS2(F+W_HU!{3!Sz0E{qJsEW!h`WFKci5vg` literal 0 HcmV?d00001 diff --git a/qml/res/icons/circle-red-cross.png b/qml/res/icons/circle-red-cross.png new file mode 100644 index 0000000000000000000000000000000000000000..9a7aad3a461be47ece60d019fcdf7a73ff6443cf GIT binary patch literal 2406 zcmV-s37PhZP)tm@JYkv&|~M!Kl#F-2+-l zcINxKTLL;TPr7@6TlEzVVE4UWpL^~(eICSOA&fr^qh>+ZcQ0#N>w=;jUJ&B_jIQ_C zLJT-U4B5JV4fxP2^v$f9bY*Ti2wgUv718JibCoDgqqag5HR#7?}MQP5c zYTcqNN1I5J2Q-7gqadvh3mE~%*V&SrjYA~{ zhua_m1XK#6?g1mjA?&PO6(^Aw%`gv5(*|JsW3o#|}TU-b1}lzrPx2e$DzcsL1xYAJ}k#ksi}zOkUw z9rolS10jZ?&Wg!N%gM~ z$mp3H0NUPWOFoP&2CZmq39$e!mGZ4*KY6K=&9Z=Qd zb|TTRLg0d`9sq&ep--$zfGyl>N>bZu{l|2Rfv_i5Er5%AnQ0_0dCv`_#`bbm0*m8X zvTWFrG!I>J3!gtM`C6LxaVC|zW2I8}+-n9!qG!@{K@Zl%{=R*?!O``zK|Vn&`d(&w z`dcfR{0fF?5)=BU=!c-HQ<<5WJ7yyBqkAnpkRaFKK;`|NOg7T~+Xo3eALPwVNR7Mj zsi{BhRO$?)tcX5peCa#gegRnel4vs)w?|UjlYm7N|R=_U~EN(qW4+wlf z5-EvJ5d`4>-UoR6n z+$#4FDv!V`%&MgcbHNVT|Mf}yXO05u>0fE@g-#R-47J$`up0|a+r#>v$P7H~_$Q&ZC*m*ca zpc6kU#7+zW-XRhdGiD<@FRz^#4m>l)VLn{QnOAx3ygYVdC}7E%s$O6$xF@tYa1p+3 zNxt(Y!V=ScsITgXhhC^nVL*~U5;Qc<2+W5@a>)y=Rj2f8sgxVB9f`Lp#YudEq7(WmWaf=hv^VO*-F6 zZz~A7)7vwVX_(%Ah73|5PN`)+oq62zkSJY1STF02yw~L zw8MGHTRBiPt;Jz4sw(Qlu}PQ&4`VP}PCS*Q=7PxMppBDNls335e9g6;No4W$xg}LS z@QU4+Z?MFZrJi^(s7JX<9u9 zEUk-2R+i&H3l!*?umev4gC+l`Ek$Vp%IllJ_l0nt4?12M#CCA=#vF+E6PC}8o8@7OCA5DQgzQH>A)5y(It;-{5P501p+JEy@(g&h52+K z-aT7PCsOBiFUfN2Khx876(?^si4<)ZmhG?+Py45HHJdmS?i#R`iZt3OW>}KcWce3nZddm)@~lF2s$CC?`?j+@g)>Y%mmRMD2s;(=#{xadgI5nGn)L13`tt&j)< zlU6!i9r}y5m*EiQEzMW*!!XObp2AgiQmIBD9P(BV`iXS59aUwT2s?g6tJ@KKObamx zbhCZQk|ebh?b`-1*kYKih8c9m#92NYjW)=d)@Lfp(T6iLe48A;#BDT4A_%NoOsAU{ zBG8DNF$|yy1lC5Lyw&63{Cv%0zZENA4E`a3e!tl9s=wv){V{?uCWNxs51%i(nPv3Tp*EOq z!BTwoGYH%)339N~+mz*oM{{%gm}AF^Q5HS2ZZF@T!(d8s#_LGl$#PSZr|HyGV_1a! Y2LWH6SR=G#9{>OV07*qoM6N<$g5~&p6951J literal 0 HcmV?d00001 diff --git a/qml/res/icons/green-check.png b/qml/res/icons/green-check.png new file mode 100644 index 0000000000000000000000000000000000000000..acce5e1a21539646b08354584cf7a08688613dc9 GIT binary patch literal 788 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX3?zBp#Z3TGjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkQ1EkrPlzj!R$rGu71VusRsPAT5Ti1VP5A%+|E{NJfLxQ!nQwl4{Pp*@ z>&~+E507+QS^*SHINWvd-Cd1!i9q&?Z|{KQng@q^uB=Kr(lh7QHm&tZiATB*y||>m zG0kXG#X_$t zR1`ep{QsZv5zq$@ffxu50P&6gAHZxJM9R#Vmsz$mE-%}E92oeqo-U3d6?1wg-}h#6 z6gd8zJ9c?oP*z;4MpT&F*9+MKBKtxw$lvCFp{T`C)y=`Ayy#F@P~gc=R;t@Rzx$T= zzWu`Y)bwYY&CPdPGZ(cqb{LvnDo*~U#Xn*9#`WD6c`P1NmUzpG`+qs1zw>CJP*j7E zYvkoalkW<6iDb{P_$%wxc`S=O)Q@;bvVc--Hyl55YFNq<(UtjkvIfmH0b{ypdE2uD-F|ZGxftH+D|9_}Y@73EaR~ OWbkzLb6Mw<&;$U4l7p@Q literal 0 HcmV?d00001 diff --git a/qml/res/src/circle-file.svg b/qml/res/src/circle-file.svg new file mode 100644 index 0000000000..d8af3949d8 --- /dev/null +++ b/qml/res/src/circle-file.svg @@ -0,0 +1,18 @@ + + circle-file-svg + + + + + + + + + + + + + \ No newline at end of file diff --git a/qml/res/src/circle-green-check.svg b/qml/res/src/circle-green-check.svg new file mode 100644 index 0000000000..d56c175fd4 --- /dev/null +++ b/qml/res/src/circle-green-check.svg @@ -0,0 +1,18 @@ + + Circle arrow up-svg + + + + + + + + + + + + + \ No newline at end of file diff --git a/qml/res/src/green-check.svg b/qml/res/src/green-check.svg new file mode 100644 index 0000000000..95f7c80ead --- /dev/null +++ b/qml/res/src/green-check.svg @@ -0,0 +1,9 @@ + + green-check-svg + + + + \ No newline at end of file From 933bd4627f0abeb81d750d17b26309884c7e2ea7 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Tue, 29 Jul 2025 12:27:02 -0700 Subject: [PATCH 2/3] qml: Implement snapshot loading functionality - Added snapshot loading capabilities to NodeModel, including progress tracking and error handling. - Introduced SnapshotQml class for processing snapshot files. - Enhanced ChainModel with methods to retrieve snapshot information and check if a snapshot is active. - Updated QML properties to reflect snapshot loading state and progress. - Included necessary includes for snapshot handling in relevant files. --- bitcoin | 2 +- qml/bitcoin.cpp | 1 + qml/models/chainmodel.cpp | 39 ++++++++++++++++ qml/models/chainmodel.h | 6 ++- qml/models/nodemodel.cpp | 93 ++++++++++++++++++++++++++++++++++++++ qml/models/nodemodel.h | 43 +++++++++++++++++- qml/models/snapshotqml.cpp | 47 +++++++++++++++++++ qml/models/snapshotqml.h | 26 +++++++++++ 8 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 qml/models/snapshotqml.cpp create mode 100644 qml/models/snapshotqml.h diff --git a/bitcoin b/bitcoin index 8ffbd7b778..0746cc67eb 160000 --- a/bitcoin +++ b/bitcoin @@ -1 +1 @@ -Subproject commit 8ffbd7b778600aa1e824027f1e675929a4240856 +Subproject commit 0746cc67eb84402b26fb381e5c9c9e1605c7f2ab diff --git a/qml/bitcoin.cpp b/qml/bitcoin.cpp index 513e3c8b07..4b4b8461e0 100644 --- a/qml/bitcoin.cpp +++ b/qml/bitcoin.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/qml/models/chainmodel.cpp b/qml/models/chainmodel.cpp index ce3a6b12ae..4e56066898 100644 --- a/qml/models/chainmodel.cpp +++ b/qml/models/chainmodel.cpp @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include using interfaces::FoundBlock; @@ -106,3 +109,39 @@ void ChainModel::setCurrentTimeRatio() Q_EMIT timeRatioListChanged(); } + +// TODO: Change this once a better solution has been found. +// Using hardcoded snapshot info to display in SnapshotSettings.qml +QVariantMap ChainModel::getSnapshotInfo() { + QVariantMap snapshot_info; + + std::vector available_heights = Params().GetAvailableSnapshotHeights(); + if (!available_heights.empty()) { + // Get the highest height snapshot (last element in the vector) + const int height = available_heights.back(); + std::optional maybe_snapshot = Params().AssumeutxoForHeight(height); + + if (maybe_snapshot.has_value()) { + const auto& latest_snapshot = maybe_snapshot.value(); + const auto& hash_serialized = latest_snapshot.hash_serialized; + + // Get block time using the interfaces pattern + uint256 block_hash{m_chain.getBlockHash(height)}; + int64_t block_time; + m_chain.findBlock(block_hash, FoundBlock().time(block_time)); + + QString fullHash = QString::fromStdString(hash_serialized.ToString()); + + int midPoint = fullHash.length() / 2; + QString firstHalf = fullHash.left(midPoint); + QString secondHalf = fullHash.mid(midPoint); + + snapshot_info["height"] = height; + snapshot_info["hashSerializedFirstHalf"] = firstHalf; + snapshot_info["hashSerializedSecondHalf"] = secondHalf; + snapshot_info["date"] = QDateTime::fromSecsSinceEpoch(block_time).toString("MMMM d yyyy"); + } + } + + return snapshot_info; +} diff --git a/qml/models/chainmodel.h b/qml/models/chainmodel.h index 9318510eda..6a5124be7f 100644 --- a/qml/models/chainmodel.h +++ b/qml/models/chainmodel.h @@ -27,6 +27,7 @@ class ChainModel : public QObject Q_PROPERTY(quint64 assumedBlockchainSize READ assumedBlockchainSize CONSTANT) Q_PROPERTY(quint64 assumedChainstateSize READ assumedChainstateSize CONSTANT) Q_PROPERTY(QVariantList timeRatioList READ timeRatioList NOTIFY timeRatioListChanged) + Q_PROPERTY(bool isSnapshotActive READ isSnapshotActive NOTIFY isSnapshotActiveChanged) public: explicit ChainModel(interfaces::Chain& chain); @@ -36,11 +37,13 @@ class ChainModel : public QObject quint64 assumedBlockchainSize() const { return m_assumed_blockchain_size; }; quint64 assumedChainstateSize() const { return m_assumed_chainstate_size; }; QVariantList timeRatioList() const { return m_time_ratio_list; }; - + bool isSnapshotActive() const { return m_chain.hasAssumedValidChain(); }; int timestampAtMeridian(); void setCurrentTimeRatio(); + Q_INVOKABLE QVariantMap getSnapshotInfo(); + public Q_SLOTS: void setTimeRatioList(int new_time); void setTimeRatioListInitial(); @@ -48,6 +51,7 @@ public Q_SLOTS: Q_SIGNALS: void timeRatioListChanged(); void currentNetworkNameChanged(); + void isSnapshotActiveChanged(); private: QString m_current_network_name; diff --git a/qml/models/nodemodel.cpp b/qml/models/nodemodel.cpp index be6fc4adba..293258b4e6 100644 --- a/qml/models/nodemodel.cpp +++ b/qml/models/nodemodel.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include @@ -14,14 +15,19 @@ #include #include +#include #include #include +#include +#include +#include NodeModel::NodeModel(interfaces::Node& node) : m_node{node} { ConnectToBlockTipSignal(); ConnectToNumConnectionsChangedSignal(); + ConnectToSnapshotLoadProgressSignal(); } void NodeModel::setBlockTipHeight(int new_height) @@ -80,6 +86,14 @@ void NodeModel::setVerificationProgress(double new_progress) if (new_progress != m_verification_progress) { setRemainingSyncTime(new_progress); + if (new_progress >= 0.00014) { + setHeadersSynced(true); + } + + if (new_progress >= 0.999) { + setIsIBDCompleted(true); + } + m_verification_progress = new_progress; Q_EMIT verificationProgressChanged(); } @@ -190,3 +204,82 @@ QString NodeModel::defaultProxyAddress() { return QString::fromStdString(std::string(DEFAULT_PROXY_HOST) + ":" + util::ToString(DEFAULT_PROXY_PORT)); } + +void NodeModel::ConnectToSnapshotLoadProgressSignal() +{ + assert(!m_handler_snapshot_load_progress); + + m_handler_snapshot_load_progress = m_node.handleSnapshotLoadProgress( + [this](double progress) { + setSnapshotProgress(progress); + }); +} + +void NodeModel::snapshotLoadThread(QString path_file) { + m_snapshot_loading = true; + Q_EMIT snapshotLoadingChanged(); + + path_file = QUrl(path_file).toLocalFile(); + + QThread* snapshot_thread = QThread::create([this, path_file]() { + SnapshotQml loader(m_node, path_file); + bool result = loader.processPath(); + if (!result) { + m_snapshot_loading = false; + Q_EMIT snapshotLoadingChanged(); + m_snapshot_error = true; + Q_EMIT snapshotErrorChanged(); + } else { + m_snapshot_loaded = true; + Q_EMIT snapshotLoaded(result); + Q_EMIT snapshotLoadingChanged(); + } + }); + + connect(snapshot_thread, &QThread::finished, snapshot_thread, &QThread::deleteLater); + + snapshot_thread->start(); +} + +void NodeModel::setSnapshotProgress(double new_progress) { + if (new_progress != m_snapshot_progress) { + m_snapshot_progress = new_progress; + Q_EMIT snapshotProgressChanged(); + } +} + +void NodeModel::setHeadersSynced(bool new_synced) { + if (new_synced != m_headers_synced) { + m_headers_synced = new_synced; + Q_EMIT headersSyncedChanged(); + checkAndLoadSnapshot(); + } +} + +void NodeModel::setIsIBDCompleted(bool new_completed) { + if (new_completed != m_is_ibd_completed) { + m_is_ibd_completed = new_completed; + Q_EMIT isIBDCompletedChanged(); + } +} + +void NodeModel::setSnapshotFilePath(const QString& new_path) { + if (new_path != m_snapshot_file_path) { + m_snapshot_file_path = new_path; + Q_EMIT snapshotFilePathChanged(); + } +} + +void NodeModel::checkAndLoadSnapshot() +{ + if (m_headers_synced && !m_snapshot_file_path.isEmpty()) { + snapshotLoadThread(m_snapshot_file_path); + } +} + +void NodeModel::setSnapshotError(bool new_error) { + if (new_error != m_snapshot_error) { + m_snapshot_error = new_error; + Q_EMIT snapshotErrorChanged(); + } +} diff --git a/qml/models/nodemodel.h b/qml/models/nodemodel.h index 993da098a5..00f45569db 100644 --- a/qml/models/nodemodel.h +++ b/qml/models/nodemodel.h @@ -37,6 +37,13 @@ class NodeModel : public QObject Q_PROPERTY(double verificationProgress READ verificationProgress NOTIFY verificationProgressChanged) Q_PROPERTY(bool pause READ pause WRITE setPause NOTIFY pauseChanged) Q_PROPERTY(bool faulted READ errorState WRITE setErrorState NOTIFY errorStateChanged) + Q_PROPERTY(double snapshotProgress READ snapshotProgress WRITE setSnapshotProgress NOTIFY snapshotProgressChanged) + Q_PROPERTY(bool snapshotLoading READ snapshotLoading NOTIFY snapshotLoadingChanged) + Q_PROPERTY(bool isSnapshotLoaded READ isSnapshotLoaded NOTIFY snapshotLoaded) + Q_PROPERTY(bool headersSynced READ headersSynced WRITE setHeadersSynced NOTIFY headersSyncedChanged) + Q_PROPERTY(bool isIBDCompleted READ isIBDCompleted WRITE setIsIBDCompleted NOTIFY isIBDCompletedChanged) + Q_PROPERTY(QString snapshotFilePath READ snapshotFilePath WRITE setSnapshotFilePath NOTIFY snapshotFilePathChanged) + Q_PROPERTY(bool snapshotError READ snapshotError WRITE setSnapshotError NOTIFY snapshotErrorChanged) public: explicit NodeModel(interfaces::Node& node); @@ -55,6 +62,18 @@ class NodeModel : public QObject void setPause(bool new_pause); bool errorState() const { return m_faulted; } void setErrorState(bool new_error); + bool isSnapshotLoaded() const { return m_snapshot_loaded; } + double snapshotProgress() const { return m_snapshot_progress; } + void setSnapshotProgress(double new_progress); + bool snapshotLoading() const { return m_snapshot_loading; } + bool headersSynced() const { return m_headers_synced; } + void setHeadersSynced(bool new_synced); + bool isIBDCompleted() const { return m_is_ibd_completed; } + void setIsIBDCompleted(bool new_completed); + QString snapshotFilePath() const { return m_snapshot_file_path; } + Q_INVOKABLE void setSnapshotFilePath(const QString& new_path); + bool snapshotError() const { return m_snapshot_error; } + Q_INVOKABLE void setSnapshotError(bool new_error); Q_INVOKABLE float getTotalBytesReceived() const { return (float)m_node.getTotalBytesRecv(); } Q_INVOKABLE float getTotalBytesSent() const { return (float)m_node.getTotalBytesSent(); } @@ -62,6 +81,9 @@ class NodeModel : public QObject Q_INVOKABLE void startNodeInitializionThread(); Q_INVOKABLE void requestShutdown(); + Q_INVOKABLE void snapshotLoadThread(QString path_file); + Q_INVOKABLE void checkAndLoadSnapshot(); + void startShutdownPolling(); void stopShutdownPolling(); @@ -83,6 +105,15 @@ public Q_SLOTS: void setTimeRatioList(int new_time); void setTimeRatioListInitial(); + void initializationFinished(); + void snapshotLoaded(bool result); + void snapshotProgressChanged(); + void snapshotLoadingChanged(); + void showProgress(const QString& title, int progress); + void headersSyncedChanged(); + void isIBDCompletedChanged(); + void snapshotFilePathChanged(); + void snapshotErrorChanged(); protected: void timerEvent(QTimerEvent* event) override; @@ -96,17 +127,25 @@ public Q_SLOTS: double m_verification_progress{0.0}; bool m_pause{false}; bool m_faulted{false}; - + double m_snapshot_progress{0.0}; int m_shutdown_polling_timer_id{0}; + int m_snapshot_timer_id{0}; + bool m_snapshot_loading{false}; + bool m_snapshot_loaded{false}; + bool m_headers_synced{false}; + bool m_is_ibd_completed{false}; + QString m_snapshot_file_path; + bool m_snapshot_error{false}; QVector> m_block_process_time; interfaces::Node& m_node; std::unique_ptr m_handler_notify_block_tip; std::unique_ptr m_handler_notify_num_peers_changed; - + std::unique_ptr m_handler_snapshot_load_progress; void ConnectToBlockTipSignal(); void ConnectToNumConnectionsChangedSignal(); + void ConnectToSnapshotLoadProgressSignal(); }; #endif // BITCOIN_QML_MODELS_NODEMODEL_H diff --git a/qml/models/snapshotqml.cpp b/qml/models/snapshotqml.cpp new file mode 100644 index 0000000000..c56e4e9a91 --- /dev/null +++ b/qml/models/snapshotqml.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2024 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SnapshotQml::SnapshotQml(interfaces::Node& node, QString path) + : m_node(node), m_path(path) {} + +bool SnapshotQml::processPath() +{ + ChainstateManager& chainman = *m_node.context()->chainman; + const fs::path path = fs::u8path(m_path.toStdString()); + if (!fs::exists(path)) { + return false; + } + + FILE* snapshot_file{fsbridge::fopen(path, "rb")}; + AutoFile afile{snapshot_file}; + if (afile.IsNull()) { + return false; + } + + node::SnapshotMetadata metadata{chainman.GetParams().MessageStart()}; + try { + afile >> metadata; + } catch (const std::exception& e) { + return false; + } + + bool result = m_node.loadSnapshot(afile, metadata, false); + if (!result) { + return false; + } + return true; +} \ No newline at end of file diff --git a/qml/models/snapshotqml.h b/qml/models/snapshotqml.h new file mode 100644 index 0000000000..75e4758d7e --- /dev/null +++ b/qml/models/snapshotqml.h @@ -0,0 +1,26 @@ +// Copyright (c) 2024 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QML_MODELS_SNAPSHOTQML_H +#define BITCOIN_QML_MODELS_SNAPSHOTQML_H + +#include +#include + +#include + +class SnapshotQml : public QObject +{ + Q_OBJECT +public: + SnapshotQml(interfaces::Node& node, QString path); + + bool processPath(); + +private: + interfaces::Node& m_node; + QString m_path; +}; + +#endif // BITCOIN_QML_MODELS_SNAPSHOTQML_H From 050fa0ed3d4584e8518a1f3f00aee0dac96bddc8 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Wed, 30 Jul 2025 09:25:22 -0700 Subject: [PATCH 3/3] patch: Enhance snapshot loading with progress notifications - Implemented a new method `snapshotLoadProgress(double progress)` in the notifications interface to report loading progress of UTXO snapshots. - Updated `ChainstateManager::PopulateAndValidateSnapshot` to calculate and send progress updates during snapshot loading. - Introduced a new signal `SnapshotLoadProgress` in the UI interface for handling progress notifications. - Added necessary handlers in the node interface to connect the new progress signal. - Included a patch file for clean CI environments to ensure consistent submodule state. --- CMakeLists.txt | 24 +++ bitcoin | 2 +- patch/0746cc67-snapshot-load-progress.patch | 192 ++++++++++++++++++++ 3 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 patch/0746cc67-snapshot-load-progress.patch diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bd9bb326f..79ca76afc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,30 @@ set(BUILD_WALLET_TOOL OFF) set(BUILD_GUI ON) set(ENABLE_WALLET ON) +# Always reset and apply the patch for clean CI environments +set(SUBMODULE_PATCH_FILE "${CMAKE_SOURCE_DIR}/patch/0746cc67-snapshot-load-progress.patch") + +if(EXISTS "${SUBMODULE_PATCH_FILE}") + message(STATUS "Resetting bitcoin submodule to clean state...") + execute_process( + COMMAND git reset --hard HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/bitcoin + ) + + message(STATUS "Applying patch to bitcoin submodule...") + execute_process( + COMMAND git apply "${SUBMODULE_PATCH_FILE}" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/bitcoin + RESULT_VARIABLE PATCH_RESULT + OUTPUT_VARIABLE PATCH_OUTPUT + ERROR_VARIABLE PATCH_ERROR + ) + + if(NOT PATCH_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to apply submodule patch:\n${PATCH_ERROR}") + endif() +endif() + # Bitcoin Core codebase # Builds libraries: univalue, core_interface, bitcoin_node, bitcoin_wallet add_subdirectory(bitcoin) diff --git a/bitcoin b/bitcoin index 0746cc67eb..8ffbd7b778 160000 --- a/bitcoin +++ b/bitcoin @@ -1 +1 @@ -Subproject commit 0746cc67eb84402b26fb381e5c9c9e1605c7f2ab +Subproject commit 8ffbd7b778600aa1e824027f1e675929a4240856 diff --git a/patch/0746cc67-snapshot-load-progress.patch b/patch/0746cc67-snapshot-load-progress.patch new file mode 100644 index 0000000000..a22c17ae22 --- /dev/null +++ b/patch/0746cc67-snapshot-load-progress.patch @@ -0,0 +1,192 @@ +From 0746cc67eb84402b26fb381e5c9c9e1605c7f2ab Mon Sep 17 00:00:00 2001 +From: D33r-Gee +Date: Tue, 29 Jul 2025 11:11:16 -0700 +Subject: [PATCH] cpp: Implement snapshot loading progress notifications + +- Added a new method `snapshotLoadProgress(double progress)` to the notifications interface to report the progress of loading UTXO snapshots. +- Updated the `ChainstateManager::PopulateAndValidateSnapshot` function to calculate and send progress updates during the snapshot loading process. +- Introduced a new signal `SnapshotLoadProgress` in the UI interface to handle progress notifications. +- Implemented the necessary handlers in the node interface to connect the new progress signal. + +This enhancement improves user feedback during lengthy snapshot loading operations. +--- + src/interfaces/node.h | 9 +++++++++ + src/kernel/notifications_interface.h | 1 + + src/node/interface_ui.cpp | 3 +++ + src/node/interface_ui.h | 3 +++ + src/node/interfaces.cpp | 10 ++++++++++ + src/node/kernel_notifications.cpp | 5 +++++ + src/node/kernel_notifications.h | 2 ++ + src/validation.cpp | 4 ++++ + 8 files changed, 37 insertions(+) + +diff --git a/src/interfaces/node.h b/src/interfaces/node.h +index 78a186c5d9..0ff267e484 100644 +--- a/src/interfaces/node.h ++++ b/src/interfaces/node.h +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -204,6 +205,10 @@ public: + //! List rpc commands. + virtual std::vector listRpcCommands() = 0; + ++ ++ //! Load UTXO Snapshot. ++ virtual bool loadSnapshot(AutoFile& afile, const node::SnapshotMetadata& metadata, bool in_memory) = 0; ++ + //! Get unspent output associated with a transaction. + virtual std::optional getUnspentOutput(const COutPoint& output) = 0; + +@@ -233,6 +238,10 @@ public: + using ShowProgressFn = std::function; + virtual std::unique_ptr handleShowProgress(ShowProgressFn fn) = 0; + ++ //! Register handler for snapshot load progress. ++ using SnapshotLoadProgressFn = std::function; ++ virtual std::unique_ptr handleSnapshotLoadProgress(SnapshotLoadProgressFn fn) = 0; ++ + //! Register handler for wallet loader constructed messages. + using InitWalletFn = std::function; + virtual std::unique_ptr handleInitWallet(InitWalletFn fn) = 0; +diff --git a/src/kernel/notifications_interface.h b/src/kernel/notifications_interface.h +index 3e97e3b45e..e6a5a1dd3f 100644 +--- a/src/kernel/notifications_interface.h ++++ b/src/kernel/notifications_interface.h +@@ -40,6 +40,7 @@ public: + [[nodiscard]] virtual InterruptResult blockTip(SynchronizationState state, CBlockIndex& index, double verification_progress) { return {}; } + virtual void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) {} + virtual void progress(const bilingual_str& title, int progress_percent, bool resume_possible) {} ++ virtual void snapshotLoadProgress(double progress) {} + virtual void warningSet(Warning id, const bilingual_str& message) {} + virtual void warningUnset(Warning id) {} + +diff --git a/src/node/interface_ui.cpp b/src/node/interface_ui.cpp +index 273e51974e..3d6a705db7 100644 +--- a/src/node/interface_ui.cpp ++++ b/src/node/interface_ui.cpp +@@ -23,6 +23,7 @@ struct UISignals { + boost::signals2::signal NotifyNetworkActiveChanged; + boost::signals2::signal NotifyAlertChanged; + boost::signals2::signal ShowProgress; ++ boost::signals2::signal SnapshotLoadProgress; + boost::signals2::signal NotifyBlockTip; + boost::signals2::signal NotifyHeaderTip; + boost::signals2::signal BannedListChanged; +@@ -46,6 +47,7 @@ ADD_SIGNALS_IMPL_WRAPPER(ShowProgress); + ADD_SIGNALS_IMPL_WRAPPER(NotifyBlockTip); + ADD_SIGNALS_IMPL_WRAPPER(NotifyHeaderTip); + ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged); ++ADD_SIGNALS_IMPL_WRAPPER(SnapshotLoadProgress); + + bool CClientUIInterface::ThreadSafeMessageBox(const bilingual_str& message, const std::string& caption, unsigned int style) { return g_ui_signals.ThreadSafeMessageBox(message, caption, style).value_or(false);} + bool CClientUIInterface::ThreadSafeQuestion(const bilingual_str& message, const std::string& non_interactive_message, const std::string& caption, unsigned int style) { return g_ui_signals.ThreadSafeQuestion(message, non_interactive_message, caption, style).value_or(false);} +@@ -55,6 +57,7 @@ void CClientUIInterface::NotifyNumConnectionsChanged(int newNumConnections) { re + void CClientUIInterface::NotifyNetworkActiveChanged(bool networkActive) { return g_ui_signals.NotifyNetworkActiveChanged(networkActive); } + void CClientUIInterface::NotifyAlertChanged() { return g_ui_signals.NotifyAlertChanged(); } + void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, bool resume_possible) { return g_ui_signals.ShowProgress(title, nProgress, resume_possible); } ++void CClientUIInterface::SnapshotLoadProgress(double progress) { return g_ui_signals.SnapshotLoadProgress(progress); } + void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex& block, double verification_progress) { return g_ui_signals.NotifyBlockTip(s, block, verification_progress); } + void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, int64_t height, int64_t timestamp, bool presync) { return g_ui_signals.NotifyHeaderTip(s, height, timestamp, presync); } + void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); } +diff --git a/src/node/interface_ui.h b/src/node/interface_ui.h +index 7732cf4797..6f25870c49 100644 +--- a/src/node/interface_ui.h ++++ b/src/node/interface_ui.h +@@ -102,6 +102,9 @@ public: + */ + ADD_SIGNALS_DECL_WRAPPER(ShowProgress, void, const std::string& title, int nProgress, bool resume_possible); + ++ /** Snapshot load progress. */ ++ ADD_SIGNALS_DECL_WRAPPER(SnapshotLoadProgress, void, double progress); ++ + /** New block has been accepted */ + ADD_SIGNALS_DECL_WRAPPER(NotifyBlockTip, void, SynchronizationState, const CBlockIndex& block, double verification_progress); + +diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp +index 0485ff48bd..f02e1d28cd 100644 +--- a/src/node/interfaces.cpp ++++ b/src/node/interfaces.cpp +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -361,6 +362,11 @@ public: + LOCK(::cs_main); + return chainman().ActiveChainstate().CoinsTip().GetCoin(output); + } ++ bool loadSnapshot(AutoFile& coins_file, const SnapshotMetadata& metadata, bool in_memory) override ++ { ++ auto activation_result{chainman().ActivateSnapshot(coins_file, metadata, in_memory)}; ++ return activation_result.has_value(); ++ } + TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) override + { + return BroadcastTransaction(*m_context, std::move(tx), err_string, max_tx_fee, /*relay=*/ true, /*wait_callback=*/ false); +@@ -385,6 +391,10 @@ public: + { + return MakeSignalHandler(::uiInterface.ShowProgress_connect(fn)); + } ++ std::unique_ptr handleSnapshotLoadProgress(SnapshotLoadProgressFn fn) override ++ { ++ return MakeSignalHandler(::uiInterface.SnapshotLoadProgress_connect(fn)); ++ } + std::unique_ptr handleInitWallet(InitWalletFn fn) override + { + return MakeSignalHandler(::uiInterface.InitWallet_connect(fn)); +diff --git a/src/node/kernel_notifications.cpp b/src/node/kernel_notifications.cpp +index 4a6f8444da..a1ae75ecc4 100644 +--- a/src/node/kernel_notifications.cpp ++++ b/src/node/kernel_notifications.cpp +@@ -77,6 +77,11 @@ void KernelNotifications::progress(const bilingual_str& title, int progress_perc + uiInterface.ShowProgress(title.translated, progress_percent, resume_possible); + } + ++void KernelNotifications::snapshotLoadProgress(double progress) ++{ ++ uiInterface.SnapshotLoadProgress(progress); ++} ++ + void KernelNotifications::warningSet(kernel::Warning id, const bilingual_str& message) + { + if (m_warnings.Set(id, message)) { +diff --git a/src/node/kernel_notifications.h b/src/node/kernel_notifications.h +index 10ee3e18d7..d873d34feb 100644 +--- a/src/node/kernel_notifications.h ++++ b/src/node/kernel_notifications.h +@@ -41,6 +41,8 @@ public: + + void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override; + ++ void snapshotLoadProgress(double progress) override; ++ + void warningSet(kernel::Warning id, const bilingual_str& message) override; + + void warningUnset(kernel::Warning id) override; +diff --git a/src/validation.cpp b/src/validation.cpp +index 3cfcd2728c..aa7cdf1c69 100644 +--- a/src/validation.cpp ++++ b/src/validation.cpp +@@ -5959,6 +5959,10 @@ util::Result ChainstateManager::PopulateAndValidateSnapshot( + --coins_left; + ++coins_processed; + ++ // Show Snapshot Loading progress ++ double progress = static_cast(coins_processed) / static_cast(coins_count); ++ GetNotifications().snapshotLoadProgress(progress); ++ + if (coins_processed % 1000000 == 0) { + LogPrintf("[snapshot] %d coins loaded (%.2f%%, %.2f MB)\n", + coins_processed, +-- +2.34.1 +