Compare commits

...
5 Commits
Author SHA1 Message Date
jwinterm 93d13a6568 v5.0.3: harden embedded-node restart handling + version bump
On top of Lucas's #119 (--db-sync-mode safe):
- DaemonManager: age out the crash-retry budget (m_restarts) so legit node
  toggles/restarts don't trip "maximum retries exceeded" after ~4 spawns over a
  session; the cap now only bites a genuine rapid crash-loop.
- DaemonManager: clear the m_stopping latch on start(), so the crash auto-restart
  keeps working after the node has been toggled off then on (it was a one-shot
  before).
- bump version 5.0.2 -> 5.0.3.
2026-06-27 13:10:54 -04:00
Lucas 64326119eb Updated DaemonManager to ensure new blocks are
committed immediately by disabling LMDB batch write mode.
2026-06-26 21:57:42 +10:00
jwinterm e3ba9c7bad v5.0.2: hide hardware-restore option + version bump
- wizard: hide the hardware-device wallet option (wownero has no Ledger/Trezor
  support; inherited Feather cruft).
- bump version 5.0.1 -> 5.0.2.

v5.0.2 also rebuilds the Linux AppImage in an ubuntu:24.04 container (glibc 2.39)
so it runs on Ubuntu 24.04 / Mint / Debian — v5.0.1's AppImage was built on
glibc 2.43 and would not start elsewhere. No wallet-code change: wallets created
on pre-table-fix builds carry a stale restore height (blank balance); the fix is
to re-restore from seed.
2026-06-25 09:19:19 -04:00
jwinterm 6cd7e8bad9 fix: regenerate stale restore-height table (fixes vanishing balance)
restore_heights_wownero_mainnet.txt was last updated 2023-12-15 (h587048).
For wallets created after that, RestoreHeightLookup::dateToHeight extrapolates
forward at 288 blk/day; 2.5y past the last anchor overshoots the chain tip, so
the computed restore height lands ABOVE the current height. The wallet then
skips output-scan on every real block => received funds are never recorded,
while the tx pool still shows them: coins appear pending, then vanish.

