mirror of
https://github.com/wownero/wowlet.git
synced 2026-10-28 19:58:35 +01:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a526061d97 | ||
|
|
f4c5ec3c25 | ||
|
|
1217ce174c | ||
|
|
914fa1ed32 | ||
|
|
93d13a6568 | ||
|
|
64326119eb | ||
|
|
e3ba9c7bad | ||
|
|
6cd7e8bad9 | ||
|
|
0d5d745311 |
@@ -0,0 +1,139 @@
|
||||
name: macos build (gitea self-hosted)
|
||||
|
||||
# Runs ONLY on the Gitea mac runner (GitHub ignores .gitea/workflows).
|
||||
# Dispatch-only: the mac runner has capacity 1 and also serves the Hash Bags
|
||||
# iOS lane, so macOS wowlet builds are an explicit button, not a push hook.
|
||||
# All build state lands under the runner's work dir, which lives on
|
||||
# /Volumes/DataSSD (never backed up: the fleet backup pulls an allowlist of
|
||||
# gitea data only).
|
||||
#
|
||||
# Signing/notarization uses the SAME seven secrets as the smirk-monorepo
|
||||
# desktop lane, set by ~/keys/suchsoftware/set-gitea-desktop-secrets.sh:
|
||||
# APPLE_CERTIFICATE (b64 Developer ID Application .p12),
|
||||
# APPLE_CERTIFICATE_PASSWORD, APPLE_SIGNING_IDENTITY, APPLE_TEAM_ID,
|
||||
# APPLE_API_ISSUER, APPLE_API_KEY, APPLE_API_KEY_P8_BASE64 (notarytool).
|
||||
# If APPLE_CERTIFICATE is unset the build ships unsigned instead of failing.
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
build-macos:
|
||||
name: "macOS (brew, native)"
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||
APPLE_API_KEY_P8_BASE64: ${{ secrets.APPLE_API_KEY_P8_BASE64 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: install dependencies (brew)
|
||||
run: |
|
||||
export PATH="/opt/homebrew/bin:$PATH"
|
||||
export NONINTERACTIVE=1 HOMEBREW_NO_ENV_HINTS=1
|
||||
brew install --quiet qt libsodium libzip qrencode unbound cmake boost hidapi openssl expat protobuf pkgconfig zxing-cpp
|
||||
|
||||
- name: fetch wownerod to embed
|
||||
run: |
|
||||
set -e
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
arm64) WOWTAR=wownero-aarch64-apple-darwin11-v0.11.4.0.tar.bz2 ;;
|
||||
*) WOWTAR=wownero-x86_64-apple-darwin11-v0.11.4.0.tar.bz2 ;;
|
||||
esac
|
||||
mkdir -p /tmp/wownerod-embed
|
||||
curl -L -o /tmp/wownero.tar.bz2 \
|
||||
"https://github.com/wownero/wownero-codeberg-mirror/releases/download/v0.11.4.0/$WOWTAR"
|
||||
tar xjf /tmp/wownero.tar.bz2 -C /tmp
|
||||
find /tmp -name wownerod -type f -exec cp {} /tmp/wownerod-embed/ \;
|
||||
chmod +x /tmp/wownerod-embed/wownerod
|
||||
ls -la /tmp/wownerod-embed
|
||||
|
||||
- name: build
|
||||
run: |
|
||||
export PATH="/opt/homebrew/bin:$PATH"
|
||||
mkdir -p build && cd build
|
||||
cmake -DWITH_SCANNER=OFF -DWOWNEROD_DIR=/tmp/wownerod-embed ..
|
||||
cmake --build . -j "$(sysctl -n hw.ncpu)"
|
||||
|
||||
- name: package app bundle
|
||||
run: |
|
||||
set -e
|
||||
cd build
|
||||
APP=$(find . -maxdepth 3 -name "*.app" -type d | head -1)
|
||||
[ -n "$APP" ] || { echo "no .app bundle produced"; ls -la bin/; exit 1; }
|
||||
mkdir -p dist
|
||||
cp -R "$APP" dist/Wowlet.app
|
||||
# cmake never places the icon in the bundle (upstream only copies it
|
||||
# for the dormant `installed/` layout)
|
||||
mkdir -p dist/Wowlet.app/Contents/Resources
|
||||
cp ../src/assets/images/appicons/appicon.icns dist/Wowlet.app/Contents/Resources/
|
||||
# bundle Qt frameworks/plugins so the app runs off-machine
|
||||
/opt/homebrew/opt/qt/bin/macdeployqt dist/Wowlet.app
|
||||
ls -la dist/
|
||||
|
||||
- name: sign and notarize (skipped when secrets absent)
|
||||
run: |
|
||||
set -e
|
||||
cd build
|
||||
if [ -z "$APPLE_CERTIFICATE" ]; then
|
||||
echo "APPLE_CERTIFICATE not set — shipping UNSIGNED build"
|
||||
exit 0
|
||||
fi
|
||||
KC=/tmp/wowlet-ci.keychain-db
|
||||
KCPW=$(/usr/bin/uuidgen)
|
||||
echo "$KCPW" > /tmp/wowlet-ci-kcpw # for the cleanup step only
|
||||
security create-keychain -p "$KCPW" "$KC"
|
||||
security set-keychain-settings -lut 3600 "$KC"
|
||||
security unlock-keychain -p "$KCPW" "$KC"
|
||||
printf '%s' "$APPLE_CERTIFICATE" | base64 -d > /tmp/dev_id.p12
|
||||
security import /tmp/dev_id.p12 -k "$KC" -P "$APPLE_CERTIFICATE_PASSWORD" \
|
||||
-T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k "$KCPW" "$KC" >/dev/null
|
||||
security list-keychains -d user -s "$KC" login.keychain-db
|
||||
|
||||
# sign nested Mach-O executables first (embedded wownerod etc.),
|
||||
# then frameworks, then the bundle — all hardened-runtime + timestamp
|
||||
SIGN() { codesign --force --options runtime --timestamp --keychain "$KC" \
|
||||
--sign "$APPLE_SIGNING_IDENTITY" "$@"; }
|
||||
find dist/Wowlet.app -type f -perm +111 ! -path "*/Frameworks/*" \
|
||||
| while read -r f; do file "$f" | grep -q Mach-O && SIGN "$f" || true; done
|
||||
find dist/Wowlet.app/Contents/Frameworks -maxdepth 1 \( -name "*.framework" -o -name "*.dylib" \) 2>/dev/null \
|
||||
| while read -r fw; do SIGN "$fw"; done
|
||||
SIGN dist/Wowlet.app
|
||||
codesign --verify --deep --strict dist/Wowlet.app
|
||||
|
||||
printf '%s' "$APPLE_API_KEY_P8_BASE64" | base64 -d > /tmp/notary.p8
|
||||
ditto -c -k --keepParent dist/Wowlet.app /tmp/wowlet-notarize.zip
|
||||
xcrun notarytool submit /tmp/wowlet-notarize.zip \
|
||||
--key /tmp/notary.p8 --key-id "$APPLE_API_KEY" --issuer "$APPLE_API_ISSUER" \
|
||||
--wait --timeout 30m
|
||||
xcrun stapler staple dist/Wowlet.app
|
||||
echo "signed + notarized + stapled"
|
||||
|
||||
- name: zip artifact
|
||||
run: |
|
||||
set -e
|
||||
cd build/dist
|
||||
ditto -c -k --keepParent Wowlet.app ../wowlet-macos-$(uname -m).zip
|
||||
ls -la ../wowlet-macos-*.zip
|
||||
|
||||
- name: cleanup signing material
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f /tmp/wowlet-ci-kcpw ]; then
|
||||
security delete-keychain /tmp/wowlet-ci.keychain-db || true
|
||||
fi
|
||||
rm -f /tmp/dev_id.p12 /tmp/notary.p8 /tmp/wowlet-ci-kcpw /tmp/wowlet-notarize.zip
|
||||
|
||||
- name: upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wowlet-macos
|
||||
path: build/wowlet-macos-*.zip
|
||||
retention-days: 14
|
||||
@@ -54,7 +54,12 @@ jobs:
|
||||
cd build
|
||||
# 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.
|
||||
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)
|
||||
- name: package AppImage
|
||||
run: |
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
project(feather
|
||||
VERSION "5.0.0"
|
||||
VERSION "5.0.3"
|
||||
DESCRIPTION "A free Monero desktop wallet"
|
||||
LANGUAGES CXX C ASM
|
||||
)
|
||||
|
||||
@@ -198,6 +198,14 @@ ifneq ($(host),$(build))
|
||||
$(1)_cmake += -DCMAKE_SYSTEM_NAME=$($(host_os)_cmake_system)
|
||||
$(1)_cmake += -DCMAKE_C_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
|
||||
endef
|
||||
|
||||
+9
-1
@@ -36,6 +36,7 @@
|
||||
#include "utils/AppData.h"
|
||||
#include "utils/AsyncTask.h"
|
||||
#include "utils/ColorScheme.h"
|
||||
#include "utils/DaemonManager.h" // wowlet: for daemonStateChanged reconnect (node-toggle race)
|
||||
#include "utils/Icons.h"
|
||||
#include "utils/TorManager.h"
|
||||
#include "utils/WebsocketNotifier.h"
|
||||
@@ -512,6 +513,13 @@ void MainWindow::initWalletContext() {
|
||||
this->onConnectionStatusChanged(status);
|
||||
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::walletPassphraseNeeded, this, &MainWindow::onWalletPassphraseNeeded);
|
||||
|
||||
@@ -703,7 +711,7 @@ void MainWindow::onWebsocketStatusChanged(bool enabled) {
|
||||
}
|
||||
|
||||
void MainWindow::onProxySettingsChangedConnect() {
|
||||
m_nodes->connectToNode();
|
||||
m_nodes->reconnect(); // wowlet: clear blocklist + force-reconnect after a live Tor/proxy switch (test 2)
|
||||
this->onProxySettingsChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ void Settings::setupNetworkTab() {
|
||||
daemonManager()->start();
|
||||
else
|
||||
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 {
|
||||
m_nodes = new Nodes(this, nullptr);
|
||||
|
||||
@@ -168,312 +168,400 @@
|
||||
1601257433:250500
|
||||
1601710572:252000
|
||||
1602154921:253500
|
||||
1609825674:279148
|
||||
1609816860:279048
|
||||
1610116800:280048
|
||||
1610420700:281048
|
||||
1610715120:282048
|
||||
1611017160:283048
|
||||
1611314940:284048
|
||||
1611616080:285048
|
||||
1611911760:286048
|
||||
1612211940:287048
|
||||
1612515660:288048
|
||||
1612810500:289048
|
||||
1613115540:290048
|
||||
1613414820:291048
|
||||
1613709300:292048
|
||||
1614008280:293048
|
||||
1614307380:294048
|
||||
1614603060:295048
|
||||
1614902280:296048
|
||||
1615204740:297048
|
||||
1615505100:298048
|
||||
1615807020:299048
|
||||
1616108820:300048
|
||||
1616402280:301048
|
||||
1616706720:302048
|
||||
1617003960:303048
|
||||
1617611100:305048
|
||||
1617911640:306048
|
||||
1618191900:307048
|
||||
1618522560:308048
|
||||
1618816620:309048
|
||||
1619098740:310048
|
||||
1619390520:311048
|
||||
1619695500:312048
|
||||
1619985120:313048
|
||||
1620290220:314048
|
||||
1620586260:315048
|
||||
1620885360:316048
|
||||
1621183620:317048
|
||||
1621482300:318048
|
||||
1621782840:319048
|
||||
1622079960:320048
|
||||
1622380260:321048
|
||||
1622678460:322048
|
||||
1622974380:323048
|
||||
1623269760:324048
|
||||
1623567420:325048
|
||||
1623869820:326048
|
||||
1624167180:327048
|
||||
1624473000:328048
|
||||
1624767600:329048
|
||||
1625065980:330048
|
||||
1625366940:331048
|
||||
1625439000:332048
|
||||
1625706420:333048
|
||||
1626101160:334048
|
||||
1626365940:335048
|
||||
1626660360:336048
|
||||
1626966840:337048
|
||||
1627231740:338048
|
||||
1627541700:339048
|
||||
1627810440:340048
|
||||
1628078820:341048
|
||||
1628406780:342048
|
||||
1628709420:343048
|
||||
1629011100:344048
|
||||
1629283260:345048
|
||||
1629582540:346048
|
||||
1629874620:347048
|
||||
1630168440:348048
|
||||
1630465980:349048
|
||||
1630756800:350048
|
||||
1631060460:351048
|
||||
1631393400:352048
|
||||
1631699460:353048
|
||||
1632012480:354048
|
||||
1632289920:355048
|
||||
1632602820:356048
|
||||
1632923220:357048
|
||||
1633203180:358048
|
||||
1633491000:359048
|
||||
1633811400:360048
|
||||
1634113140:361048
|
||||
1634430300:362048
|
||||
1634722260:363048
|
||||
1635035460:364048
|
||||
1635368760:365048
|
||||
1635639660:366048
|
||||
1635957840:367048
|
||||
1636245240:368048
|
||||
1636535820:369048
|
||||
1636863900:370048
|
||||
1637160360:371048
|
||||
1637457780:372048
|
||||
1637793240:373048
|
||||
1638062460:374048
|
||||
1638407580:375048
|
||||
1638675240:376048
|
||||
1638981360:377048
|
||||
1639273140:378048
|
||||
1639612740:379048
|
||||
1639928760:380048
|
||||
1640242500:381048
|
||||
1640530620:382048
|
||||
1640838000:383048
|
||||
1641136800:384048
|
||||
1641437400:385048
|
||||
1641716040:386048
|
||||
1642030680:387048
|
||||
1642330500:388048
|
||||
1642714260:389048
|
||||
1643040360:390048
|
||||
1643344560:391048
|
||||
1643612040:392048
|
||||
1643913360:393048
|
||||
1644223920:394048
|
||||
1644491820:395048
|
||||
1644794760:396048
|
||||
1645110660:397048
|
||||
1645379400:398048
|
||||
1645693500:399048
|
||||
1645981080:400048
|
||||
1646301360:401048
|
||||
1646576220:402048
|
||||
1646919420:403048
|
||||
1647208320:404048
|
||||
1647506640:405048
|
||||
1647768840:406048
|
||||
1648082520:407048
|
||||
1648391700:408048
|
||||
1648686240:409048
|
||||
1649002860:410048
|
||||
1649282820:411048
|
||||
1649599860:412048
|
||||
1649959980:413048
|
||||
1650248280:414048
|
||||
1650528840:415048
|
||||
1650809340:416048
|
||||
1651118280:417048
|
||||
1651432260:418048
|
||||
1651740360:419048
|
||||
1652031600:420048
|
||||
1652351400:421048
|
||||
1652639520:422048
|
||||
1652954640:423048
|
||||
1653252600:424048
|
||||
1653549840:425048
|
||||
1653862740:426048
|
||||
1654161060:427048
|
||||
1654461900:428048
|
||||
1654782600:429048
|
||||
1655076960:430048
|
||||
1655441820:431048
|
||||
1655740500:432048
|
||||
1656030240:433048
|
||||
1656324720:434048
|
||||
1656624060:435048
|
||||
1656925920:436048
|
||||
1657236840:437048
|
||||
1657526700:438048
|
||||
1657816740:439048
|
||||
1658135940:440048
|
||||
1658434440:441048
|
||||
1658715780:442048
|
||||
1659010080:443048
|
||||
1659318120:444048
|
||||
1659611040:445048
|
||||
1659914940:446048
|
||||
1660204260:447048
|
||||
1660505220:448048
|
||||
1660831740:449048
|
||||
1661114940:450048
|
||||
1661397720:451048
|
||||
1661708760:452048
|
||||
1661997180:453048
|
||||
1662335640:454048
|
||||
1662630120:455048
|
||||
1662908820:456048
|
||||
1663213380:457048
|
||||
1663501140:458048
|
||||
1663832400:459048
|
||||
1664109480:460048
|
||||
1664412600:461048
|
||||
1664724720:462048
|
||||
1665004620:463048
|
||||
1665305460:464048
|
||||
1665600480:465048
|
||||
1665894960:466048
|
||||
1666213620:467048
|
||||
1666527600:468048
|
||||
1666816380:469048
|
||||
1667123160:470048
|
||||
1667427180:471048
|
||||
1667720640:472048
|
||||
1668021000:473048
|
||||
1668334740:474048
|
||||
1668649440:475048
|
||||
1668946680:476048
|
||||
1669247100:477048
|
||||
1669560960:478048
|
||||
1669852800:479048
|
||||
1670164020:480048
|
||||
1670467860:481048
|
||||
1670767440:482048
|
||||
1671063480:483048
|
||||
1671363960:484048
|
||||
1671639900:485048
|
||||
1671950940:486048
|
||||
1672248180:487048
|
||||
1672566480:488048
|
||||
1672859400:489048
|
||||
1673179080:490048
|
||||
1673451120:491048
|
||||
1673759520:492048
|
||||
1674053100:493048
|
||||
1674374100:494048
|
||||
1674658440:495048
|
||||
1674978420:496048
|
||||
1675249380:497048
|
||||
1675559460:498048
|
||||
1675847160:499048
|
||||
1676178900:500048
|
||||
1676463600:501048
|
||||
1676782680:502048
|
||||
1677074640:503048
|
||||
1677385020:504048
|
||||
1677686160:505048
|
||||
1677996420:506048
|
||||
1678277700:507048
|
||||
1678564680:508048
|
||||
1678852920:509048
|
||||
1679178960:510048
|
||||
1679473500:511048
|
||||
1679780220:512048
|
||||
1680075120:513048
|
||||
1680415620:514048
|
||||
1680715500:515048
|
||||
1681014240:516048
|
||||
1681314240:517048
|
||||
1681628940:518048
|
||||
1681919940:519048
|
||||
1682226660:520048
|
||||
1682528460:521048
|
||||
1682838000:522048
|
||||
1683149040:523048
|
||||
1683448680:524048
|
||||
1683750540:525048
|
||||
1684050540:526048
|
||||
1684346940:527048
|
||||
1684659780:528048
|
||||
1684960560:529048
|
||||
1685261880:530048
|
||||
1685565420:531048
|
||||
1685865660:532048
|
||||
1686178020:533048
|
||||
1686480060:534048
|
||||
1686776820:535048
|
||||
1687078860:536048
|
||||
1687379580:537048
|
||||
1687685100:538048
|
||||
1687993320:539048
|
||||
1688293380:540048
|
||||
1688598420:541048
|
||||
1688898120:542048
|
||||
1689208500:543048
|
||||
1689503940:544048
|
||||
1689802800:545048
|
||||
1690109040:546048
|
||||
1690414320:547048
|
||||
1690721280:548048
|
||||
1691025480:549048
|
||||
1691325720:550048
|
||||
1691633940:551048
|
||||
1691931480:552048
|
||||
1692233280:553048
|
||||
1692532620:554048
|
||||
1692843420:555048
|
||||
1693147020:556048
|
||||
1693453140:557048
|
||||
1693749540:558048
|
||||
1694054460:559048
|
||||
1694348940:560048
|
||||
1694661120:561048
|
||||
1694961240:562048
|
||||
1695270600:563048
|
||||
1695572880:564048
|
||||
1695891600:565048
|
||||
1696201200:566048
|
||||
1696506300:567048
|
||||
1696833360:568048
|
||||
1697119860:569048
|
||||
1697424660:570048
|
||||
1697728620:571048
|
||||
1698030720:572048
|
||||
1698328680:573048
|
||||
1698640800:574048
|
||||
1698955800:575048
|
||||
1699261560:576048
|
||||
1699561560:577048
|
||||
1699873800:578048
|
||||
1700185680:579048
|
||||
1700481900:580048
|
||||
1700801820:581048
|
||||
1701103860:582048
|
||||
1701414900:583048
|
||||
1701721080:584048
|
||||
1702027260:585048
|
||||
1702332300:586048
|
||||
1702641960:587048
|
||||
1602596617:255000
|
||||
1603053507:256500
|
||||
1603501419:258000
|
||||
1603951028:259500
|
||||
1604397604:261000
|
||||
1604844178:262500
|
||||
1605289509:264000
|
||||
1605742395:265500
|
||||
1606192218:267000
|
||||
1606638968:268500
|
||||
1607083718:270000
|
||||
1607539961:271500
|
||||
1607991764:273000
|
||||
1608439952:274500
|
||||
1608890342:276000
|
||||
1609334849:277500
|
||||
1609777306:279000
|
||||
1610229306:280500
|
||||
1610678028:282000
|
||||
1611129205:283500
|
||||
1611578787:285000
|
||||
1612024723:286500
|
||||
1612479368:288000
|
||||
1612925285:289500
|
||||
1613378445:291000
|
||||
1613818784:292500
|
||||
1614269634:294000
|
||||
1614718724:295500
|
||||
1615167437:297000
|
||||
1615619299:298500
|
||||
1616071879:300000
|
||||
1616517919:301500
|
||||
1616969191:303000
|
||||
1617419137:304500
|
||||
1617878649:306000
|
||||
1618306800:307500
|
||||
1618784409:309000
|
||||
1619210159:310500
|
||||
1619658329:312000
|
||||
1620103337:313500
|
||||
1620558729:315000
|
||||
1621002710:316500
|
||||
1621453789:318000
|
||||
1621895697:319500
|
||||
1622347075:321000
|
||||
1622790315:322500
|
||||
1623238613:324000
|
||||
1623686218:325500
|
||||
1624135035:327000
|
||||
1624588247:328500
|
||||
1625034084:330000
|
||||
1625387596:331500
|
||||
1625683505:333000
|
||||
1626206520:334500
|
||||
1626634887:336000
|
||||
1627067110:337500
|
||||
1627510005:339000
|
||||
1627915694:340500
|
||||
1628363606:342000
|
||||
1628827297:343500
|
||||
1629250950:345000
|
||||
1629688820:346500
|
||||
1630145454:348000
|
||||
1630591816:349500
|
||||
1631025927:351000
|
||||
1631515284:352500
|
||||
1631986172:354000
|
||||
1632422703:355500
|
||||
1632890624:357000
|
||||
1633326321:358500
|
||||
1633775768:360000
|
||||
1634233650:361500
|
||||
1634693889:363000
|
||||
1635139294:364500
|
||||
1635617950:366000
|
||||
1636079519:367500
|
||||
1636497795:369000
|
||||
1636997495:370500
|
||||
1637418065:372000
|
||||
1637897948:373500
|
||||
1638376658:375000
|
||||
1638790941:376500
|
||||
1639235224:378000
|
||||
1639757854:379500
|
||||
1640218491:381000
|
||||
1640646944:382500
|
||||
1641106951:384000
|
||||
1641544811:385500
|
||||
1641989328:387000
|
||||
1642443489:388500
|
||||
1643009648:390000
|
||||
1643455341:391500
|
||||
1643876044:393000
|
||||
1644333115:394500
|
||||
1644747412:396000
|
||||
1645196998:397500
|
||||
1645649589:399000
|
||||
1646107975:400500
|
||||
1646543179:402000
|
||||
1647017449:403500
|
||||
1647475207:405000
|
||||
1647881079:406500
|
||||
1648354637:408000
|
||||
1648816244:409500
|
||||
1649261814:411000
|
||||
1649734559:412500
|
||||
1650212215:414000
|
||||
1650656774:415500
|
||||
1651081594:417000
|
||||
1651543047:418500
|
||||
1651999579:420000
|
||||
1652464011:421500
|
||||
1652926487:423000
|
||||
1653376589:424500
|
||||
1653829783:426000
|
||||
1654287342:427500
|
||||
1654747411:429000
|
||||
1655193349:430500
|
||||
1655710653:432000
|
||||
1656134720:433500
|
||||
1656592570:435000
|
||||
1657054346:436500
|
||||
1657494432:438000
|
||||
1657939752:439500
|
||||
1658390870:441000
|
||||
1658822838:442500
|
||||
1659286786:444000
|
||||
1659718822:445500
|
||||
1660176142:447000
|
||||
1660639818:448500
|
||||
1661083112:450000
|
||||
1661517325:451500
|
||||
1661970076:453000
|
||||
1662446938:454500
|
||||
1662873827:456000
|
||||
1663336599:457500
|
||||
1663789611:459000
|
||||
1664221220:460500
|
||||
1664685037:462000
|
||||
1665126666:463500
|
||||
1665557872:465000
|
||||
1666034414:466500
|
||||
1666495833:468000
|
||||
1666929111:469500
|
||||
1667394182:471000
|
||||
1667835975:472500
|
||||
1668288340:474000
|
||||
1668766046:475500
|
||||
1669214111:477000
|
||||
1669665943:478500
|
||||
1670117908:480000
|
||||
1670586033:481500
|
||||
1671029494:483000
|
||||
1671465279:484500
|
||||
1671904526:486000
|
||||
1672376519:487500
|
||||
1672822751:489000
|
||||
1673269600:490500
|
||||
1673719076:492000
|
||||
1674182057:493500
|
||||
1674626828:495000
|
||||
1675085613:496500
|
||||
1675521264:498000
|
||||
1675977630:499500
|
||||
1676426332:501000
|
||||
1676890583:502500
|
||||
1677344883:504000
|
||||
1677795450:505500
|
||||
1678241156:507000
|
||||
1678685201:508500
|
||||
1679136670:510000
|
||||
1679592119:511500
|
||||
1680041240:513000
|
||||
1680537447:514500
|
||||
1680981131:516000
|
||||
1681434379:517500
|
||||
1681894205:519000
|
||||
1682345821:520500
|
||||
1682805333:522000
|
||||
1683258937:523500
|
||||
1683714088:525000
|
||||
1684168676:526500
|
||||
1684616632:528000
|
||||
1685076954:529500
|
||||
1685528631:531000
|
||||
1685980354:532500
|
||||
1686442998:534000
|
||||
1686893463:535500
|
||||
1687341403:537000
|
||||
1687800345:538500
|
||||
1688260852:540000
|
||||
1688713555:541500
|
||||
1689164934:543000
|
||||
1689616311:544500
|
||||
1690075687:546000
|
||||
1690533856:547500
|
||||
1690984681:549000
|
||||
1691444970:550500
|
||||
1691897459:552000
|
||||
1692352014:553500
|
||||
1692807402:555000
|
||||
1693259665:556500
|
||||
1693718708:558000
|
||||
1694169620:559500
|
||||
1694621215:561000
|
||||
1695073751:562500
|
||||
1695544139:564000
|
||||
1696010948:565500
|
||||
1696480073:567000
|
||||
1696934545:568500
|
||||
1697383183:570000
|
||||
1697850119:571500
|
||||
1698298303:573000
|
||||
1698763478:574500
|
||||
1699224283:576000
|
||||
1699683095:577500
|
||||
1700138177:579000
|
||||
1700593190:580500
|
||||
1701074336:582000
|
||||
1701528794:583500
|
||||
1701994567:585000
|
||||
1702441199:586500
|
||||
1702920637:588000
|
||||
1703388368:589500
|
||||
1703830741:591000
|
||||
1704284418:592500
|
||||
1704734515:594000
|
||||
1705184380:595500
|
||||
1705643726:597000
|
||||
1706087617:598500
|
||||
1706539628:600000
|
||||
1706999159:601500
|
||||
1707446328:603000
|
||||
1707920134:604500
|
||||
1708380012:606000
|
||||
1708845016:607500
|
||||
1709293588:609000
|
||||
1709748870:610500
|
||||
1710202528:612000
|
||||
1710647834:613500
|
||||
1711098409:615000
|
||||
1711550139:616500
|
||||
1712000662:618000
|
||||
1712465889:619500
|
||||
1712914193:621000
|
||||
1713366831:622500
|
||||
1713821520:624000
|
||||
1714275415:625500
|
||||
1714718068:627000
|
||||
1715165016:628500
|
||||
1715625858:630000
|
||||
1716087656:631500
|
||||
1716542805:633000
|
||||
1716996296:634500
|
||||
1717442953:636000
|
||||
1717899613:637500
|
||||
1718343607:639000
|
||||
1718798893:640500
|
||||
1719259600:642000
|
||||
1719715208:643500
|
||||
1720171971:645000
|
||||
1720619357:646500
|
||||
1721076296:648000
|
||||
1721532239:649500
|
||||
1721987052:651000
|
||||
1722437031:652500
|
||||
1722888953:654000
|
||||
1723343110:655500
|
||||
1723795513:657000
|
||||
1724247189:658500
|
||||
1724705009:660000
|
||||
1725165021:661500
|
||||
1725617360:663000
|
||||
1726071946:664500
|
||||
1726522439:666000
|
||||
1726979451:667500
|
||||
1727430507:669000
|
||||
1727884081:670500
|
||||
1728334947:672000
|
||||
1728789282:673500
|
||||
1729231059:675000
|
||||
1729687775:676500
|
||||
1730150813:678000
|
||||
1730607377:679500
|
||||
1731052869:681000
|
||||
1731502177:682500
|
||||
1731953584:684000
|
||||
1732397467:685500
|
||||
1732857103:687000
|
||||
1733305492:688500
|
||||
1733757932:690000
|
||||
1734213761:691500
|
||||
1734665858:693000
|
||||
1735109323:694500
|
||||
1735571098:696000
|
||||
1736017391:697500
|
||||
1736470274:699000
|
||||
1736922413:700500
|
||||
1737375374:702000
|
||||
1737822863:703500
|
||||
1738278318:705000
|
||||
1738734744:706500
|
||||
1739188167:708000
|
||||
1739646442:709500
|
||||
1740096952:711000
|
||||
1740548052:712500
|
||||
1741000180:714000
|
||||
1741453599:715500
|
||||
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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// SPDX-FileCopyrightText: The Monero Project
|
||||
|
||||
#include <QQuickWindow>
|
||||
#include <QSslSocket>
|
||||
|
||||
#include "Application.h"
|
||||
@@ -88,6 +89,14 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round);
|
||||
#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);
|
||||
|
||||
QApplication::setApplicationName("wowlet");
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -4,12 +4,24 @@
|
||||
#include "utils/DaemonManager.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QEventLoop>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#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/networktype.h"
|
||||
#include "utils/TorManager.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::errorOccurred, this, &DaemonManager::handleProcessError);
|
||||
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);
|
||||
@@ -46,18 +63,42 @@ void DaemonManager::start() {
|
||||
if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)
|
||||
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()) {
|
||||
this->setErrorMessage("Unable to unpack the embedded wownerod binary.");
|
||||
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)) {
|
||||
// Something is already listening on our RPC port; assume it is a usable local node.
|
||||
m_alreadyRunning = true;
|
||||
emit daemonStateChanged(true);
|
||||
return;
|
||||
if (this->verifyAdoptableNode()) {
|
||||
qDebug() << "Adopting existing wownerod on" << this->rpcAddress();
|
||||
m_alreadyRunning = true;
|
||||
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;
|
||||
if (m_restarts > 4) {
|
||||
this->setErrorMessage("wownerod failed to start: maximum retries exceeded");
|
||||
@@ -81,6 +122,14 @@ void DaemonManager::start() {
|
||||
arguments << "--no-igd"; // no UPnP port mapping
|
||||
arguments << "--out-peers" << "16";
|
||||
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.
|
||||
if (conf()->get(Config::pruneBlockchain).toBool())
|
||||
@@ -106,6 +155,7 @@ void DaemonManager::start() {
|
||||
qDebug() << QString("Starting wownerod: %1 %2").arg(this->daemonPath, arguments.join(" "));
|
||||
|
||||
m_process->start(this->daemonPath, arguments);
|
||||
ChildProcessGuard::adoptAfterStart(m_process); // wowlet: bind to kill-on-crash job (Windows)
|
||||
m_started = true;
|
||||
}
|
||||
|
||||
@@ -238,6 +288,95 @@ bool DaemonManager::shouldStartDaemon() {
|
||||
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) {
|
||||
this->errorMsg = msg;
|
||||
if (!msg.isEmpty())
|
||||
|
||||
@@ -59,6 +59,11 @@ private slots:
|
||||
|
||||
private:
|
||||
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);
|
||||
|
||||
QProcess *m_process;
|
||||
@@ -68,6 +73,7 @@ private:
|
||||
bool m_synced = false;
|
||||
bool m_stopping = false;
|
||||
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;
|
||||
};
|
||||
|
||||
+62
-19
@@ -6,6 +6,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDirIterator>
|
||||
|
||||
#include "utils/ChildProcessGuard.h" // wowlet: OS-level die-with-parent for bundled tor
|
||||
#include "utils/config.h"
|
||||
#include "utils/Utils.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::errorOccurred, this, &TorManager::handleProcessError);
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
auto state = m_process->state();
|
||||
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_started = false;
|
||||
}
|
||||
@@ -54,6 +66,8 @@ void TorManager::init() {
|
||||
}
|
||||
|
||||
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_started = false;
|
||||
}
|
||||
@@ -82,6 +96,17 @@ void TorManager::start() {
|
||||
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);
|
||||
|
||||
m_restarts += 1;
|
||||
@@ -102,6 +127,7 @@ void TorManager::start() {
|
||||
qDebug() << QString("%1 %2").arg(this->torPath, arguments.join(" "));
|
||||
|
||||
m_process->start(this->torPath, arguments);
|
||||
ChildProcessGuard::adoptAfterStart(m_process); // wowlet: bind to kill-on-crash job (Windows)
|
||||
m_started = true;
|
||||
}
|
||||
|
||||
@@ -201,37 +227,54 @@ bool TorManager::unpackBins() {
|
||||
return true;
|
||||
}
|
||||
|
||||
SemanticVersion embeddedVersion = SemanticVersion::fromString(QString(TOR_VERSION));
|
||||
SemanticVersion filesystemVersion = this->getVersion(torPath);
|
||||
qDebug() << QString("Tor versions: embedded %1, filesystem %2").arg(embeddedVersion.toString(), filesystemVersion.toString());
|
||||
if (SemanticVersion::isValid(filesystemVersion) && (embeddedVersion > filesystemVersion)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
// wowlet: QFile::copy never creates parent dirs; without this the copy loop can silently no-op on
|
||||
// a fresh profile, leaving an empty tor/ dir and "tor failed to start" (Lucas/Fedora). main.cpp
|
||||
// also mkpaths this, but make unpackBins() self-sufficient. Mirrors DaemonManager::unpackBins().
|
||||
QDir().mkpath(this->torDir);
|
||||
|
||||
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);
|
||||
while (it.hasNext()) {
|
||||
QString assetFile = it.next();
|
||||
QFileInfo assetFileInfo = QFileInfo(assetFile);
|
||||
QString filePath = QDir(this->torDir).filePath(QFileInfo(assetFile).fileName());
|
||||
QFile f(assetFile);
|
||||
QString filePath = QDir(this->torDir).filePath(assetFileInfo.fileName());
|
||||
f.copy(filePath);
|
||||
f.close();
|
||||
}
|
||||
qInfo() << "Wrote Tor binaries to: " << this->torDir;
|
||||
qInfo() << "Wrote Tor binaries to:" << this->torDir;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
QFile tor(this->torPath);
|
||||
tor.setPermissions(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther
|
||||
| QFile::ReadOwner | QFile::ReadGroup | QFile::ReadOther);
|
||||
// wowlet: only chmod if the binary actually landed.
|
||||
if (QFileInfo(this->torPath).isFile()) {
|
||||
QFile tor(this->torPath);
|
||||
tor.setPermissions(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther
|
||||
| QFile::ReadOwner | QFile::ReadGroup | QFile::ReadOther);
|
||||
}
|
||||
#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;
|
||||
return true;
|
||||
}
|
||||
@@ -310,7 +353,7 @@ bool TorManager::shouldStartTorDaemon() {
|
||||
SemanticVersion TorManager::getVersion(const QString &fileName) {
|
||||
QProcess process;
|
||||
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);
|
||||
QString output = process.readAllStandardOutput();
|
||||
|
||||
|
||||
@@ -115,7 +115,10 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
|
||||
// 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::socks5Host, {QS("socks5Host"), "127.0.0.1"}},
|
||||
{Config::socks5Port, {QS("socks5Port"), "9050"}},
|
||||
|
||||
@@ -330,6 +330,15 @@ void Nodes::autoConnect(bool forceReconnect) {
|
||||
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() {
|
||||
// Pick a node at random to connect to
|
||||
auto rtn = FeatherNode();
|
||||
|
||||
@@ -173,6 +173,9 @@ public slots:
|
||||
void onNodeSourceChanged(NodeSource nodeSource);
|
||||
void setCustomNodes(const QList<FeatherNode>& nodes);
|
||||
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:
|
||||
void onWalletRefreshed();
|
||||
|
||||
@@ -15,6 +15,10 @@ PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget
|
||||
, m_fields(fields)
|
||||
{
|
||||
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");
|
||||
|
||||
ui->label_version->setText(QString("wowlet %1 — by wownero.org").arg(FEATHER_VERSION));
|
||||
|
||||
Reference in New Issue
Block a user