Same bug class as 8686e9bf (which fixed the block-time CONSTANT from monero's
120s/720-per-day to wownero's 300s/288); here the constant was right but the
TABLE went stale. Regenerated with contrib/generate-restore-heights/heights.py
to h849000 (2026-06-19). Verified dateToHeight < tip for all dates 2019..today.
2026-06-23 21:25:18 -04:00
jwinterm 0d5d745311 v5.0.1: Windows QtQuick crash fix + node/Tor lifecycle overhaul
Windows crash chain (exposed by the embedded-node default):
- main.cpp: force the QtQuick software renderer. The Mining tab's QQuickWidget
  drove the default RHI/GL path and faulted (0xc0000005) on broken-GL hosts
  (VirtualBox VMSVGA), crashing the app and orphaning the embedded node.
  Real hardware was unaffected.
- ChildProcessGuard + DaemonManager/TorManager: kernel-level die-with-parent
  (Windows Job Object KILL_ON_JOB_CLOSE / Linux PR_SET_PDEATHSIG) so the
  embedded wownerod and bundled tor never survive a wowlet exit or crash.
- DaemonManager: verify-before-adopt via get_info (nettype/height) instead of
  trusting a bare open RPC port, so a stale/orphan node is never adopted.

Node/Tor lifecycle + remote-node connectivity:
- config: torPrivacyLevel default -> allTorExceptNode. NEVER sync blocks over
  Tor; the wallet's node connection is always clearnet. Tor is only for tx
  broadcast, so remote nodes work with or without Tor.
- nodes: add Nodes::reconnect() (clear failure blocklist + force reconnect);
  node/Tor toggle handlers use it so a transient outage no longer strands the
  wallet at "Disconnected".
- MainWindow/SettingsDialog: reconnect on node/Tor toggle and on the async
  daemonStateChanged (closes the start() race; a freshly-toggled local node
  connects immediately).
- TorManager: reset retry/unpack state in init() so a system<->bundled Tor
  re-toggle can start again; quiesce on stop(). Linux unpackBins() rework
  (mkpath + presence-based unpack + graceful return-false), getVersion() arg
  fix, and a missing-binary start guard.
- build.yml: derive TOR_VERSION from the embedded tor binary (a hardcoded
  bundle number made unpackBins rewrite Tor every launch).
- depends/funcs.mk: confine cross-cmake find_package to the depends sysroot so
  host -dev libraries don't leak into the build.
2026-06-23 17:45:39 -04:00
16 changed files with 810 additions and 337 deletions
+6 -1
View File
@@ -54,7 +54,12 @@ jobs:
cd build cd build
# ARCH=x86-64: portable generic baseline. NEVER 'native' for a release — the CI runner's # ARCH=x86-64: portable generic baseline. NEVER 'native' for a release — the CI runner's
# CPU (often newer than users') would bake in unsupported instructions → SIGILL on launch. # CPU (often newer than users') would bake in unsupported instructions → SIGILL on launch.
cmake -DWITH_SCANNER=OFF -DARCH=x86-64 -DWOWNEROD_DIR=/tmp/wownerod-embed -DTOR_DIR=/tmp/tor-embed -DTOR_VERSION=15.0 .. # wowlet: TOR_VERSION must EXACTLY match `tor --version` of the embedded binary, else
# TorManager::unpackBins() sees embedded > the unpacked copy and rewrites Tor every launch.
# The tor-expert-bundle's RELEASE number (15.0) is NOT its tor version (0.4.8.x) — derive it.
TORVER=$(/tmp/tor-embed/tor --version | grep -oE '[0-9]+(\.[0-9]+){3}' | head -1)
echo "embedded tor version: $TORVER"
cmake -DWITH_SCANNER=OFF -DARCH=x86-64 -DWOWNEROD_DIR=/tmp/wownerod-embed -DTOR_DIR=/tmp/tor-embed -DTOR_VERSION="$TORVER" ..
cmake --build . -j $(nproc) cmake --build . -j $(nproc)
- name: package AppImage - name: package AppImage
run: | run: |
+1 -1
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.18) cmake_minimum_required(VERSION 3.18)
project(feather project(feather
VERSION "5.0.0" VERSION "5.0.3"
DESCRIPTION "A free Monero desktop wallet" DESCRIPTION "A free Monero desktop wallet"
LANGUAGES CXX C ASM LANGUAGES CXX C ASM
) )
+8
View File
@@ -198,6 +198,14 @@ ifneq ($(host),$(build))
$(1)_cmake += -DCMAKE_SYSTEM_NAME=$($(host_os)_cmake_system) $(1)_cmake += -DCMAKE_SYSTEM_NAME=$($(host_os)_cmake_system)
$(1)_cmake += -DCMAKE_C_COMPILER_TARGET=$(host) $(1)_cmake += -DCMAKE_C_COMPILER_TARGET=$(host)
$(1)_cmake += -DCMAKE_CXX_COMPILER_TARGET=$(host) $(1)_cmake += -DCMAKE_CXX_COMPILER_TARGET=$(host)
# wowlet: confine find_package to the depends sysroot so cross cmake builds don't grab host
# /usr -dev packages (libzip's find_package(ZLIB) was picking up host zlib1g-dev). Mirrors
# toolchain.cmake.in; no-op on clean CI, needed on dev boxes with -dev libs. Upstreamable.
$(1)_cmake += -DCMAKE_FIND_ROOT_PATH=$(host_prefix)
$(1)_cmake += -DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER
$(1)_cmake += -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY
$(1)_cmake += -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY
$(1)_cmake += -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY
endif endif
endif endif
endef endef
+9 -1
View File
@@ -36,6 +36,7 @@
#include "utils/AppData.h" #include "utils/AppData.h"
#include "utils/AsyncTask.h" #include "utils/AsyncTask.h"
#include "utils/ColorScheme.h" #include "utils/ColorScheme.h"
#include "utils/DaemonManager.h" // wowlet: for daemonStateChanged reconnect (node-toggle race)
#include "utils/Icons.h" #include "utils/Icons.h"
#include "utils/TorManager.h" #include "utils/TorManager.h"
#include "utils/WebsocketNotifier.h" #include "utils/WebsocketNotifier.h"
@@ -512,6 +513,13 @@ void MainWindow::initWalletContext() {
this->onConnectionStatusChanged(status); this->onConnectionStatusChanged(status);
m_nodes->autoConnect(); m_nodes->autoConnect();
}); });
// wowlet: the embedded node's RPC port comes up asynchronously after daemonManager()->start(), so a
// freshly-toggled local node would otherwise sit Disconnected until the next status tick. Reconnect the
// instant its RPC is up. (autoConnect's runLocalNode branch routes to 127.0.0.1; start() is idempotent.)
connect(daemonManager(), &DaemonManager::daemonStateChanged, this, [this](bool up){
if (up && conf()->get(Config::runLocalNode).toBool())
m_nodes->autoConnect(true);
});
connect(m_wallet, &Wallet::currentSubaddressAccountChanged, this, &MainWindow::updateTitle); connect(m_wallet, &Wallet::currentSubaddressAccountChanged, this, &MainWindow::updateTitle);
connect(m_wallet, &Wallet::walletPassphraseNeeded, this, &MainWindow::onWalletPassphraseNeeded); connect(m_wallet, &Wallet::walletPassphraseNeeded, this, &MainWindow::onWalletPassphraseNeeded);
@@ -703,7 +711,7 @@ void MainWindow::onWebsocketStatusChanged(bool enabled) {
} }
void MainWindow::onProxySettingsChangedConnect() { void MainWindow::onProxySettingsChangedConnect() {
m_nodes->connectToNode(); m_nodes->reconnect(); // wowlet: clear blocklist + force-reconnect after a live Tor/proxy switch (test 2)
this->onProxySettingsChanged(); this->onProxySettingsChanged();
} }
+1 -1
View File
@@ -177,7 +177,7 @@ void Settings::setupNetworkTab() {
daemonManager()->start(); daemonManager()->start();
else else
daemonManager()->stop(false); // non-blocking: don't freeze Settings while wownerod flushes daemonManager()->stop(false); // non-blocking: don't freeze Settings while wownerod flushes
m_nodes->autoConnect(true); m_nodes->reconnect(); // wowlet: clear blocklist + force-reconnect (handles a previously-failed remote)
}); });
} else { } else {
m_nodes = new Nodes(this, nullptr); m_nodes = new Nodes(this, nullptr);
+397 -309
View File
@@ -168,312 +168,400 @@
1601257433:250500 1601257433:250500
1601710572:252000 1601710572:252000
1602154921:253500 1602154921:253500
1609825674:279148 1602596617:255000
1609816860:279048 1603053507:256500
1610116800:280048 1603501419:258000
1610420700:281048 1603951028:259500
1610715120:282048 1604397604:261000
1611017160:283048 1604844178:262500
1611314940:284048 1605289509:264000
1611616080:285048 1605742395:265500
1611911760:286048 1606192218:267000
1612211940:287048 1606638968:268500
1612515660:288048 1607083718:270000
1612810500:289048 1607539961:271500
1613115540:290048 1607991764:273000
1613414820:291048 1608439952:274500
1613709300:292048 1608890342:276000
1614008280:293048 1609334849:277500
1614307380:294048 1609777306:279000
1614603060:295048 1610229306:280500
1614902280:296048 1610678028:282000
1615204740:297048 1611129205:283500
1615505100:298048 1611578787:285000
1615807020:299048 1612024723:286500
1616108820:300048 1612479368:288000
1616402280:301048 1612925285:289500
1616706720:302048 1613378445:291000
1617003960:303048 1613818784:292500
1617611100:305048 1614269634:294000
1617911640:306048 1614718724:295500
1618191900:307048 1615167437:297000
1618522560:308048 1615619299:298500
1618816620:309048 1616071879:300000
1619098740:310048 1616517919:301500
1619390520:311048 1616969191:303000
1619695500:312048 1617419137:304500
1619985120:313048 1617878649:306000
1620290220:314048 1618306800:307500
1620586260:315048 1618784409:309000
1620885360:316048 1619210159:310500
1621183620:317048 1619658329:312000
1621482300:318048 1620103337:313500
1621782840:319048 1620558729:315000
1622079960:320048 1621002710:316500
1622380260:321048 1621453789:318000
1622678460:322048 1621895697:319500
1622974380:323048 1622347075:321000
1623269760:324048 1622790315:322500
1623567420:325048 1623238613:324000
1623869820:326048 1623686218:325500
1624167180:327048 1624135035:327000
1624473000:328048 1624588247:328500
1624767600:329048 1625034084:330000
1625065980:330048 1625387596:331500
1625366940:331048 1625683505:333000
1625439000:332048 1626206520:334500
1625706420:333048 1626634887:336000
1626101160:334048 1627067110:337500
1626365940:335048 1627510005:339000
1626660360:336048 1627915694:340500
1626966840:337048 1628363606:342000
1627231740:338048 1628827297:343500
1627541700:339048 1629250950:345000
1627810440:340048 1629688820:346500
1628078820:341048 1630145454:348000
1628406780:342048 1630591816:349500
1628709420:343048 1631025927:351000
1629011100:344048 1631515284:352500
1629283260:345048 1631986172:354000
1629582540:346048 1632422703:355500
1629874620:347048 1632890624:357000
1630168440:348048 1633326321:358500
1630465980:349048 1633775768:360000
1630756800:350048 1634233650:361500
1631060460:351048 1634693889:363000
1631393400:352048 1635139294:364500
1631699460:353048 1635617950:366000
1632012480:354048 1636079519:367500
1632289920:355048 1636497795:369000
1632602820:356048 1636997495:370500
1632923220:357048 1637418065:372000
1633203180:358048 1637897948:373500
1633491000:359048 1638376658:375000
1633811400:360048 1638790941:376500
1634113140:361048 1639235224:378000
1634430300:362048 1639757854:379500
1634722260:363048 1640218491:381000
1635035460:364048 1640646944:382500
1635368760:365048 1641106951:384000
1635639660:366048 1641544811:385500
1635957840:367048 1641989328:387000
1636245240:368048 1642443489:388500
1636535820:369048 1643009648:390000
1636863900:370048 1643455341:391500
1637160360:371048 1643876044:393000
1637457780:372048 1644333115:394500
1637793240:373048 1644747412:396000
1638062460:374048 1645196998:397500
1638407580:375048 1645649589:399000
1638675240:376048 1646107975:400500
1638981360:377048 1646543179:402000
1639273140:378048 1647017449:403500
1639612740:379048 1647475207:405000
1639928760:380048 1647881079:406500
1640242500:381048 1648354637:408000
1640530620:382048 1648816244:409500
1640838000:383048 1649261814:411000
1641136800:384048 1649734559:412500
1641437400:385048 1650212215:414000
1641716040:386048 1650656774:415500
1642030680:387048 1651081594:417000
1642330500:388048 1651543047:418500
1642714260:389048 1651999579:420000
1643040360:390048 1652464011:421500
1643344560:391048 1652926487:423000
1643612040:392048 1653376589:424500
1643913360:393048 1653829783:426000
1644223920:394048 1654287342:427500
1644491820:395048 1654747411:429000
1644794760:396048 1655193349:430500
1645110660:397048 1655710653:432000
1645379400:398048 1656134720:433500
1645693500:399048 1656592570:435000
1645981080:400048 1657054346:436500
1646301360:401048 1657494432:438000
1646576220:402048 1657939752:439500
1646919420:403048 1658390870:441000
1647208320:404048 1658822838:442500
1647506640:405048 1659286786:444000
1647768840:406048 1659718822:445500
1648082520:407048 1660176142:447000
1648391700:408048 1660639818:448500
1648686240:409048 1661083112:450000
1649002860:410048 1661517325:451500
1649282820:411048 1661970076:453000
1649599860:412048 1662446938:454500
1649959980:413048 1662873827:456000
1650248280:414048 1663336599:457500
1650528840:415048 1663789611:459000
1650809340:416048 1664221220:460500
1651118280:417048 1664685037:462000
1651432260:418048 1665126666:463500
1651740360:419048 1665557872:465000
1652031600:420048 1666034414:466500
1652351400:421048 1666495833:468000
1652639520:422048 1666929111:469500
1652954640:423048 1667394182:471000
1653252600:424048 1667835975:472500
1653549840:425048 1668288340:474000
1653862740:426048 1668766046:475500
1654161060:427048 1669214111:477000
1654461900:428048 1669665943:478500
1654782600:429048 1670117908:480000
1655076960:430048 1670586033:481500
1655441820:431048 1671029494:483000
1655740500:432048 1671465279:484500
1656030240:433048 1671904526:486000
1656324720:434048 1672376519:487500
1656624060:435048 1672822751:489000
1656925920:436048 1673269600:490500
1657236840:437048 1673719076:492000
1657526700:438048 1674182057:493500
1657816740:439048 1674626828:495000
1658135940:440048 1675085613:496500
1658434440:441048 1675521264:498000
1658715780:442048 1675977630:499500
1659010080:443048 1676426332:501000
1659318120:444048 1676890583:502500
1659611040:445048 1677344883:504000
1659914940:446048 1677795450:505500
1660204260:447048 1678241156:507000
1660505220:448048 1678685201:508500
1660831740:449048 1679136670:510000
1661114940:450048 1679592119:511500
1661397720:451048 1680041240:513000
1661708760:452048 1680537447:514500
1661997180:453048 1680981131:516000
1662335640:454048 1681434379:517500
1662630120:455048 1681894205:519000
1662908820:456048 1682345821:520500
1663213380:457048 1682805333:522000
1663501140:458048 1683258937:523500
1663832400:459048 1683714088:525000
1664109480:460048 1684168676:526500
1664412600:461048 1684616632:528000
1664724720:462048 1685076954:529500
1665004620:463048 1685528631:531000
1665305460:464048 1685980354:532500
1665600480:465048 1686442998:534000
1665894960:466048 1686893463:535500
1666213620:467048 1687341403:537000
1666527600:468048 1687800345:538500
1666816380:469048 1688260852:540000
1667123160:470048 1688713555:541500
1667427180:471048 1689164934:543000
1667720640:472048 1689616311:544500
1668021000:473048 1690075687:546000
1668334740:474048 1690533856:547500
1668649440:475048 1690984681:549000
1668946680:476048 1691444970:550500
1669247100:477048 1691897459:552000
1669560960:478048 1692352014:553500
1669852800:479048 1692807402:555000
1670164020:480048 1693259665:556500
1670467860:481048 1693718708:558000
1670767440:482048 1694169620:559500
1671063480:483048 1694621215:561000
1671363960:484048 1695073751:562500
1671639900:485048 1695544139:564000
1671950940:486048 1696010948:565500
1672248180:487048 1696480073:567000
1672566480:488048 1696934545:568500
1672859400:489048 1697383183:570000
1673179080:490048 1697850119:571500
1673451120:491048 1698298303:573000
1673759520:492048 1698763478:574500
1674053100:493048 1699224283:576000
1674374100:494048 1699683095:577500
1674658440:495048 1700138177:579000
1674978420:496048 1700593190:580500
1675249380:497048 1701074336:582000
1675559460:498048 1701528794:583500
1675847160:499048 1701994567:585000
1676178900:500048 1702441199:586500
1676463600:501048 1702920637:588000
1676782680:502048 1703388368:589500
1677074640:503048 1703830741:591000
1677385020:504048 1704284418:592500
1677686160:505048 1704734515:594000
1677996420:506048 1705184380:595500
1678277700:507048 1705643726:597000
1678564680:508048 1706087617:598500
1678852920:509048 1706539628:600000
1679178960:510048 1706999159:601500
1679473500:511048 1707446328:603000
1679780220:512048 1707920134:604500
1680075120:513048 1708380012:606000
1680415620:514048 1708845016:607500
1680715500:515048 1709293588:609000
1681014240:516048 1709748870:610500
1681314240:517048 1710202528:612000
1681628940:518048 1710647834:613500
1681919940:519048 1711098409:615000
1682226660:520048 1711550139:616500
1682528460:521048 1712000662:618000
1682838000:522048 1712465889:619500
1683149040:523048 1712914193:621000
1683448680:524048 1713366831:622500
1683750540:525048 1713821520:624000
1684050540:526048 1714275415:625500
1684346940:527048 1714718068:627000
1684659780:528048 1715165016:628500
1684960560:529048 1715625858:630000
1685261880:530048 1716087656:631500
1685565420:531048 1716542805:633000
1685865660:532048 1716996296:634500
1686178020:533048 1717442953:636000
1686480060:534048 1717899613:637500
1686776820:535048 1718343607:639000
1687078860:536048 1718798893:640500
1687379580:537048 1719259600:642000
1687685100:538048 1719715208:643500
1687993320:539048 1720171971:645000
1688293380:540048 1720619357:646500
1688598420:541048 1721076296:648000
1688898120:542048 1721532239:649500
1689208500:543048 1721987052:651000
1689503940:544048 1722437031:652500
1689802800:545048 1722888953:654000
1690109040:546048 1723343110:655500
1690414320:547048 1723795513:657000
1690721280:548048 1724247189:658500
1691025480:549048 1724705009:660000
1691325720:550048 1725165021:661500
1691633940:551048 1725617360:663000
1691931480:552048 1726071946:664500
1692233280:553048 1726522439:666000
1692532620:554048 1726979451:667500
1692843420:555048 1727430507:669000
1693147020:556048 1727884081:670500
1693453140:557048 1728334947:672000
1693749540:558048 1728789282:673500
1694054460:559048 1729231059:675000
1694348940:560048 1729687775:676500
1694661120:561048 1730150813:678000
1694961240:562048 1730607377:679500
1695270600:563048 1731052869:681000
1695572880:564048 1731502177:682500
1695891600:565048 1731953584:684000
1696201200:566048 1732397467:685500
1696506300:567048 1732857103:687000
1696833360:568048 1733305492:688500
1697119860:569048 1733757932:690000
1697424660:570048 1734213761:691500
1697728620:571048 1734665858:693000
1698030720:572048 1735109323:694500
1698328680:573048 1735571098:696000
1698640800:574048 1736017391:697500
1698955800:575048 1736470274:699000
1699261560:576048 1736922413:700500
1699561560:577048 1737375374:702000
1699873800:578048 1737822863:703500
1700185680:579048 1738278318:705000
1700481900:580048 1738734744:706500
1700801820:581048 1739188167:708000
1701103860:582048 1739646442:709500
1701414900:583048 1740096952:711000
1701721080:584048 1740548052:712500
1702027260:585048 1741000180:714000
1702332300:586048 1741453599:715500
1702641960:587048 1741905136:717000
1742364662:718500
1742812126:720000
1743264843:721500
1743714525:723000
1744175390:724500
1744626378:726000
1745080531:727500
1745536519:729000
1745985232:730500
1746429926:732000
1746893280:733500
1747344244:735000
1747800382:736500
1748256803:738000
1748720133:739500
1749171722:741000
1749626761:742500
1750075168:744000
1750529452:745500
1750980136:747000
1751430178:748500
1751884163:750000
1752336519:751500
1752789487:753000
1753244800:754500
1753695473:756000
1754167218:757500
1754620759:759000
1755063742:760500
1755512182:762000
1755966558:763500
1756408673:765000
1756867066:766500
1757321935:768000
1757781405:769500
1758227963:771000
1758690444:772500
1759142623:774000
1759597441:775500
1760050166:777000
1760517211:778500
1760966094:780000
1761425868:781500
1761871754:783000
1762332424:784500
1762788746:786000
1763241559:787500
1763691073:789000
1764148322:790500
1764600720:792000
1765052466:793500
1765508447:795000
1765967616:796500
1766426715:798000
1766881106:799500
1767332242:801000
1767779604:802500
1768229063:804000
1768689528:805500
1769142523:807000
1769587539:808500
1770045670:810000
1770505705:811500
1770956798:813000
1771407979:814500
1771860479:816000
1772315752:817500
1772768810:819000
1773214642:820500
1773666005:822000
1774124830:823500
1774579737:825000
1775029537:826500
1775486297:828000
1775943681:829500
1776404619:831000
1776859398:832500
1777309533:834000
1777768990:835500
1778220072:837000
1778676940:838500
1779131063:840000
1779582902:841500
1780038251:843000
1780489721:844500
1780940799:846000
1781397311:847500
1781853697:849000
+9
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project // SPDX-FileCopyrightText: The Monero Project
#include <QQuickWindow>
#include <QSslSocket> #include <QSslSocket>
#include "Application.h" #include "Application.h"
@@ -88,6 +89,14 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round);
#endif #endif
// wowlet: force the QtQuick scene graph to the software renderer. The app's only QtQuick surface
// (the Mining tab's QQuickWidget) otherwise drives the default RHI/GL path, which faults (0xc0000005)
// on broken-GL environments — notably VirtualBox's VMSVGA — crashing the whole app and orphaning the
// embedded node. The mining widget is small + static, so software rendering costs nothing meaningful
// and guarantees no GL-init crash on any user's hardware. (Confirmed in a VMSVGA Win11 VM: the software
// backend launches cleanly where the default GL backend crashed.)
QQuickWindow::setGraphicsApi(QSGRendererInterface::Software);
Application app(argc, argv); Application app(argc, argv);
QApplication::setApplicationName("wowlet"); QApplication::setApplicationName("wowlet");
+115
View File
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#include "utils/ChildProcessGuard.h"
#include <QDebug>
#if defined(Q_OS_WIN)
#include <windows.h>
#elif defined(Q_OS_UNIX)
#include <csignal>
#if defined(Q_OS_LINUX)
#include <sys/prctl.h>
#include <unistd.h>
#endif
#endif
namespace ChildProcessGuard
{
#if defined(Q_OS_WIN)
// One process-wide job for all wowlet children. Created lazily on first use; the HANDLE is
// intentionally never closed — it must stay open for wowlet's entire lifetime so the job persists.
// On process death (clean OR crash) the kernel closes it, fires KILL_ON_JOB_CLOSE, and reaps the kids.
static HANDLE g_job = nullptr;
static HANDLE jobHandle()
{
if (g_job)
return g_job;
HANDLE job = CreateJobObjectW(nullptr, nullptr);
if (!job) {
qWarning() << "ChildProcessGuard: CreateJobObject failed:" << GetLastError();
return nullptr;
}
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info{};
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info, sizeof(info))) {
qWarning() << "ChildProcessGuard: SetInformationJobObject failed:" << GetLastError();
CloseHandle(job);
return nullptr;
}
g_job = job;
return g_job;
}
void installPreStart(QProcess *) { /* no-op on Windows: assignment happens post-start */ }
void adoptAfterStart(QProcess *process)
{
HANDLE job = jobHandle();
if (!job)
return;
// QProcess::start() is asynchronous: right after it returns the child often has no PID yet. Wait
// (bounded) for it to actually start, else the assign is silently skipped and the child survives a
// parent crash (orphaned) — this is exactly what stranded wownerod in testing. Returns the instant
// the process is created, so the UI-thread cost is tiny.
if (process->state() == QProcess::Starting)
process->waitForStarted(5000);
const qint64 pid = process->processId(); // 0 if it still isn't running
if (pid == 0) {
qWarning() << "ChildProcessGuard: child has no PID; not assigned to job";
return;
}
// AssignProcessToJobObject needs PROCESS_SET_QUOTA | PROCESS_TERMINATE. QProcess does not expose
// the child HANDLE, so re-open it by PID.
HANDLE proc = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, FALSE,
static_cast<DWORD>(pid));
if (!proc) {
qWarning() << "ChildProcessGuard: OpenProcess failed for pid" << pid << ":" << GetLastError();
return;
}
if (!AssignProcessToJobObject(job, proc))
qWarning() << "ChildProcessGuard: AssignProcessToJobObject failed:" << GetLastError();
else
qInfo() << "ChildProcessGuard: bound child pid" << pid << "to kill-on-close job";
CloseHandle(proc); // closing this per-child handle does NOT remove the process from the job
}
#elif defined(Q_OS_UNIX)
void installPreStart(QProcess *process)
{
#if defined(Q_OS_LINUX)
// Runs in the forked child between fork() and exec(). Keep it async-signal-safe.
process->setChildProcessModifier([] {
::prctl(PR_SET_PDEATHSIG, SIGKILL);
// Fork-race guard: if the parent already died before prctl ran, self-terminate.
if (::getppid() == 1)
::raise(SIGKILL);
});
#else
Q_UNUSED(process) // macOS/BSD: no PR_SET_PDEATHSIG; rely on aboutToQuit + destructor stop()
#endif
}
void adoptAfterStart(QProcess *) { /* no-op on Unix: handled pre-start */ }
#else
void installPreStart(QProcess *) {}
void adoptAfterStart(QProcess *) {}
#endif
} // namespace ChildProcessGuard
+33
View File
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#ifndef WOWLET_CHILDPROCESSGUARD_H
#define WOWLET_CHILDPROCESSGUARD_H
#include <QProcess>
// wowlet: OS-level "die with parent" for spawned child processes (embedded wownerod, bundled tor).
//
// QProcess does NOT kill its child when the parent exits or crashes; it only detaches. Feather's
// TorManager/DaemonManager relied solely on an explicit stop() in the normal-quit path, so an
// unhandled crash (e.g. a 0xc0000005 access violation) orphaned the children. This binds the
// child's lifetime to wowlet's at the kernel level, which survives a hard crash where no C++
// destructor and no Qt teardown runs.
//
// Windows: a single process-wide Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. Assign each
// child after start(); when wowlet's process dies the job's last handle closes and the
// kernel terminates every process still in the job.
// Unix: prctl(PR_SET_PDEATHSIG, SIGKILL) installed in the child (via setChildProcessModifier),
// so the kernel SIGKILLs the child when the parent dies.
namespace ChildProcessGuard
{
// Unix: arrange for `process` to receive SIGKILL when wowlet dies. Call BEFORE process->start().
// No-op on Windows (the Job Object handles it post-start).
void installPreStart(QProcess *process);
// Windows: assign the (now-running) child to wowlet's kill-on-close job. Call right AFTER start()
// returns and the child has a PID. No-op on Unix.
void adoptAfterStart(QProcess *process);
}
#endif //WOWLET_CHILDPROCESSGUARD_H
+143 -4
View File
@@ -4,12 +4,24 @@
#include "utils/DaemonManager.h" #include "utils/DaemonManager.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QDateTime>
#include <QDir> #include <QDir>
#include <QDirIterator> #include <QDirIterator>
#include <QEventLoop>
#include <QFileInfo> #include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QRegularExpression> #include <QRegularExpression>
#include <QUrl>
#include "constants.h"
#include "utils/AppData.h"
#include "utils/ChildProcessGuard.h" // wowlet: OS-level die-with-parent for the embedded node
#include "utils/config.h" #include "utils/config.h"
#include "utils/networktype.h"
#include "utils/TorManager.h" #include "utils/TorManager.h"
#include "utils/Utils.h" #include "utils/Utils.h"
@@ -25,6 +37,11 @@ DaemonManager::DaemonManager(QObject *parent)
connect(m_process, &QProcess::readyReadStandardOutput, this, &DaemonManager::handleProcessOutput); connect(m_process, &QProcess::readyReadStandardOutput, this, &DaemonManager::handleProcessOutput);
connect(m_process, &QProcess::errorOccurred, this, &DaemonManager::handleProcessError); connect(m_process, &QProcess::errorOccurred, this, &DaemonManager::handleProcessError);
connect(m_process, &QProcess::stateChanged, this, &DaemonManager::onStateChanged); connect(m_process, &QProcess::stateChanged, this, &DaemonManager::onStateChanged);
// wowlet: SIGKILL wownerod if wowlet dies (Unix); Windows binds it to a kill-on-close job post-start.
ChildProcessGuard::installPreStart(m_process);
// wowlet: graceful stop on any clean quit, not just the last-window-closed path (flushes LMDB).
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]{ this->stop(); });
} }
QPointer<DaemonManager> DaemonManager::m_instance(nullptr); QPointer<DaemonManager> DaemonManager::m_instance(nullptr);
@@ -46,18 +63,42 @@ void DaemonManager::start() {
if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting) if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)
return; return;
// wowlet: a deliberate (re)start clears the stop latch, so a later UNEXPECTED exit can auto-restart
// again. Without this, m_stopping stays true after the first node toggle-off and the crash
// auto-restart is silently dead for the rest of the session.
m_stopping = false;
if (!this->unpackBins()) { if (!this->unpackBins()) {
this->setErrorMessage("Unable to unpack the embedded wownerod binary."); this->setErrorMessage("Unable to unpack the embedded wownerod binary.");
return; return;
} }
// wowlet: don't trust a bare open port. Before adopting whatever is listening on our RPC port,
// verify via get_info that it's a healthy wownero-mainnet wownerod (right nettype, sane height,
// making progress) — not a stale/stuck orphan from a prior crash or an unrelated squatter.
if (Utils::portOpen(this->rpcHost, this->rpcPort)) { if (Utils::portOpen(this->rpcHost, this->rpcPort)) {
// Something is already listening on our RPC port; assume it is a usable local node. if (this->verifyAdoptableNode()) {
m_alreadyRunning = true; qDebug() << "Adopting existing wownerod on" << this->rpcAddress();
emit daemonStateChanged(true); m_alreadyRunning = true;
return; emit daemonStateChanged(true);
return;
}
qWarning() << "Refusing to adopt unverified listener on" << this->rpcAddress()
<< "- will spawn a managed wownerod instead";
this->setErrorMessage(
QString("Found an unrecognized process on %1; starting a fresh node.").arg(this->rpcAddress()));
// fall through to spawn; if the port/data-dir is genuinely held, the spawn fails to bind
// and surfaces a clear error via handleProcessError().
} }
// wowlet: the retry cap guards against a rapid crash-loop, not legit restarts. If the node had been
// up a while before this (re)start, clear the budget — otherwise a few node toggles over a session
// trip "maximum retries exceeded" and it won't start again until wowlet is relaunched.
const qint64 nowMs = QDateTime::currentMSecsSinceEpoch();
if (m_lastSpawnMs != 0 && (nowMs - m_lastSpawnMs) > 60000)
m_restarts = 0;
m_lastSpawnMs = nowMs;
m_restarts += 1; m_restarts += 1;
if (m_restarts > 4) { if (m_restarts > 4) {
this->setErrorMessage("wownerod failed to start: maximum retries exceeded"); this->setErrorMessage("wownerod failed to start: maximum retries exceeded");
@@ -81,6 +122,14 @@ void DaemonManager::start() {
arguments << "--no-igd"; // no UPnP port mapping arguments << "--no-igd"; // no UPnP port mapping
arguments << "--out-peers" << "16"; arguments << "--out-peers" << "16";
arguments << "--log-file" << QDir(dataDir).filePath("wownerod.log"); arguments << "--log-file" << QDir(dataDir).filePath("wownerod.log");
// wowlet: disable LMDB batch write mode so every block is committed in its own transaction.
// Without this, wownero accumulates blocks in a single large write transaction that is ABORTED
// (not committed) when the daemon shuts down mid-sync — causing all progress since the last
// batch boundary to be silently lost. This is visible in wownerod.log: sessions consistently
// revert to a stale height despite a full graceful shutdown sequence. Using "safe" mode is
// slower for initial sync but is the only correct choice for an embedded node that starts and
// stops with the wallet; the wownero chain is small enough that the overhead is acceptable.
arguments << "--db-sync-mode" << "safe";
// Full archival by default (wownero's chain is small); prune is an explicit opt-in. // Full archival by default (wownero's chain is small); prune is an explicit opt-in.
if (conf()->get(Config::pruneBlockchain).toBool()) if (conf()->get(Config::pruneBlockchain).toBool())
@@ -106,6 +155,7 @@ void DaemonManager::start() {
qDebug() << QString("Starting wownerod: %1 %2").arg(this->daemonPath, arguments.join(" ")); qDebug() << QString("Starting wownerod: %1 %2").arg(this->daemonPath, arguments.join(" "));
m_process->start(this->daemonPath, arguments); m_process->start(this->daemonPath, arguments);
ChildProcessGuard::adoptAfterStart(m_process); // wowlet: bind to kill-on-crash job (Windows)
m_started = true; m_started = true;
} }
@@ -238,6 +288,95 @@ bool DaemonManager::shouldStartDaemon() {
return true; return true;
} }
// wowlet: synchronous, bounded get_info probe used by start() to decide whether an already-open RPC
// port is a node we can safely adopt. Returns true ONLY for a healthy wownero-mainnet wownerod.
// Self-contained (own QNetworkAccessManager + local QEventLoop with a hard timeout) so it can run
// inside the synchronous start() at boot, before any wallet/DaemonRpc exists. Mirrors TorManager's
// blocking getVersion() probe idiom.
bool DaemonManager::verifyAdoptableNode() {
if (conf()->get(Config::offlineMode).toBool())
return false;
QNetworkAccessManager nam;
QNetworkRequest req(QUrl(QString("http://%1/get_info").arg(this->rpcAddress())));
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop loop;
QTimer timeout;
timeout.setSingleShot(true);
QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
QNetworkReply *reply = nam.post(req, QByteArray("{}"));
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
timeout.start(1500); // hard cap on UI-thread block
loop.exec();
if (!reply->isFinished()) { // timed out
reply->abort();
reply->deleteLater();
qWarning() << "get_info verify: no response from" << this->rpcAddress() << "within 1.5s";
return false;
}
bool netOk = reply->error() == QNetworkReply::NoError;
QByteArray data = reply->readAll();
reply->deleteLater();
if (!netOk || data.isEmpty() || !Utils::validateJSON(data)) {
qWarning() << "get_info verify: not a JSON-RPC daemon on" << this->rpcAddress();
return false;
}
QJsonObject obj = QJsonDocument::fromJson(data).object();
if (obj.value("status").toString() != "OK") { // 1. real wownerod RPC
qWarning() << "get_info verify: bad status" << obj.value("status").toString();
return false;
}
const QString wantNet = constants::networkType == NetworkType::MAINNET ? "mainnet" // 2. nettype
: constants::networkType == NetworkType::TESTNET ? "testnet"
: "stagenet";
if (obj.value("nettype").toString() != wantNet) {
qWarning() << "get_info verify: wrong nettype" << obj.value("nettype").toString()
<< "(want" << wantNet << ")";
return false;
}
const quint64 height = obj.value("height").toVariant().toULongLong(); // 3. height sanity
if (height == 0) {
qWarning() << "get_info verify: node reports height 0";
return false;
}
quint64 knownHeight = 0;
if (appData()->heights.contains(constants::networkType))
knownHeight = static_cast<quint64>(appData()->heights[constants::networkType]);
const quint64 peers = obj.value("outgoing_connections_count").toVariant().toULongLong()
+ obj.value("incoming_connections_count").toVariant().toULongLong();
const bool progressing = obj.value("busy_syncing").toBool() || obj.value("synchronized").toBool();
if (knownHeight > 0) {
const quint64 slack = 720; // ~1 day of wownero blocks; live-but-lagging is still adoptable
if (height + slack < knownHeight) {
if (peers == 0 && !progressing) { // 4. stuck-orphan signature: behind AND peerless AND idle
qWarning() << "get_info verify: stale/stuck node — height" << height
<< "<< known" << knownHeight << "with 0 peers, not syncing";
return false;
}
qWarning() << "get_info verify: node behind (" << height << "/" << knownHeight
<< ") but live; adopting and letting it catch up";
}
}
if (peers == 0) // soft: a freshly-spawned-but-orphaned node sits at 0 peers for minutes
qWarning() << "get_info verify: adopting node with 0 peers (likely still bootstrapping)";
qDebug() << "get_info verify: OK — nettype" << wantNet << "height" << height << "peers" << peers;
return true;
}
void DaemonManager::setErrorMessage(const QString &msg) { void DaemonManager::setErrorMessage(const QString &msg) {
this->errorMsg = msg; this->errorMsg = msg;
if (!msg.isEmpty()) if (!msg.isEmpty())
+6
View File
@@ -59,6 +59,11 @@ private slots:
private: private:
bool shouldStartDaemon(); bool shouldStartDaemon();
// wowlet: verify an already-open RPC port really is a healthy wownero-mainnet wownerod before
// adopting it, instead of trusting a bare TCP port-open probe. Prevents the wallet from silently
// syncing against a stale/stuck orphan (e.g. left by a prior crash) or a process squatting
// 127.0.0.1:34568. Synchronous, bounded (~1.5s), UI-thread-safe.
bool verifyAdoptableNode();
void setErrorMessage(const QString &msg); void setErrorMessage(const QString &msg);
QProcess *m_process; QProcess *m_process;
@@ -68,6 +73,7 @@ private:
bool m_synced = false; bool m_synced = false;
bool m_stopping = false; bool m_stopping = false;
int m_restarts = 0; int m_restarts = 0;
qint64 m_lastSpawnMs = 0; // wowlet: wall-clock of last spawn — ages out the crash-retry budget
static QPointer<DaemonManager> m_instance; static QPointer<DaemonManager> m_instance;
}; };
+62 -19
View File
@@ -6,6 +6,7 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDirIterator> #include <QDirIterator>
#include "utils/ChildProcessGuard.h" // wowlet: OS-level die-with-parent for bundled tor
#include "utils/config.h" #include "utils/config.h"
#include "utils/Utils.h" #include "utils/Utils.h"
#include "utils/os/tails.h" #include "utils/os/tails.h"
@@ -37,15 +38,26 @@ TorManager::TorManager(QObject *parent)
connect(m_process, &QProcess::readyReadStandardOutput, this, &TorManager::handleProcessOutput); connect(m_process, &QProcess::readyReadStandardOutput, this, &TorManager::handleProcessOutput);
connect(m_process, &QProcess::errorOccurred, this, &TorManager::handleProcessError); connect(m_process, &QProcess::errorOccurred, this, &TorManager::handleProcessError);
connect(m_process, &QProcess::stateChanged, this, &TorManager::stateChanged); connect(m_process, &QProcess::stateChanged, this, &TorManager::stateChanged);
ChildProcessGuard::installPreStart(m_process); // wowlet: SIGKILL tor if wowlet dies (Unix)
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]{ this->stop(); }); // wowlet
} }
QPointer<TorManager> TorManager::m_instance(nullptr); QPointer<TorManager> TorManager::m_instance(nullptr);
void TorManager::init() { void TorManager::init() {
// wowlet: a live proxy/useLocalTor toggle re-runs init(). Reset failure/retry state so switching
// system->bundled Tor after a prior failure (Linux unpack, or a FailedToStart that latched
// m_stopRetries in handleProcessError) can actually start again instead of staying silently dead.
m_restarts = 0;
m_stopRetries = false;
m_unpacked = false; // re-evaluate the unpack on the next start()
m_localTor = !shouldStartTorDaemon(); m_localTor = !shouldStartTorDaemon();
auto state = m_process->state(); auto state = m_process->state();
if (m_localTor && (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)) { if (m_localTor && (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)) {
m_stopRetries = true; // wowlet: intentional kill (switching to system Tor); don't auto-restart it
m_process->kill(); m_process->kill();
m_started = false; m_started = false;
} }
@@ -54,6 +66,8 @@ void TorManager::init() {
} }
void TorManager::stop() { void TorManager::stop() {
m_stopRetries = true; // wowlet: intentional stop — suppress the stateChanged() auto-restart
m_checkConnectionTimer->stop(); // wowlet: stop probing the connection after an intentional stop
m_process->kill(); m_process->kill();
m_started = false; m_started = false;
} }
@@ -82,6 +96,17 @@ void TorManager::start() {
this->torPath = alternativeTorFile; this->torPath = alternativeTorFile;
} }
// wowlet: never hand QProcess a non-existent path (the Linux "empty folder" symptom). If the bundled
// binary isn't on disk, degrade to local/external-Tor mode + surface an error instead of burning the
// 4-retry budget on FailedToStart. With clearnet sync, node sync is unaffected; only broadcast-over-Tor
// is lost, which DaemonManager tolerates via clearnet relay.
if (!QFileInfo(this->torPath).isFile()) {
this->setErrorMessage("Bundled Tor binary not found; using external/local Tor if available: " + this->torPath);
m_localTor = true;
this->checkConnection();
return;
}
qDebug() << QString("Start process: %1").arg(this->torPath); qDebug() << QString("Start process: %1").arg(this->torPath);
m_restarts += 1; m_restarts += 1;
@@ -102,6 +127,7 @@ void TorManager::start() {
qDebug() << QString("%1 %2").arg(this->torPath, arguments.join(" ")); qDebug() << QString("%1 %2").arg(this->torPath, arguments.join(" "));
m_process->start(this->torPath, arguments); m_process->start(this->torPath, arguments);
ChildProcessGuard::adoptAfterStart(m_process); // wowlet: bind to kill-on-crash job (Windows)
m_started = true; m_started = true;
} }
@@ -201,37 +227,54 @@ bool TorManager::unpackBins() {
return true; return true;
} }
SemanticVersion embeddedVersion = SemanticVersion::fromString(QString(TOR_VERSION)); // wowlet: QFile::copy never creates parent dirs; without this the copy loop can silently no-op on
SemanticVersion filesystemVersion = this->getVersion(torPath); // a fresh profile, leaving an empty tor/ dir and "tor failed to start" (Lucas/Fedora). main.cpp
qDebug() << QString("Tor versions: embedded %1, filesystem %2").arg(embeddedVersion.toString(), filesystemVersion.toString()); // also mkpaths this, but make unpackBins() self-sufficient. Mirrors DaemonManager::unpackBins().
if (SemanticVersion::isValid(filesystemVersion) && (embeddedVersion > filesystemVersion)) { QDir().mkpath(this->torDir);
qInfo() << "Embedded version is newer, overwriting.";
QFile::setPermissions(torPath, QFile::ReadOther | QFile::WriteOther);
if (!QFile::remove(torPath)) {
qWarning() << "Unable to remove old Tor binary";
return false;
}
}
if (embeddedVersion > filesystemVersion) { // wowlet: unpack when the binary is MISSING, not only when a version compare says "newer". Overwrite
// only when the embedded build is a VALID newer version (getVersion() of a non-existent file returns
// an invalid SemanticVersion, so it must not gate the first unpack).
bool present = QFileInfo(this->torPath).isFile();
SemanticVersion embeddedVersion = SemanticVersion::fromString(QString(TOR_VERSION));
SemanticVersion filesystemVersion = present ? this->getVersion(this->torPath) : SemanticVersion();
qDebug() << QString("Tor versions: embedded %1, filesystem %2").arg(embeddedVersion.toString(), filesystemVersion.toString());
bool needsOverwrite = present && SemanticVersion::isValid(filesystemVersion)
&& (embeddedVersion > filesystemVersion);
if (!present || needsOverwrite) {
if (needsOverwrite) {
qInfo() << "Embedded Tor is newer, overwriting.";
QFile::setPermissions(this->torPath, QFile::ReadOther | QFile::WriteOther);
QFile::remove(this->torPath);
}
QDirIterator it(":/assets/tor", QDirIterator::Subdirectories); QDirIterator it(":/assets/tor", QDirIterator::Subdirectories);
while (it.hasNext()) { while (it.hasNext()) {
QString assetFile = it.next(); QString assetFile = it.next();
QFileInfo assetFileInfo = QFileInfo(assetFile); QString filePath = QDir(this->torDir).filePath(QFileInfo(assetFile).fileName());
QFile f(assetFile); QFile f(assetFile);
QString filePath = QDir(this->torDir).filePath(assetFileInfo.fileName());
f.copy(filePath); f.copy(filePath);
f.close(); f.close();
} }
qInfo() << "Wrote Tor binaries to: " << this->torDir; qInfo() << "Wrote Tor binaries to:" << this->torDir;
} }
#if defined(Q_OS_UNIX) #if defined(Q_OS_UNIX)
QFile tor(this->torPath); // wowlet: only chmod if the binary actually landed.
tor.setPermissions(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther if (QFileInfo(this->torPath).isFile()) {
| QFile::ReadOwner | QFile::ReadGroup | QFile::ReadOther); QFile tor(this->torPath);
tor.setPermissions(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther
| QFile::ReadOwner | QFile::ReadGroup | QFile::ReadOther);
}
#endif #endif
// wowlet: do NOT report success if nothing landed — return false so shouldStartTorDaemon() falls
// back to --use-local-tor cleanly (see :297-303) instead of start()ing a missing binary.
if (!QFileInfo(this->torPath).isFile()) {
qWarning() << "Tor was not unpacked (no embedded Tor, or copy failed):" << this->torPath;
return false;
}
m_unpacked = true; m_unpacked = true;
return true; return true;
} }
@@ -310,7 +353,7 @@ bool TorManager::shouldStartTorDaemon() {
SemanticVersion TorManager::getVersion(const QString &fileName) { SemanticVersion TorManager::getVersion(const QString &fileName) {
QProcess process; QProcess process;
process.setProcessChannelMode(QProcess::MergedChannels); process.setProcessChannelMode(QProcess::MergedChannels);
process.start(this->torPath, QStringList() << "--version"); process.start(fileName, QStringList() << "--version"); // wowlet: use the passed path, not this->torPath
process.waitForFinished(-1); process.waitForFinished(-1);
QString output = process.readAllStandardOutput(); QString output = process.readAllStandardOutput();
+4 -1
View File
@@ -115,7 +115,10 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
// Tor // Tor
{Config::proxy, {QS("proxy"), Config::Proxy::Tor}}, {Config::proxy, {QS("proxy"), Config::Proxy::Tor}},
{Config::torPrivacyLevel, {QS("torPrivacyLevel"), 1}}, // wowlet: allTorExceptNode(0) — NEVER sync blocks over Tor. Block sync is ALWAYS clearnet (speed,
// the standard monero model). Tor is ONLY for tx broadcast (via DaemonManager's --tx-proxy), which
// is independent of this. This is what makes remote nodes work with OR without Tor.
{Config::torPrivacyLevel, {QS("torPrivacyLevel"), 0}},
{Config::torOnlyAllowOnion, {QS("torOnlyAllowOnion"), false}}, {Config::torOnlyAllowOnion, {QS("torOnlyAllowOnion"), false}},
{Config::socks5Host, {QS("socks5Host"), "127.0.0.1"}}, {Config::socks5Host, {QS("socks5Host"), "127.0.0.1"}},
{Config::socks5Port, {QS("socks5Port"), "9050"}}, {Config::socks5Port, {QS("socks5Port"), "9050"}},
+9
View File
@@ -330,6 +330,15 @@ void Nodes::autoConnect(bool forceReconnect) {
this->updateModels(); this->updateModels();
} }
// wowlet: clear the recent-failure blocklist then force a reconnect. autoConnect(true) alone is not
// enough — pickEligibleNode() still filters the failed node out of m_recentFailures, so a node that
// dropped during a transient Tor/proxy outage would stay blocklisted ("permanent Disconnected"). The
// node/Tor live-toggle handlers call this to recover.
void Nodes::reconnect() {
m_recentFailures.clear();
this->autoConnect(true);
}
FeatherNode Nodes::pickEligibleNode() { FeatherNode Nodes::pickEligibleNode() {
// Pick a node at random to connect to // Pick a node at random to connect to
auto rtn = FeatherNode(); auto rtn = FeatherNode();
+3
View File
@@ -173,6 +173,9 @@ public slots:
void onNodeSourceChanged(NodeSource nodeSource); void onNodeSourceChanged(NodeSource nodeSource);
void setCustomNodes(const QList<FeatherNode>& nodes); void setCustomNodes(const QList<FeatherNode>& nodes);
void autoConnect(bool forceReconnect = false); void autoConnect(bool forceReconnect = false);
// wowlet: clear the recent-failure blocklist and force-reconnect. Used by the node/Tor live-toggle
// handlers so a node blocklisted during a transient outage (e.g. a Tor toggle) is actually retried.
void reconnect();
private slots: private slots:
void onWalletRefreshed(); void onWalletRefreshed();
+4
View File
@@ -15,6 +15,10 @@ PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget
, m_fields(fields) , m_fields(fields)
{ {
ui->setupUi(this); ui->setupUi(this);
// wowlet: hide the hardware-device option — wownero has no Ledger/Trezor support
ui->radioCreateFromDevice->setVisible(false);
this->setButtonText(QWizard::FinishButton, "Open recent wallet"); this->setButtonText(QWizard::FinishButton, "Open recent wallet");
ui->label_version->setText(QString("wowlet %1 — by wownero.org").arg(FEATHER_VERSION)); ui->label_version->setText(QString("wowlet %1 — by wownero.org").arg(FEATHER_VERSION));