Compare commits

...

46 Commits

Author SHA1 Message Date
sami7777 e8bf45af00 Bump version to 5.5.6
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
HTTPS seed fetch, hardcoded onion seeds, staking crash fix,
-zapwallettxes support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 03:20:31 -07:00
sami7777 80a39fa1de Upgrade seed fetch to HTTPS + add hardcoded onion seeds
The seeds.cryptographic-triangles.org endpoint uses Caddy with auto-TLS,
so the daemon's seed fetcher now connects over HTTPS (port 443) using
OpenSSL instead of plain HTTP (port 80) which got a 308 redirect.

Also hardcodes 5 known onion seed addresses in onionseed.h as a fallback
for initial peer discovery when the HTTPS endpoint is unreachable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 03:10:43 -07:00
sami7777 14abcc9746 Fix CI: use 64-bit inetc plugin for MSYS2 NSIS
MSYS2 mingw64 NSIS is a 64-bit build that needs amd64-unicode plugins.
Copy the amd64-unicode INetC.dll to Plugins/unicode/ instead of x86.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 01:25:27 -07:00
sami7777 096a4f9927 Fix CI: install inetc plugin for all NSIS architectures
- Copy INetC.dll to x86-unicode, x86-ansi, and amd64-unicode dirs
- Add debug output to identify which plugin dir NSIS actually uses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 01:15:11 -07:00
sami7777 c4949a6d4f Fix CI: test_GetThrow for UTXO model, install unzip for inetc
- transaction_tests: AreInputsStandard returns false (not throw) for missing inputs
- build-all.yml: install unzip package before extracting inetc plugin

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 00:33:59 -07:00
sami7777 557d5807d8 Fix CI: update transaction_tests for UTXO model, fix inetc plugin install
- transaction_tests.cpp: use COutPoint+CUtxoEntry instead of old MapPrevTx
- build-all.yml: use msys2 shell for inetc plugin download/install

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 00:24:23 -07:00
sami7777 014947580b Fix staking crash with large wallets + add -zapwallettxes
- ThreadStakeMiner: catch-and-retry instead of crash on exception
  (boost::bad_weak_ptr no longer kills the daemon)
- GetStakeWeight: take wallet lock once instead of per-coin to
  reduce lock contention with 20K+ transaction wallets
- StakeMiner: continue instead of exit when CreateNewBlock fails
- Wrap all NotifyTransactionChanged/NotifyAddressBookChanged signal
  emissions in try/catch to absorb stale slot exceptions
- Add -zapwallettxes flag: strips all tx records from wallet.dat
  keeping only keys, then rescans blockchain to rebuild history

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 23:12:21 -07:00
sami7777 64db028788 Fix block relay stall + revert protocol to 70205
Ask peers for blocks whenever they report a higher chain height,
fixing post-IBD sync stall where node stops requesting missing blocks
after initial sync completes.

Revert protocol version from 70206 back to 70205 to match network.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 22:40:48 -07:00
sami7777 e1ef89a169 Fix CI: install NSIS inetc plugin, update test for UTXO model
- Download and install inetc NSIS plugin for bootstrap download feature
- test/script_P2SH_tests.cpp already updated in prior commit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 22:22:13 -07:00
sami7777 6a9b710b18 Restore NSIS bootstrap installer page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 22:21:38 -07:00
sami7777 22e888dd47 Fix CI: update test for UTXO model, remove bootstrap from installer
- Update script_P2SH_tests.cpp to use new MapPrevTx (COutPoint->CUtxoEntry)
- Remove obsolete bootstrap download from NSIS installer (requires inetc
  plugin; nodes now sync fast from network)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 22:21:02 -07:00
sami7777 d8fb2b7d7d v5.5.5: UTXO model, fast startup, lazy DB migration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 22:06:39 -07:00
sami7777 f5a5ebb204 UTXO database model + startup performance optimizations
Replace per-transaction CTxIndex spent tracking with per-output UTXO
database (CUtxoEntry). ConnectBlock writes/erases UTXOs as blocks are
processed. FetchInputs reads directly from UTXO DB instead of
deserializing full transactions from disk.

Persist nChainTrust in block index (dbformat v3) to skip expensive
recalculation on every startup. Only populate setStakeSeen for last
500 blocks instead of all 2M+.

Lazy fallback to old CTxIndex path for databases upgrading from
pre-UTXO format - no big-bang migration required.

Fixes pre-existing bugs in introdialog.cpp (extra brace) and
net_bootstrap.cpp (namespace extern).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 22:01:37 -07:00
sami7777 42a33457bf v5.6.0 Tor-native: embed Tor, force .onion-only networking
Every Triangles wallet is now a Tor node. Staking rewards subsidize
Tor infrastructure.

Core changes:
- Embedded Tor 0.4.9.6 as git submodule
- ConnectNode rejects all non-.onion peers
- Tor failure is fatal - wallet requires Tor to operate
- All proxies forced through embedded Tor SOCKS
- Clearnet (IPv4/IPv6) disabled at startup
- HTTP seed fetch routes through Tor proxy (removed boost::asio dep)
- Merged PoW cleanup: -623 lines of dead mining code
- Stripped dead LEGACY/MIXED bootstrap modes from net_bootstrap
- RPC getnetworkinfo reports tor_native mode

Tooling:
- scripts/bump-version.sh syncs version across all 17+ files
- Version bumped to 5.6.0 across all packaging manifests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 17:55:41 -07:00
sami7777 279d643582 Remove dead PoW mining code to reduce antivirus false positives
Strip getwork, getworkex, getblocktemplate, submitblock RPC commands
and their helper functions (SHA256Transform, FormatHashBlocks,
FormatHashBuffers, IncrementExtraNonce, CheckWork) which have been
dead code since PoW ended at block 9000. AV engines pattern-match
these nonce-incrementing loops and mining pool interfaces as
cryptominer signatures. Block validation (CheckProofOfWork) and
Hash9 algorithm files are preserved - only block *creation* for
PoW mining is removed. PoS staking code is untouched.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 17:53:35 -07:00
Krystie baa38340a6 Add version bump script (scripts/bump-version.sh)
Single command to update version across all 12+ files:
  scripts/bump-version.sh 5.7.0

Updates: clientversion.h, version.h, triangles-qt.pro, Dockerfile,
and all packaging manifests (Docker, AUR, Chocolatey, Debian, RPM,
WinGet, Homebrew, Nix, AppImage).
2026-04-03 17:51:26 -07:00
Krystie fb4c0708bf v5.5.5: Auto-bootstrap for new nodes - zero config required
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
- Daemon: automatically downloads blockchain snapshot when no data exists
  No -bootstrap flag needed. Use -nobootstrap to skip.
- Qt wallet: auto-bootstraps on first run (no question asked)
  Existing users still get the optional re-download prompt.
- New users just install and run - blockchain downloads automatically
- Works on all platforms (Windows, Linux, macOS, ARM64)
2026-04-03 16:18:31 -07:00
Krystie cfaf742053 Revert hardcoded seed nodes - peer discovery is dynamic 2026-04-03 16:10:07 -07:00
Krystie 308f8a5f5c v5.5.4: Add hardcoded seed nodes for automatic network mesh
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
- Hardcoded DNS3 (74.208.167.19), DNS2 (194.233.88.206), and Contabo (100.98.123.59) as fixed seeds
- Nodes will automatically connect to these on first run
- No manual addnode configuration needed
- Full mesh network connectivity built into the code
2026-04-03 15:55:51 -07:00
Krystie 069f42d6d0 v5.5.3: Per-user installer (no UAC), network drive support, branding
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
2026-04-03 00:58:39 -07:00
Krystie 6c87931901 Triangles branding + fix Qt platform plugin for Windows installer
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
2026-04-03 00:10:32 -07:00
Krystie b31d8d08dd Bump clientversion.h to 5.5.2 (match tag) 2026-04-02 23:38:52 -07:00
Krystie e3705a66b8 Install NSIS via MSYS2 pacman (SourceForge downloads unreliable in CI) 2026-04-02 23:07:04 -07:00
Krystie f0a2c0e237 Use NSIS portable zip instead of installer (corrupted download fix) 2026-04-02 22:57:45 -07:00
Krystie 7120df3989 Fully self-contained on ALL platforms — zero external dependencies
Linux Qt .deb: bundles all .so files + LD_LIBRARY_PATH wrapper
Linux daemon .deb: same + systemd Environment= for LD_LIBRARY_PATH
Windows: already handled (ldd scan for DLLs)
macOS: already handled (install_name_tool into Frameworks)

Removed all Depends: from .deb control files. Every package
runs on a clean machine with nothing pre-installed.
2026-04-02 22:48:31 -07:00
Krystie a031795aea Bundle ALL runtime libraries for every platform
Windows Qt: ldd scan copies every MSYS2 DLL into installer
Windows daemon: ships with DLLs + Tor in a zip
macOS: copies Homebrew dylibs into .app/Frameworks with install_name_tool
Linux: unchanged (.deb Depends handles it via apt)
2026-04-02 22:46:41 -07:00
Krystie 3eb436bcd2 Install NSIS directly from SourceForge instead of Chocolatey
Chocolatey had a 503 outage. Direct download is more reliable for CI.
2026-04-02 22:43:48 -07:00
Krystie 6ca0770d72 Fix Tor download: use dist.torproject.org v15.0.8 (14.0.8 was 404) 2026-04-02 22:28:38 -07:00
Krystie 7e8ae1a25b Proper installers for all platforms
Windows: NSIS setup.exe — double-click to install with Start Menu
  shortcuts, desktop icon, uninstaller in Add/Remove Programs.
  Tor bundled in tor/ subfolder, auto-detected by wallet.

Linux: .deb packages (dpkg -i) for both Qt wallet and daemon.
  Wallet gets desktop entry + app icon. Daemon gets systemd service.
  Tor bundled in /usr/lib/cryptographic-triangles/tor/.

macOS: DMG with Tor inside .app bundle (unchanged).

All platforms: download one file, install, run. Zero configuration.
2026-04-02 22:11:29 -07:00
Krystie 552809e359 Bundle Tor Expert Bundle in all platform releases
Every release now ships with Tor integrated:
- Windows Qt/daemon: tor.exe + geoip data in tor/ subfolder
- Linux Qt/daemon: tor binary + geoip data in tor/ subfolder
- macOS DMG: tor binary inside .app/Contents/MacOS/tor/

The wallet auto-detects tor in the tor/ subfolder next to the binary.
No user configuration needed - Tor starts automatically on launch.

Release assets now packaged as archives (zip/tar.gz) to include
the tor/ directory alongside the wallet binary.
2026-04-02 21:58:32 -07:00
Krystie 099f78efea Bundle Tor binary with all platform releases
Every release now ships with the Tor Expert Bundle included:
- Windows Qt: tor/ directory alongside triangles-qt.exe
- Windows daemon: tor/ directory alongside trianglesd.exe
- Linux Qt: tor/ directory in release tarball
- Linux daemon: tor/ directory in release tarball
- macOS: tor/ inside .app bundle (Contents/MacOS/tor/)

The wallet already auto-detects tor binary next to itself or in
a tor/ subfolder. Zero configuration needed for users - Tor starts
automatically with the wallet and stops when it exits.

Release assets now packaged as zip/tar.gz to include tor directory.
2026-04-02 21:55:33 -07:00
Krystie 207e1ed676 Fix Tor v3 onion address checksum: SHA-256 -> SHA3-256
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
The Tor v3 spec requires SHA3-256 (FIPS-202) for the .onion address
checksum computation, but ToStringIP() was using SHA-256 (double-hash).
This caused every reconstructed .onion address to have incorrect suffix
characters, making all outbound Tor connections fail with SOCKS5 'general
failure' - the entire network had 0 Tor peers despite working Tor instances.

Fix: Replace Hash() call with OpenSSL EVP_sha3_256() which is available
in OpenSSL 3.0+ and produces the correct FIPS-202 SHA3-256 checksum.

Tested: All 5 onion seed nodes now connect successfully.
2026-04-02 14:16:41 -07:00
sami7777 2fba88bfc5 Fix LookupHost call to use vector overload in HTTP seed fetch
LookupHost expects std::vector<CNetAddr>& but was passed a single
CNetAddr, breaking compilation on all platforms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:52:06 -07:00
sami7777 4563e7952b Add dynamic HTTP seed discovery, remove hardcoded seeds (v5.5.0)
Build All Platforms / build-linux-qt (push) Failing after 3h0m3s
Build All Platforms / test-linux-unit (push) Failing after 3h0m4s
Build All Platforms / build-linux-daemon (push) Failing after 21s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Replace all hardcoded seed addresses (onion, clearnet, DNS) with a
dynamic HTTP-based seed list fetched from seeds.cryptographic-triangles.org
on startup. New getseedlist RPC exposes known .onion peers from the
address manager for a collector script to publish.

Any wallet that comes online with an onion address is automatically
discovered by peers via P2P addr exchange and appears in the seed list
within minutes. No binary rebuilds needed when addresses change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:25:43 -07:00
sami7777 ea86ab077c Optimize wallet rescan and address indexing during IBD
Move wallet rescan to a background thread after IBD completes instead
of blocking on the main thread. Address index is now built during IBD
rather than skipped and rebuilt later. Wallet scan releases cs_wallet
lock while reading blocks from disk to improve concurrency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:10:00 -07:00
Krystie a1b137a7bb Bump version to 5.4.4 - Updated seed nodes
Build All Platforms / build-linux-qt (push) Failing after 7s
Build All Platforms / build-linux-daemon (push) Failing after 1h10m35s
Build All Platforms / test-linux-unit (push) Failing after 1h10m43s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
2026-03-31 17:07:54 -07:00
Krystie 939606a5f7 Update Docker seed node onion addresses (contabo-de deployment) 2026-03-31 02:32:41 -07:00
Krystie 73cecd90d1 Add v3 onion seed nodes for network bootstrap
Added 5 new .onion v3 seed addresses:
- DNS3 main node
- 4 Docker-based seed nodes running on DNS2

Total seed count: 2 -> 7 onion seeds for improved
network connectivity and peer discovery.
2026-03-30 19:08:43 -07:00
sami7777 c74c92c542 Fix Boost filesystem API for modern Boost (copy_options)
Build All Platforms / test-linux-unit (push) Waiting to run
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-linux-daemon (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
copy_option::overwrite_if_exists was removed in Boost 1.90+,
replaced with copy_options::overwrite_existing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 12:49:17 -07:00
sami7777 97ae675f0a Bump version to v5.4.3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 12:40:33 -07:00
sami7777 b0b591364f Raise MAX_MONEY from 222222 to 2222222
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 12:38:22 -07:00
sami7777 bf257858a4 Add data directory change feature to Options dialog
Adds a "Data Directory" section to Options > Main tab that lets users
browse for a new data directory. On confirmation, files are automatically
migrated to the new location on restart (wallet.dat copied first with
atomic rename for safety). Supports "Restart Now" or "Later" workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 12:38:15 -07:00
sami7777 16611efe72 Bump version to v5.4.2
Build All Platforms / test-linux-unit (push) Waiting to run
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-linux-daemon (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 01:00:43 -07:00
sami7777 1f3deacb7a Fix persistent PoS chain forks with deterministic tiebreaker and tighter timestamps
PoS blocks at the same height have identical difficulty, producing equal chain
trust scores. The old "strictly greater" comparison meant first-seen-wins,
causing permanent forks when nodes received competing blocks in different order.

v5.4 fork (block 2186941) adds:
- Deterministic tiebreaker: equal-trust chains resolve to the lower tip hash
- Tighter time drift: ±3 min (was ±10 min), reducing the competing block window

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 00:59:38 -07:00
SamiAhmed7777 8847571193 Merge pull request #3 from SamiAhmed7777/fix/version-detection
Fix version detection: prioritize exact tag match in genbuild.sh
2026-03-27 23:03:53 -07:00
Krystie a58eb3e9ef Fix version detection: prioritize exact tag match in genbuild.sh
When building from a release tag (e.g. v5.4.1), git describe was finding
the nearest ancestor tag (v5.3.8) instead of the exact tag, resulting in
version strings like 'v5.3.8-9-gdfb4b22' instead of 'v5.4.1'.

Now genbuild.sh tries --exact-match first, falling back to distance-based
describe only when not on a tagged commit.
2026-03-27 23:02:04 -07:00
64 changed files with 3068 additions and 1561 deletions
+327 -30
View File
@@ -63,6 +63,13 @@ jobs:
ln -sf /mingw64/bin/lrelease-qt5.exe /mingw64/bin/lrelease.exe 2>/dev/null || true
ln -sf /mingw64/bin/windeployqt-qt5.exe /mingw64/bin/windeployqt.exe 2>/dev/null || true
- name: Set VERSION from source
run: |
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
- name: Clean stale build artifacts
run: rm -rf build/*.o build/*.h
@@ -73,31 +80,110 @@ jobs:
CC=gcc CXX=g++ TARGET_OS=OS_WINDOWS_CROSSCOMPILE make OPT="-fno-keep-inline-dllexport -march=nocona -msahf -mtune=generic -Wa,-mbig-obj -O2" libleveldb.a libmemenv.a
- name: Run qmake
run: |
qmake triangles-qt.pro "RELEASE=1"
run: qmake triangles-qt.pro "RELEASE=1"
- name: Build
run: |
make -j$(nproc)
run: make -j$(nproc)
- name: Package
run: |
mkdir -p dist
cp release/triangles-qt.exe dist/
windeployqt dist/triangles-qt.exe || true
# Copy runtime DLLs
# Copy ALL runtime DLLs the binary needs
# MinGW runtime
for dll in libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll; do
cp /mingw64/bin/$dll dist/ 2>/dev/null || true
done
# Boost
for dll in /mingw64/bin/libboost_system*.dll /mingw64/bin/libboost_filesystem*.dll \
/mingw64/bin/libboost_thread*.dll /mingw64/bin/libboost_program_options*.dll \
/mingw64/bin/libboost_chrono*.dll; do
cp $dll dist/ 2>/dev/null || true
done
# OpenSSL
for dll in /mingw64/bin/libssl*.dll /mingw64/bin/libcrypto*.dll; do
cp $dll dist/ 2>/dev/null || true
done
# BerkeleyDB, libevent, miniupnpc, zlib
for dll in /mingw64/bin/libdb*.dll /mingw64/bin/libevent*.dll \
/mingw64/bin/libminiupnpc*.dll /mingw64/bin/zlib1.dll; do
cp $dll dist/ 2>/dev/null || true
done
- name: Strip binary
run: strip --strip-all dist/triangles-qt.exe
# Catch anything we missed: scan ldd output for /mingw64 deps
ldd dist/triangles-qt.exe | grep '/mingw64' | awk '{print $3}' | while read dll; do
cp "$dll" dist/ 2>/dev/null || true
done
- name: Upload artifact
# Write qt.conf so the exe finds plugins relative to itself
printf '[Paths]\nPlugins = .\n' > dist/qt.conf
# Ensure Qt platform plugins are present (windeployqt sometimes misses them in MSYS2)
if [ ! -f dist/platforms/qwindows.dll ]; then
echo "WARNING: windeployqt did not copy platform plugins, copying manually..."
mkdir -p dist/platforms
cp /mingw64/share/qt5/plugins/platforms/qwindows.dll dist/platforms/ 2>/dev/null || \
cp /mingw64/lib/qt5/plugins/platforms/qwindows.dll dist/platforms/ 2>/dev/null || \
find /mingw64 -name 'qwindows.dll' -exec cp {} dist/platforms/ \; 2>/dev/null
fi
# Also copy styles and imageformats for good measure
for plugdir in styles imageformats; do
if [ ! -d "dist/$plugdir" ]; then
srcdir=$(find /mingw64 -type d -name "$plugdir" -path "*/plugins/*" 2>/dev/null | head -1)
if [ -n "$srcdir" ]; then
cp -r "$srcdir" dist/
fi
fi
done
strip --strip-all dist/triangles-qt.exe
echo "=== dist/ contents ==="
find dist/ -type f | head -50
- name: Download Tor
shell: powershell
run: |
$TOR_VERSION = "15.0.8"
$TOR_URL = "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz"
Invoke-WebRequest -Uri $TOR_URL -OutFile tor-bundle.tar.gz
New-Item -ItemType Directory -Path tor-extract -Force
tar -xzf tor-bundle.tar.gz -C tor-extract
New-Item -ItemType Directory -Path tor-files -Force
Copy-Item tor-extract/tor/tor.exe tor-files/
Copy-Item tor-extract/tor/tor-gencert.exe tor-files/ -ErrorAction SilentlyContinue
if (Test-Path tor-extract/tor/pluggable_transports) {
Copy-Item -Recurse tor-extract/tor/pluggable_transports tor-files/
}
if (Test-Path tor-extract/data) {
Copy-Item -Recurse tor-extract/data tor-files/data
}
- name: Install NSIS via MSYS2
run: pacman -S --noconfirm mingw-w64-x86_64-nsis
- name: Install NSIS inetc plugin
run: |
pacman -S --noconfirm unzip
NSIS_DIR="/mingw64/share/nsis"
cd /tmp
curl -L -o Inetc.zip "https://nsis.sourceforge.io/mediawiki/images/c/c9/Inetc.zip"
unzip -o Inetc.zip -d inetc_extract
# MSYS2 mingw64 NSIS is 64-bit, needs amd64-unicode plugin in Plugins/unicode/
mkdir -p "$NSIS_DIR/Plugins/unicode"
cp inetc_extract/Plugins/amd64-unicode/INetC.dll "$NSIS_DIR/Plugins/unicode/"
echo "Installed 64-bit INetC.dll to $NSIS_DIR/Plugins/unicode/"
- name: Build NSIS installer
run: makensis //DVERSION=$VERSION contrib/nsis/setup.nsi
- name: Upload installer
uses: actions/upload-artifact@v4
with:
name: windows-qt
path: dist/
name: windows-qt-setup
path: contrib/nsis/Cryptographic-Triangles-*-setup.exe
build-windows-daemon:
runs-on: windows-latest
@@ -133,13 +219,34 @@ jobs:
mkdir -p obj
make -f makefile.mingw DEPSDIR=/mingw64 all -j$(nproc) 2>&1
strip --strip-all trianglesd.exe
cp trianglesd.exe ../trianglesd.exe
- name: Package daemon with DLLs
run: |
mkdir -p daemon-dist/tor
cp src/trianglesd.exe daemon-dist/
# Copy all linked DLLs from MSYS2
ldd src/trianglesd.exe | grep '/mingw64' | awk '{print $3}' | while read dll; do
cp "$dll" daemon-dist/ 2>/dev/null || true
done
- name: Bundle Tor for daemon
shell: powershell
run: |
$TOR_VERSION = "15.0.8"
Invoke-WebRequest -Uri "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz" -OutFile tor-bundle.tar.gz
New-Item -ItemType Directory -Path tor-extract -Force
tar -xzf tor-bundle.tar.gz -C tor-extract
Copy-Item tor-extract/tor/tor.exe daemon-dist/tor/
if (Test-Path tor-extract/data) {
Copy-Item -Recurse tor-extract/data daemon-dist/tor/data
}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: windows-daemon
path: trianglesd.exe
path: daemon-dist/
build-linux-qt:
runs-on: ubuntu-22.04
@@ -179,14 +286,83 @@ jobs:
- name: Strip binary
run: strip --strip-all triangles-qt
- name: Rename
run: mv triangles-qt Cryptographic-Triangles-v${VERSION}-linux-x64-qt
- name: Build .deb package (fully self-contained)
run: |
TOR_VERSION="15.0.8"
curl -sL "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-linux-x86_64-${TOR_VERSION}.tar.gz" -o tor-bundle.tar.gz
mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract
- name: Upload artifact
PKG="cryptographic-triangles_${VERSION}_amd64"
mkdir -p ${PKG}/DEBIAN
mkdir -p ${PKG}/usr/lib/cryptographic-triangles/lib
mkdir -p ${PKG}/usr/lib/cryptographic-triangles/tor
mkdir -p ${PKG}/usr/bin
mkdir -p ${PKG}/usr/share/applications
mkdir -p ${PKG}/usr/share/pixmaps
cp triangles-qt ${PKG}/usr/lib/cryptographic-triangles/
cp tor-extract/tor/tor ${PKG}/usr/lib/cryptographic-triangles/tor/
chmod +x ${PKG}/usr/lib/cryptographic-triangles/tor/tor
[ -d tor-extract/data ] && cp -r tor-extract/data ${PKG}/usr/lib/cryptographic-triangles/tor/data
# Bundle ALL shared library dependencies (except glibc/kernel)
ldd triangles-qt | grep '=> /' | awk '{print $3}' | while read lib; do
case "$lib" in
/lib/x86_64-linux-gnu/libc.so*|/lib/x86_64-linux-gnu/libm.so*|/lib/x86_64-linux-gnu/libpthread.so*|/lib/x86_64-linux-gnu/libdl.so*|/lib/x86_64-linux-gnu/librt.so*|/lib/x86_64-linux-gnu/ld-linux*|/lib64/ld-linux*)
;; # Skip glibc core — always present
*)
cp -L "$lib" ${PKG}/usr/lib/cryptographic-triangles/lib/ 2>/dev/null || true
;;
esac
done
echo "=== Bundled libs ==="
ls ${PKG}/usr/lib/cryptographic-triangles/lib/ | wc -l
ls ${PKG}/usr/lib/cryptographic-triangles/lib/
# Launcher with LD_LIBRARY_PATH
cat > ${PKG}/usr/bin/cryptographic-triangles << 'LAUNCHER'
#!/bin/bash
INSTALL_DIR=/usr/lib/cryptographic-triangles
export LD_LIBRARY_PATH="${INSTALL_DIR}/lib:${LD_LIBRARY_PATH}"
exec "${INSTALL_DIR}/triangles-qt" "$@"
LAUNCHER
sed -i 's/^ //' ${PKG}/usr/bin/cryptographic-triangles
chmod +x ${PKG}/usr/bin/cryptographic-triangles
cat > ${PKG}/usr/share/applications/cryptographic-triangles.desktop << 'DESKTOP'
[Desktop Entry]
Name=Cryptographic Triangles
Comment=Triangles Cryptocurrency Wallet
Exec=cryptographic-triangles
Terminal=false
Type=Application
Icon=cryptographic-triangles
Categories=Finance;Network;
DESKTOP
sed -i 's/^ //' ${PKG}/usr/share/applications/cryptographic-triangles.desktop
cp src/qt/res/icons/triangles.ico ${PKG}/usr/share/pixmaps/cryptographic-triangles.ico 2>/dev/null || true
cat > ${PKG}/DEBIAN/control << CTRL
Package: cryptographic-triangles
Version: ${VERSION}
Architecture: amd64
Maintainer: Cryptographic Triangles <dev@cryptographic-triangles.org>
Description: Cryptographic Triangles wallet with integrated Tor
Fully self-contained wallet with all libraries and Tor bundled.
No external dependencies required — runs on any x86_64 Linux.
Section: finance
Priority: optional
CTRL
sed -i 's/^ //' ${PKG}/DEBIAN/control
dpkg-deb --build ${PKG}
- name: Upload .deb
uses: actions/upload-artifact@v4
with:
name: linux-qt
path: Cryptographic-Triangles-v*-linux-x64-qt
name: linux-qt-deb
path: cryptographic-triangles_*_amd64.deb
build-linux-daemon:
runs-on: ubuntu-22.04
@@ -222,14 +398,97 @@ jobs:
- name: Strip binary
run: strip --strip-all src/trianglesd
- name: Rename
run: mv src/trianglesd Cryptographic-Triangles-v${VERSION}-linux-x64-daemon
- name: Build .deb package (fully self-contained)
run: |
TOR_VERSION="15.0.8"
curl -sL "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-linux-x86_64-${TOR_VERSION}.tar.gz" -o tor-bundle.tar.gz
mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract
- name: Upload artifact
PKG="cryptographic-triangles-daemon_${VERSION}_amd64"
mkdir -p ${PKG}/DEBIAN
mkdir -p ${PKG}/usr/lib/cryptographic-triangles/lib
mkdir -p ${PKG}/usr/lib/cryptographic-triangles/tor
mkdir -p ${PKG}/usr/bin
mkdir -p ${PKG}/etc/systemd/system
cp src/trianglesd ${PKG}/usr/lib/cryptographic-triangles/
cp tor-extract/tor/tor ${PKG}/usr/lib/cryptographic-triangles/tor/
chmod +x ${PKG}/usr/lib/cryptographic-triangles/tor/tor
[ -d tor-extract/data ] && cp -r tor-extract/data ${PKG}/usr/lib/cryptographic-triangles/tor/data
# Bundle ALL shared library dependencies (except glibc/kernel)
ldd src/trianglesd | grep '=> /' | awk '{print $3}' | while read lib; do
case "$lib" in
/lib/x86_64-linux-gnu/libc.so*|/lib/x86_64-linux-gnu/libm.so*|/lib/x86_64-linux-gnu/libpthread.so*|/lib/x86_64-linux-gnu/libdl.so*|/lib/x86_64-linux-gnu/librt.so*|/lib/x86_64-linux-gnu/ld-linux*|/lib64/ld-linux*)
;; # Skip glibc core — always present
*)
cp -L "$lib" ${PKG}/usr/lib/cryptographic-triangles/lib/ 2>/dev/null || true
;;
esac
done
echo "=== Bundled libs ==="
ls ${PKG}/usr/lib/cryptographic-triangles/lib/ | wc -l
ls ${PKG}/usr/lib/cryptographic-triangles/lib/
# Launcher with LD_LIBRARY_PATH
cat > ${PKG}/usr/bin/trianglesd << 'LAUNCHER'
#!/bin/bash
INSTALL_DIR=/usr/lib/cryptographic-triangles
export LD_LIBRARY_PATH="${INSTALL_DIR}/lib:${LD_LIBRARY_PATH}"
exec "${INSTALL_DIR}/trianglesd" "$@"
LAUNCHER
sed -i 's/^ //' ${PKG}/usr/bin/trianglesd
chmod +x ${PKG}/usr/bin/trianglesd
cat > ${PKG}/etc/systemd/system/trianglesd.service << 'SVC'
[Unit]
Description=Cryptographic Triangles Daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Environment=LD_LIBRARY_PATH=/usr/lib/cryptographic-triangles/lib
ExecStart=/usr/lib/cryptographic-triangles/trianglesd
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
SVC
sed -i 's/^ //' ${PKG}/etc/systemd/system/trianglesd.service
cat > ${PKG}/DEBIAN/control << CTRL
Package: cryptographic-triangles-daemon
Version: ${VERSION}
Architecture: amd64
Maintainer: Cryptographic Triangles <dev@cryptographic-triangles.org>
Description: Cryptographic Triangles daemon with integrated Tor
Fully self-contained headless node with all libraries, Tor, and systemd service.
No external dependencies required — runs on any x86_64 Linux.
Section: finance
Priority: optional
CTRL
sed -i 's/^ //' ${PKG}/DEBIAN/control
cat > ${PKG}/DEBIAN/postinst << 'POST'
#!/bin/bash
systemctl daemon-reload
echo ""
echo "Cryptographic Triangles daemon installed."
echo " Start: sudo systemctl start trianglesd"
echo " On boot: sudo systemctl enable trianglesd"
echo ""
POST
chmod +x ${PKG}/DEBIAN/postinst
dpkg-deb --build ${PKG}
- name: Upload .deb
uses: actions/upload-artifact@v4
with:
name: linux-daemon
path: Cryptographic-Triangles-v*-linux-x64-daemon
name: linux-daemon-deb
path: cryptographic-triangles-daemon_*_amd64.deb
build-macos:
runs-on: macos-15
@@ -284,6 +543,43 @@ jobs:
export PATH="/opt/homebrew/opt/qt@5/bin:$PATH"
macdeployqt Triangles-Qt.app -verbose=1
- name: Bundle non-Qt dylibs into app
run: |
FRAMEWORKS="Triangles-Qt.app/Contents/Frameworks"
MACOS="Triangles-Qt.app/Contents/MacOS"
BINARY="$MACOS/Triangles-Qt"
# Copy Homebrew dylibs that macdeployqt doesn't handle
for lib in boost_system boost_filesystem boost_thread boost_program_options boost_chrono; do
DYLIB=$(otool -L "$BINARY" | grep "$lib" | awk '{print $1}')
if [ -n "$DYLIB" ] && [ -f "$DYLIB" ]; then
cp "$DYLIB" "$FRAMEWORKS/"
BASENAME=$(basename "$DYLIB")
install_name_tool -change "$DYLIB" "@executable_path/../Frameworks/$BASENAME" "$BINARY"
fi
done
for lib in libssl libcrypto libevent libdb_cxx libminiupnpc libsodium; do
DYLIB=$(otool -L "$BINARY" | grep "$lib" | awk '{print $1}')
if [ -n "$DYLIB" ] && [ -f "$DYLIB" ]; then
cp "$DYLIB" "$FRAMEWORKS/"
BASENAME=$(basename "$DYLIB")
install_name_tool -change "$DYLIB" "@executable_path/../Frameworks/$BASENAME" "$BINARY"
fi
done
echo "=== Final dylib dependencies ==="
otool -L "$BINARY" | head -30
- name: Bundle Tor into app
run: |
TOR_VERSION="15.0.8"
curl -sL "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-macos-aarch64-${TOR_VERSION}.tar.gz" -o tor-bundle.tar.gz
mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract
mkdir -p Triangles-Qt.app/Contents/MacOS/tor
cp tor-extract/tor/tor Triangles-Qt.app/Contents/MacOS/tor/
chmod +x Triangles-Qt.app/Contents/MacOS/tor/tor
[ -d tor-extract/data ] && cp -r tor-extract/data Triangles-Qt.app/Contents/MacOS/tor/data
- name: Create DMG
run: |
mkdir -p dmg_contents
@@ -318,14 +614,15 @@ jobs:
- name: Prepare release assets
run: |
mkdir -p release
# Windows
cd artifacts/windows-qt && zip -r ../../release/Cryptographic-Triangles-${VERSION}-win-x64.zip . && cd ../..
cp artifacts/windows-qt/triangles-qt.exe release/Cryptographic-Triangles-${VERSION}-win-x64-qt.exe
cp artifacts/windows-daemon/trianglesd.exe release/Cryptographic-Triangles-${VERSION}-win-x64-daemon.exe
# Linux
cp artifacts/linux-qt/Cryptographic-Triangles-* release/
cp artifacts/linux-daemon/Cryptographic-Triangles-* release/
# macOS
# Windows Qt installer (setup.exe — includes Tor, Start Menu shortcuts, uninstaller)
cp artifacts/windows-qt-setup/*.exe release/
# Windows daemon (zip with DLLs + Tor)
cd artifacts/windows-daemon && zip -r "../../release/Cryptographic-Triangles-${VERSION}-win-x64-daemon.zip" . && cd ../..
# Linux Qt .deb (dpkg -i to install — includes Tor, desktop entry, icon)
cp artifacts/linux-qt-deb/*.deb release/
# Linux daemon .deb (dpkg -i to install — includes Tor, systemd service)
cp artifacts/linux-daemon-deb/*.deb release/
# macOS DMG (drag to Applications — Tor inside .app bundle)
cp artifacts/macos-arm64-dmg/*.dmg release/
ls -la release/
+3
View File
@@ -0,0 +1,3 @@
[submodule "src/tor/tor-src"]
path = src/tor/tor-src
url = https://gitlab.torproject.org/tpo/core/tor.git
+3 -2
View File
@@ -2,7 +2,7 @@ FROM ubuntu:22.04
LABEL maintainer="Cryptographic Triangles Team"
LABEL description="Cryptographic Triangles (TRI) headless daemon"
LABEL version="5.1.5"
LABEL version="5.6.0"
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
@@ -19,8 +19,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
tor \
&& rm -rf /var/lib/apt/lists/*
ARG VERSION=5.6.0
RUN curl -L -o /usr/local/bin/trianglesd \
https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.1.5/trianglesd-linux \
https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/Cryptographic-Triangles-v${VERSION}-linux-x64-daemon \
&& chmod +x /usr/local/bin/trianglesd
RUN useradd -m -s /bin/bash triangles
+284
View File
@@ -0,0 +1,284 @@
# Triangles Tor-Native Architecture
**Date:** 2026-03-26
**Status:** ✅ IMPLEMENTED & WORKING
---
## What This Is
Triangles is now a **Tor-native proof-of-stake network** where:
- **Every node = Tor hidden service** (.onion address)
- **All P2P traffic = routed through Tor** (mandatory SOCKS5)
- **Zero clearnet connections** (IPv4/IPv6 disabled)
- **Network-layer anonymity = enforced by design**
This is not "Tor support" or "Tor optional" — this is a network that **cannot exist outside Tor**.
---
## Architecture Enforcements
### 1. Mandatory Tor Routing (`init.cpp`)
```cpp
// Force all network types through Tor SOCKS proxy
SetProxy(NET_IPV4, torProxyAddr, 5);
SetProxy(NET_IPV6, torProxyAddr, 5);
SetProxy(NET_TOR, torProxyAddr, 5);
SetNameProxy(torProxyAddr, 5);
// Disable clearnet reachability
SetReachable(NET_IPV4, false);
SetReachable(NET_IPV6, false);
SetReachable(NET_TOR, true);
```
**Result:** No traffic can leave except through Tor.
---
### 2. .onion-Only Peer Filter (`net.cpp`)
```cpp
// Reject all non-.onion addresses at connection time
std::string addrStr = pszDest ? std::string(pszDest) : addrConnect.ToStringIP();
if (addrStr.find(".onion") == std::string::npos) {
printf("ConnectNode(): REJECTED non-onion address: %s\n", addrStr.c_str());
return NULL;
}
```
**Result:** Peers with IP addresses are refused immediately.
---
### 3. Onion-Only DNS Seeds (`net.cpp`)
```cpp
static const char* strDNSSeed[] = {
"7nu7ibx7cnbjy2dohuc2rhzjowruuoq6tyaeuhivepg5ougxrye656yd.onion",
"byo5cmef72jtrotvo4lbadlqsciijcws2v5g7c6ligh4pcazolouvvqd.onion",
};
```
**Result:** Bootstrap uses .onion seeds only (no DNS, no clearnet fallback).
---
### 4. UPnP Disabled (`init.cpp`)
```cpp
#ifdef USE_UPNP
fUseUPnP = false;
#endif
```
**Result:** No port forwarding attempts (not needed for hidden services).
---
### 5. Embedded Tor Requirement (`init.cpp`)
```cpp
if (torStarted) {
printf("TOR-NATIVE MODE: All network traffic forced through Tor\n");
} else {
return InitError(_("Tor failed to start. Triangles requires Tor to operate."));
}
```
**Result:** If Tor doesn't start, the daemon refuses to run.
---
## What This Achieves
### Privacy Guarantees
| Attack Vector | Protection |
|---------------|------------|
| IP address exposure | ✅ Impossible - all traffic through Tor |
| ISP/network monitoring | ✅ Tor circuits + encryption |
| Node location tracking | ✅ Hidden service identity only |
| Clearnet metadata leaks | ✅ Clearnet completely disabled |
| Peer correlation | ✅ .onion addresses unlinkable to IPs |
---
### Network Properties
- **Identity = .onion address** (56-character Ed25519 v3)
- **No DNS required** (onion resolution via Tor)
- **No port forwarding** (hidden services are inbound-accessible)
- **Global connectivity** (Tor handles NAT traversal)
- **Censorship resistance** (Tor bridges available)
---
## Testing Verification
### Expected Behavior
1. **Startup:**
```
Embedded Tor starting (SOCKS 19099, HS port 24111)...
TOR-NATIVE MODE: All network traffic forced through Tor
Clearnet disabled - .onion addresses only
Tor hidden service: [56-char-onion].onion
```
2. **Connection attempts:**
```
SOCKS5 connecting [onion-address].onion
trying connection [onion-address].onion:24111
```
3. **No clearnet peers:**
```
# This should NOT appear:
trying connection 192.168.x.x ❌
trying connection 8.8.8.8 ❌
```
### Test Command
```bash
./trianglesd -testnet -datadir=/tmp/test
# Check log:
tail -f /tmp/test/testnet/debug.log | grep -E "TOR-NATIVE|SOCKS5|onion"
```
---
## Positioning Statement
**Before:**
> Triangles is a cryptocurrency with Tor support
**After:**
> **Triangles is a Tor-native proof-of-stake network where all nodes operate as hidden services and all communication is routed through the Tor network, eliminating IP-level identity exposure.**
---
## Implementation Commits
1. `85fe0d0` - Add Tor 0.4.9 as submodule
2. `de1d4ec` - Fix makefile link order for libtor
3. `36ade21` - Document embedded Tor success
4. `fe5a4cb` - **Enforce Tor-native architecture**
---
## Trade-offs
### Pros ✅
- **Network-layer anonymity** (not optional)
- **Censorship resistance** (Tor bridges)
- **No port forwarding** needed
- **Global connectivity** (NAT traversal via Tor)
- **Real privacy differentiation** (not marketing)
### Cons ⚠️
- **Latency** (~300-500ms circuit build time)
- **Bootstrap dependency** (requires Tor network to be accessible)
- **Bandwidth** (Tor circuits add overhead)
- **Seed node requirement** (must run .onion seeds)
---
## Future Work
### Phase 2: Tor Control Port Integration
Currently: Tor runs embedded but without control port management.
**Next:**
- Connect to Tor control port (127.0.0.1:9051)
- Use `ADD_ONION` to create hidden service programmatically
- Persist onion identity across restarts
- Advertise .onion to network
### Phase 3: End-to-End Encrypted Messaging
Tor provides hop-by-hop encryption. For secure messaging:
- Add E2EE layer on top of Tor
- Use wallet keys for identity
- Implement forward secrecy (Double Ratchet)
### Phase 4: Seed Node Infrastructure
- Deploy at least 3 stable .onion seed nodes
- Consider using `HiddenServiceNonAnonymousMode` for seeds (faster, acceptable for public seeds)
- Monitor seed health
---
## Security Considerations
### What Tor Provides
- **Circuit-level encryption** (3 hops)
- **IP address hiding** (exit node sees destination, not origin)
- **Hidden service anonymity** (rendezvous point protocol)
### What Tor Does NOT Provide
- **End-to-end encryption** (add separately for messaging)
- **Traffic analysis immunity** (sophisticated adversaries can correlate)
- **Perfect forward secrecy** (depends on implementation)
### Threat Model
**Protected against:**
- ISP surveillance
- Network-level attackers
- Peer location tracking
- Passive metadata collection
**NOT protected against:**
- Global passive adversary (NSA-level)
- Timing correlation attacks (requires significant resources)
- Application-level leaks (use Tor Browser principles)
---
## Comparison to Other Projects
| Project | Tor Integration | Enforcement |
|---------|----------------|-------------|
| **Triangles** | Embedded, mandatory | ✅ Enforced |
| Bitcoin | Optional (via `-onlynet=onion`) | ❌ Optional |
| Monero | Optional (via `--proxy`) | ❌ Optional |
| Zcash | Optional | ❌ Optional |
| Verge (XVG) | Embedded | ⚠️ Mixed mode |
**Key difference:** Triangles cannot operate without Tor. The network architecture requires it.
---
## Documentation Updates Needed
1. **README.md** - Update project description
2. **Build docs** - Add Tor dependency requirements
3. **FAQ** - Explain why Tor is mandatory
4. **Whitepaper** - Document privacy architecture
---
## Conclusion
Triangles is no longer "a coin with Tor support" — it's a **Tor-native network**.
This architectural decision makes privacy a fundamental property, not a feature. Clearnet connectivity isn't just discouraged — it's **architecturally impossible**.
For users who value network-layer anonymity, Triangles is now the only cryptocurrency where every single node is guaranteed to be a Tor hidden service.
---
**Implementation:** Complete ✅
**Testing:** Verified ✅
**Ready for:** Mainnet deployment
+148
View File
@@ -0,0 +1,148 @@
; Cryptographic Triangles NSIS Installer
; Produces a single setup.exe with wallet + Tor bundled
; Uses per-user install (no UAC elevation) so network drives stay visible
!include "MUI2.nsh"
!include "FileFunc.nsh"
!ifndef VERSION
!define VERSION "0.0.0"
!endif
!define APPNAME "Cryptographic Triangles"
!define COMPANYNAME "Cryptographic Triangles"
!define EXENAME "triangles-qt.exe"
Name "${APPNAME} v${VERSION}"
OutFile "Cryptographic-Triangles-${VERSION}-win-x64-setup.exe"
InstallDir "$LOCALAPPDATA\${APPNAME}"
InstallDirRegKey HKCU "Software\${APPNAME}" "InstallDir"
RequestExecutionLevel user
; UI — icons and bitmaps are relative to THIS .nsi file
!define MUI_ICON "..\..\src\qt\res\icons\triangles.ico"
!define MUI_UNICON "..\..\src\qt\res\icons\triangles.ico"
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "..\..\share\pixmaps\nsis-header.bmp"
!define MUI_WELCOMEFINISHPAGE_BITMAP "..\..\share\pixmaps\nsis-wizard.bmp"
!define MUI_ABORTWARNING
!define MUI_FINISHPAGE_RUN "$INSTDIR\${EXENAME}"
!define MUI_FINISHPAGE_RUN_TEXT "Launch ${APPNAME}"
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
; Bootstrap page
Page custom BootstrapPage
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
; Bootstrap selection variable
Var BootstrapChoice
; Bootstrap page function
Function BootstrapPage
!insertmacro MUI_HEADER_TEXT "Blockchain Sync" "Choose how to synchronize the blockchain"
nsDialogs::Create 1018
Pop $0
${NSD_CreateLabel} 0 10u 100% 20u "The Triangles blockchain requires ~1GB of data. Choose sync method:"
Pop $0
${NSD_CreateRadioButton} 10u 40u 100% 12u "Download bootstrap (~1.3GB) — Recommended (fast)"
Pop $1
${NSD_Check} $1
${NSD_CreateRadioButton} 10u 60u 100% 12u "Sync from network — Slow (may take days)"
Pop $2
${NSD_CreateLabel} 10u 80u 100% 30u "Bootstrap will download a recent blockchain snapshot, saving hours or days of sync time. Network bandwidth required: ~1.3GB."
Pop $0
nsDialogs::Show
${NSD_GetState} $1 $BootstrapChoice
FunctionEnd
Section "Install"
SetOutPath "$INSTDIR"
; Wallet + Qt DLLs (prepared by the Package step into dist/)
File /r "..\..\dist\*.*"
; Tor binary + data (prepared by Download Tor step into tor-files/)
SetOutPath "$INSTDIR\tor"
File /r "..\..\tor-files\*.*"
; Create data directory
CreateDirectory "$APPDATA\Triangles"
; Download blockchain bootstrap if selected
${If} $BootstrapChoice == ${BST_CHECKED}
DetailPrint "Downloading blockchain bootstrap..."
inetc::get /CAPTION "Downloading Blockchain" /CANCELTEXT "Skip" \
"http://bootstrap.cryptographic-triangles.org/tri-blockchain.tar.gz" \
"$TEMP\tri-blockchain.tar.gz" /END
Pop $0
${If} $0 == "OK"
DetailPrint "Extracting blockchain..."
nsExec::ExecToLog '"$INSTDIR\7z.exe" x "$TEMP\tri-blockchain.tar.gz" -o"$TEMP" -y'
nsExec::ExecToLog '"$INSTDIR\7z.exe" x "$TEMP\tri-blockchain.tar" -o"$APPDATA\Triangles" -y'
Delete "$TEMP\tri-blockchain.tar.gz"
Delete "$TEMP\tri-blockchain.tar"
DetailPrint "Blockchain bootstrap installed!"
${Else}
DetailPrint "Bootstrap download failed or skipped — will sync from network"
${EndIf}
${EndIf}
; Uninstaller
WriteUninstaller "$INSTDIR\uninstall.exe"
; Start menu
CreateDirectory "$SMPROGRAMS\${APPNAME}"
CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\${EXENAME}" "" "$INSTDIR\${EXENAME}" 0
CreateShortcut "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe"
; Desktop shortcut
CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\${EXENAME}" "" "$INSTDIR\${EXENAME}" 0
; Add/Remove Programs (per-user)
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME}"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayIcon" "$INSTDIR\${EXENAME}"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "Publisher" "${COMPANYNAME}"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayVersion" "${VERSION}"
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "NoModify" 1
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "NoRepair" 1
WriteRegStr HKCU "Software\${APPNAME}" "InstallDir" "$INSTDIR"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "EstimatedSize" "$0"
SectionEnd
Section "Uninstall"
; Stop running processes
nsExec::ExecToLog 'taskkill /F /IM triangles-qt.exe'
nsExec::ExecToLog 'taskkill /F /IM trianglesd.exe'
nsExec::ExecToLog 'taskkill /F /IM tor.exe'
; Remove installation
RMDir /r "$INSTDIR"
; Remove shortcuts
RMDir /r "$SMPROGRAMS\${APPNAME}"
Delete "$DESKTOP\${APPNAME}.lnk"
; Remove registry
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
DeleteRegKey HKCU "Software\${APPNAME}"
SectionEnd
+147
View File
@@ -0,0 +1,147 @@
# Triangles Dynamic Seed Node - Setup Guide
## Overview
Triangles v5.5.0+ uses a dynamic HTTP seed list instead of hardcoded addresses.
A collector script runs on a VPS alongside a Triangles node, periodically
querying the node for known .onion peers and publishing them to a static file.
New wallets fetch this file on startup to bootstrap peer discovery.
Once any wallet syncs and obtains its own .onion address, other nodes learn
about it via P2P address exchange. The collector picks it up automatically
on its next run. No manual intervention is needed after initial setup.
## Requirements
- Linux VPS
- Triangles daemon (`trianglesd`) running with Tor enabled
- A web server (Caddy, nginx, Apache, etc.)
- DNS control for the domain serving the seed list
- `jq` and `curl` (`apt install jq curl`)
## Step 1: DNS
Create an A record for the seed list hostname pointing to the VPS IP address.
The default hostname the wallet fetches is `seeds.cryptographic-triangles.org`.
This can be overridden per-node with the `-seedurl` flag.
## Step 2: Web Server
Create a directory for the seed file:
```bash
sudo mkdir -p /var/www/seeds
sudo chown $USER:$USER /var/www/seeds
```
Configure the web server to serve that directory on the seed list hostname.
**Caddy example** (add to Caddyfile):
```
seeds.cryptographic-triangles.org {
root * /var/www/seeds
file_server
}
```
**nginx example** (add server block):
```
server {
listen 80;
server_name seeds.cryptographic-triangles.org;
root /var/www/seeds;
}
```
Reload the web server after making changes.
## Step 3: Install the Collector Script
```bash
sudo cp contrib/seeds/collect-seeds.sh /usr/local/bin/collect-seeds.sh
sudo chmod +x /usr/local/bin/collect-seeds.sh
```
## Step 4: Configure and Test
The script communicates with `trianglesd` via JSON-RPC. It reads credentials
from environment variables. Check `triangles.conf` for `rpcuser` and `rpcpassword`.
Run it manually to verify:
```bash
export RPC_USER="your_rpc_username"
export RPC_PASSWORD="your_rpc_password"
export RPC_PORT="19112"
export OUTPUT_FILE="/var/www/seeds/seeds.txt"
/usr/local/bin/collect-seeds.sh
```
Expected output: `Updated /var/www/seeds/seeds.txt with N seeds`
The resulting file should contain one `.onion:port` entry per line:
```
# Triangles seed nodes - auto-generated 2026-04-01T12:00:00Z
exampleaddress1234567890abcdefghijklmnopqrstuvwxyz234567.onion:24112
anotheraddress1234567890abcdefghijklmnopqrstuvwxyz23456.onion:24112
```
## Step 5: Cron Job
Schedule the collector to run every 5 minutes:
```bash
crontab -e
```
Add:
```
*/5 * * * * RPC_USER="your_rpc_username" RPC_PASSWORD="your_rpc_password" OUTPUT_FILE="/var/www/seeds/seeds.txt" /usr/local/bin/collect-seeds.sh >> /var/log/triangles-seeds.log 2>&1
```
## Step 6: Verify End-to-End
From any machine:
```bash
curl http://seeds.cryptographic-triangles.org/seeds.txt
```
The response should list .onion addresses.
## Troubleshooting
**"no onion seeds found"**
The node has not yet learned any .onion peer addresses. Ensure Tor is enabled
and the node has at least one connected peer. Check with `trianglesd getpeerinfo`.
**"RPC call failed"**
Verify `trianglesd` is running and RPC credentials are correct:
```bash
curl -s --user "user:pass" --data-binary \
'{"jsonrpc":"1.0","method":"getinfo","params":[]}' \
http://127.0.0.1:19112/
```
**seeds.txt not updating**
Check the cron log: `tail /var/log/triangles-seeds.log`
## How It Works
1. The collector calls the `getseedlist` RPC, which returns all known .onion
addresses from the node's address manager
2. Results are written to a static text file served by the web server
3. On startup, Triangles wallets fetch this file and add the addresses to
their peer database
4. As wallets connect and exchange addresses via P2P, new .onion addresses
propagate across the network
5. The collector discovers newly-propagated addresses on its next run
This creates a fully automatic cycle where every online wallet with a Tor
hidden service becomes a discoverable seed node.
+49
View File
@@ -0,0 +1,49 @@
#!/bin/bash
# Triangles Dynamic Seed Collector
# Run via cron on a VPS that runs a Triangles node.
# Queries the local node's getseedlist RPC for known .onion peers
# and writes them to a static file served by a web server.
#
# Example cron (every 5 minutes):
# */5 * * * * /path/to/collect-seeds.sh
#
# The web server (Caddy, nginx, etc.) serves the output file at:
# http://seeds.cryptographic-triangles.org/seeds.txt
# Configuration
RPC_USER="${RPC_USER:-trianglesrpc}"
RPC_PASSWORD="${RPC_PASSWORD:-}"
RPC_PORT="${RPC_PORT:-19112}"
OUTPUT_FILE="${OUTPUT_FILE:-/var/www/seeds/seeds.txt}"
if [ -z "$RPC_PASSWORD" ]; then
echo "Error: RPC_PASSWORD not set" >&2
exit 1
fi
# Query the node for known onion seeds
RESPONSE=$(curl -s --user "${RPC_USER}:${RPC_PASSWORD}" \
--data-binary '{"jsonrpc":"1.0","id":"seedcollect","method":"getseedlist","params":[]}' \
-H 'content-type: text/plain;' \
"http://127.0.0.1:${RPC_PORT}/" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
echo "Error: RPC call failed" >&2
exit 1
fi
# Extract addresses and write to temp file, then atomically move
TMPFILE=$(mktemp)
echo "# Triangles seed nodes - auto-generated $(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$TMPFILE"
echo "$RESPONSE" | jq -r '.result[] | .address + ":" + (.port|tostring)' >> "$TMPFILE" 2>/dev/null
SEED_COUNT=$(grep -c '.onion' "$TMPFILE" 2>/dev/null || echo 0)
if [ "$SEED_COUNT" -gt 0 ]; then
mv "$TMPFILE" "$OUTPUT_FILE"
echo "Updated ${OUTPUT_FILE} with ${SEED_COUNT} seeds"
else
rm -f "$TMPFILE"
echo "Warning: no onion seeds found, keeping previous file" >&2
fi
+1 -1
View File
@@ -3,7 +3,7 @@
# Run on a Linux x64 system with appimagetool installed
set -e
VERSION="5.3.7"
VERSION="5.6.0"
APPDIR="Triangles-x86_64.AppDir"
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
+1 -1
View File
@@ -1,6 +1,6 @@
# Maintainer: Cryptographic Triangles Team
pkgname=triangles-qt-bin
pkgver=5.3.7
pkgver=5.5.6
pkgrel=1
pkgdesc="Cryptographic Triangles (TRI) cryptocurrency wallet - Qt GUI"
arch=('x86_64')
+1 -1
View File
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>triangles</id>
<version>5.3.7</version>
<version>5.5.6</version>
<title>Cryptographic Triangles</title>
<authors>Cryptographic Triangles Team</authors>
<owners>SamiAhmed7777</owners>
+1 -1
View File
@@ -1,5 +1,5 @@
Package: triangles
Version: 5.3.7-1
Version: 5.5.6-1
Section: net
Priority: optional
Architecture: amd64
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# Post-installation script for Triangles .deb package
set -e
echo "════════════════════════════════════════════════════════"
echo " Triangles Installation Complete"
echo "════════════════════════════════════════════════════════"
echo ""
echo "Optional: Download blockchain bootstrap to skip days of sync"
echo ""
echo " sudo triangles-bootstrap-install"
echo ""
echo "This will download ~1.3GB and extract to ~/.triangles/"
echo "════════════════════════════════════════════════════════"
echo ""
exit 0
+9 -2
View File
@@ -3,7 +3,7 @@
# Run from the packaging/debian directory
set -e
VERSION="5.3.7"
VERSION="5.6.0"
PKGDIR="triangles_${VERSION}-1_amd64"
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
@@ -13,10 +13,13 @@ echo "Building .deb package for Triangles v${VERSION}..."
rm -rf "$PKGDIR"
mkdir -p "$PKGDIR/DEBIAN"
mkdir -p "$PKGDIR/usr/bin"
mkdir -p "$PKGDIR/usr/local/bin"
mkdir -p "$PKGDIR/usr/share/applications"
# Copy control file
# Copy control and postinst
cp DEBIAN/control "$PKGDIR/DEBIAN/"
cp DEBIAN/postinst "$PKGDIR/DEBIAN/"
chmod 755 "$PKGDIR/DEBIAN/postinst"
# Download binaries
echo "Downloading binaries..."
@@ -24,6 +27,10 @@ curl -L -o "$PKGDIR/usr/bin/triangles-qt" "${RELEASE_URL}/Cryptographic-Triangle
curl -L -o "$PKGDIR/usr/bin/trianglesd" "${RELEASE_URL}/Cryptographic-Triangles-v${VERSION}-linux-x64-daemon"
chmod 755 "$PKGDIR/usr/bin/triangles-qt" "$PKGDIR/usr/bin/trianglesd"
# Copy bootstrap installer
cp usr/local/bin/triangles-bootstrap-install "$PKGDIR/usr/local/bin/"
chmod 755 "$PKGDIR/usr/local/bin/triangles-bootstrap-install"
# Create desktop entry
cat > "$PKGDIR/usr/share/applications/triangles-qt.desktop" << 'DESKTOP'
[Desktop Entry]
+87
View File
@@ -0,0 +1,87 @@
#!/bin/bash
# Triangles Blockchain Bootstrap Installer
# Downloads and extracts blockchain snapshot to save sync time
set -e
echo "╔═══════════════════════════════════════╗"
echo "║ Triangles Blockchain Bootstrap ║"
echo "╚═══════════════════════════════════════╝"
echo ""
# Determine data directory
if [ -n "$1" ]; then
DATA_DIR="$1"
elif [ -d "$HOME/.triangles" ]; then
DATA_DIR="$HOME/.triangles"
else
DATA_DIR="$HOME/.triangles"
mkdir -p "$DATA_DIR"
fi
echo "Data directory: $DATA_DIR"
echo ""
# Check if triangles is running
if pgrep -x trianglesd > /dev/null || pgrep -x triangles-qt > /dev/null; then
echo "⚠️ Triangles is currently running!"
echo " Please stop it first:"
echo " trianglesd stop (or close triangles-qt)"
echo ""
exit 1
fi
# Check existing blockchain
if [ -f "$DATA_DIR/blk0001.dat" ]; then
SIZE=$(du -sh "$DATA_DIR/blk0001.dat" | cut -f1)
echo "⚠️ Existing blockchain found ($SIZE)"
echo ""
read -p " Overwrite? This will replace your current blockchain [y/N]: " CONFIRM
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
echo "Cancelled."
exit 0
fi
echo ""
fi
# Download bootstrap
BOOTSTRAP_URL="http://bootstrap.cryptographic-triangles.org/tri-blockchain.tar.gz"
TMP_FILE="/tmp/tri-blockchain-$$.tar.gz"
echo "⬇️ Downloading blockchain bootstrap (~1.3GB)..."
echo " This may take several minutes..."
echo ""
if ! curl -# -L --fail --connect-timeout 30 --max-time 1800 -o "$TMP_FILE" "$BOOTSTRAP_URL"; then
echo "❌ Download failed!"
echo " URL: $BOOTSTRAP_URL"
rm -f "$TMP_FILE"
exit 1
fi
echo ""
echo "✓ Downloaded!"
echo ""
# Extract
echo "📦 Extracting blockchain..."
if ! tar xzf "$TMP_FILE" -C "$DATA_DIR/"; then
echo "❌ Extraction failed!"
rm -f "$TMP_FILE"
exit 1
fi
rm -f "$TMP_FILE"
echo "✓ Blockchain installed!"
echo ""
echo "╔═══════════════════════════════════════╗"
echo "║ Bootstrap Complete! ║"
echo "╚═══════════════════════════════════════╝"
echo ""
echo "You can now start Triangles:"
echo " trianglesd -daemon"
echo " (or launch triangles-qt)"
echo ""
echo "The node will sync the remaining ~8,000 blocks from the network."
echo ""
+2 -2
View File
@@ -2,9 +2,9 @@ FROM ubuntu:22.04
LABEL maintainer="Cryptographic Triangles Team"
LABEL description="Cryptographic Triangles (TRI) headless daemon"
LABEL version="5.3.7"
LABEL version="5.6.0"
ARG VERSION=5.3.7
ARG VERSION=5.6.0
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
+1 -1
View File
@@ -3,7 +3,7 @@ version: "3.8"
services:
trianglesd:
build: .
image: cryptographic-triangles/trianglesd:5.3.7
image: cryptographic-triangles/trianglesd:5.6.0
container_name: trianglesd
restart: unless-stopped
ports:
@@ -25,7 +25,7 @@ modules:
- install -Dm644 org.cryptographic_triangles.TrianglesQt.metainfo.xml /app/share/metainfo/org.cryptographic_triangles.TrianglesQt.metainfo.xml
sources:
- type: file
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-qt
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-qt
sha256: ed220eb8d0b403f62cdac28988541fd1a27864491e233216f9c00a4c2537b4a3
dest-filename: triangles-qt-linux
- type: file
@@ -55,6 +55,6 @@ modules:
- install -Dm755 trianglesd-linux /app/bin/trianglesd
sources:
- type: file
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-daemon
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-daemon
sha256: 4d2ab25d61127d6aff3e6f3069556d04f4b823f8849e97629c12871ad4779517
dest-filename: trianglesd-linux
+1 -1
View File
@@ -2,7 +2,7 @@ class Triangles < Formula
desc "Cryptographic Triangles (TRI) cryptocurrency wallet and daemon"
homepage "https://cryptographic-triangles.org"
license "MIT"
version "5.3.7"
version "5.5.6"
on_macos do
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-macos-arm64.dmg"
+1 -1
View File
@@ -14,7 +14,7 @@
}:
let
version = "5.3.7";
version = "5.5.6";
desktopItem = makeDesktopItem {
name = "triangles-qt";
+1 -1
View File
@@ -4,7 +4,7 @@
# Install build tools: sudo dnf install rpm-build rpmdevtools
set -e
VERSION="5.3.7"
VERSION="5.6.0"
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
echo "Building RPM for Triangles v${VERSION}..."
+1 -1
View File
@@ -1,5 +1,5 @@
Name: triangles
Version: 5.3.7
Version: 5.6.0
Release: 1%{?dist}
Summary: Cryptographic Triangles (TRI) cryptocurrency wallet
License: MIT
+29
View File
@@ -0,0 +1,29 @@
{
"version": "5.6.0",
"description": "Cryptographic Triangles (TRI) cryptocurrency wallet with PoS staking and encrypted messaging",
"homepage": "https://cryptographic-triangles.org",
"license": "MIT",
"architecture": {
"64bit": {
"url": "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-5.6.0-win-x64.zip",
"hash": "6f002a669a7e92aaf3d8dd7b1ae80f06a086c99a15ca05cf107665009ffc06b7"
}
},
"bin": [
"triangles-qt.exe",
"trianglesd.exe"
],
"shortcuts": [
["triangles-qt.exe", "Cryptographic Triangles"]
],
"checkver": {
"github": "https://github.com/SamiAhmed7777/triangles_v5"
},
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v$version/Cryptographic-Triangles-$version-win-x64.zip"
}
}
}
}
@@ -1,5 +1,5 @@
PackageIdentifier: CryptographicTriangles.TrianglesQt
PackageVersion: 5.3.7
PackageVersion: 5.6.0
PackageLocale: en-US
Publisher: Cryptographic Triangles
PublisherUrl: https://cryptographic-triangles.org
@@ -27,7 +27,7 @@ Installers:
- RelativeFilePath: triangles-qt.exe
PortableCommandAlias: triangles-qt
ArchiveBinariesDependOnPath: true
InstallerUrl: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-5.3.7-win-x64.zip
InstallerUrl: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-5.6.0-win-x64.zip
InstallerSha256: 6F002A669A7E92AAF3D8DD7B1AE80F06A086C99A15CA05CF107665009FFC06B7
ManifestType: singleton
ManifestVersion: 1.6.0
+29
View File
@@ -0,0 +1,29 @@
# Version Bump Script
Updates the version number across all files in the repo from a single command.
## Usage
**Set a specific version:**
```bash
bash scripts/bump-version.sh 5.7.0
```
**Or edit `src/clientversion.h` first, then sync everything else:**
```bash
bash scripts/bump-version.sh
```
## What it updates
- `src/clientversion.h` (source of truth)
- `src/version.h`
- `triangles-qt.pro`
- `Dockerfile`
- All packaging manifests (Docker, Snap, Scoop, WinGet, RPM, Flatpak, Debian, AppImage)
## What still needs manual review after running
- `packaging/appstream/...metainfo.xml` — add a new `<release>` entry
- `README.md` — update header version if desired
- Any documentation with download URLs
+149
View File
@@ -0,0 +1,149 @@
#!/bin/bash
# bump-version.sh - Sync all version references from src/clientversion.h
#
# Usage:
# ./scripts/bump-version.sh # Read version from clientversion.h, update everything
# ./scripts/bump-version.sh 5.7.0 # Set version to 5.7.0 in clientversion.h AND everywhere else
#
# The single source of truth is src/clientversion.h
set -e
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
CLIENTVERSION="$REPO_ROOT/src/clientversion.h"
if [ ! -f "$CLIENTVERSION" ]; then
echo "ERROR: Cannot find $CLIENTVERSION"
exit 1
fi
# If a version argument is provided, update clientversion.h first
if [ -n "$1" ]; then
IFS='.' read -r MAJOR MINOR REV <<< "$1"
REV="${REV:-0}"
BUILD=0
sed -i "s/#define CLIENT_VERSION_MAJOR.*/#define CLIENT_VERSION_MAJOR $MAJOR/" "$CLIENTVERSION"
sed -i "s/#define CLIENT_VERSION_MINOR.*/#define CLIENT_VERSION_MINOR $MINOR/" "$CLIENTVERSION"
sed -i "s/#define CLIENT_VERSION_REVISION.*/#define CLIENT_VERSION_REVISION $REV/" "$CLIENTVERSION"
sed -i "s/#define CLIENT_VERSION_BUILD.*/#define CLIENT_VERSION_BUILD $BUILD/" "$CLIENTVERSION"
echo "Updated clientversion.h to $MAJOR.$MINOR.$REV.$BUILD"
fi
# Read version from clientversion.h (the source of truth)
MAJOR=$(grep '#define CLIENT_VERSION_MAJOR' "$CLIENTVERSION" | awk '{print $3}')
MINOR=$(grep '#define CLIENT_VERSION_MINOR' "$CLIENTVERSION" | awk '{print $3}')
REV=$(grep '#define CLIENT_VERSION_REVISION' "$CLIENTVERSION" | awk '{print $3}')
BUILD=$(grep '#define CLIENT_VERSION_BUILD' "$CLIENTVERSION" | awk '{print $3}')
VERSION="$MAJOR.$MINOR.$REV"
VERSION_FULL="$MAJOR.$MINOR.$REV.$BUILD"
echo "Syncing all files to version $VERSION (full: $VERSION_FULL)"
echo "==========================================================="
update_file() {
local file="$1"
local pattern="$2"
local replacement="$3"
if [ -f "$file" ]; then
sed -i "$pattern" "$file"
echo " Updated: $file"
fi
}
# --- Source files ---
# src/version.h - DISPLAY_VERSION macros
update_file "$REPO_ROOT/src/version.h" \
"s/#define DISPLAY_VERSION_MAJOR.*/#define DISPLAY_VERSION_MAJOR $MAJOR/" ""
update_file "$REPO_ROOT/src/version.h" \
"s/#define DISPLAY_VERSION_MINOR.*/#define DISPLAY_VERSION_MINOR $MINOR/" ""
update_file "$REPO_ROOT/src/version.h" \
"s/#define DISPLAY_VERSION_REVISION.*/#define DISPLAY_VERSION_REVISION $REV/" ""
update_file "$REPO_ROOT/src/version.h" \
"s/#define DISPLAY_VERSION_BUILD.*/#define DISPLAY_VERSION_BUILD $BUILD/" ""
# triangles-qt.pro
update_file "$REPO_ROOT/triangles-qt.pro" \
"s/^VERSION = .*/VERSION = $VERSION_FULL/" ""
# --- Docker ---
update_file "$REPO_ROOT/Dockerfile" \
"s/LABEL version=\"[^\"]*\"/LABEL version=\"$VERSION\"/" ""
update_file "$REPO_ROOT/packaging/docker/Dockerfile" \
"s/LABEL version=\"[^\"]*\"/LABEL version=\"$VERSION\"/" ""
update_file "$REPO_ROOT/packaging/docker/Dockerfile" \
"s/ARG VERSION=.*/ARG VERSION=$VERSION/" ""
update_file "$REPO_ROOT/packaging/docker/docker-compose.yml" \
"s|cryptographic-triangles/trianglesd:[0-9.]*|cryptographic-triangles/trianglesd:$VERSION|" ""
# --- Snap ---
update_file "$REPO_ROOT/snap/snapcraft.yaml" \
"s/^version: '[^']*'/version: '$VERSION'/" ""
# Update download URLs in snapcraft.yaml
if [ -f "$REPO_ROOT/snap/snapcraft.yaml" ]; then
sed -i "s|/download/v[0-9.]*\/|/download/v$VERSION/|g" "$REPO_ROOT/snap/snapcraft.yaml"
sed -i "s/Cryptographic-Triangles-v[0-9.]*-linux/Cryptographic-Triangles-v$VERSION-linux/g" "$REPO_ROOT/snap/snapcraft.yaml"
fi
# --- Scoop ---
if [ -f "$REPO_ROOT/packaging/scoop/triangles.json" ]; then
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" "$REPO_ROOT/packaging/scoop/triangles.json"
sed -i "s|/download/v[0-9.]*/|/download/v$VERSION/|g" "$REPO_ROOT/packaging/scoop/triangles.json"
sed -i "s/Cryptographic-Triangles-[0-9.]*-win/Cryptographic-Triangles-$VERSION-win/g" "$REPO_ROOT/packaging/scoop/triangles.json"
echo " Updated: packaging/scoop/triangles.json"
fi
# --- WinGet ---
if [ -f "$REPO_ROOT/packaging/winget/CryptographicTriangles.TrianglesQt.yaml" ]; then
sed -i "s/PackageVersion: .*/PackageVersion: $VERSION/" "$REPO_ROOT/packaging/winget/CryptographicTriangles.TrianglesQt.yaml"
sed -i "s|/download/v[0-9.]*/|/download/v$VERSION/|g" "$REPO_ROOT/packaging/winget/CryptographicTriangles.TrianglesQt.yaml"
sed -i "s/Cryptographic-Triangles-[0-9.]*-win/Cryptographic-Triangles-$VERSION-win/g" "$REPO_ROOT/packaging/winget/CryptographicTriangles.TrianglesQt.yaml"
echo " Updated: packaging/winget/CryptographicTriangles.TrianglesQt.yaml"
fi
# --- RPM ---
update_file "$REPO_ROOT/packaging/rpm/triangles.spec" \
"s/^Version: .*/Version: $VERSION/" ""
if [ -f "$REPO_ROOT/packaging/rpm/build-rpm.sh" ]; then
sed -i "s/^VERSION=\"[^\"]*\"/VERSION=\"$VERSION\"/" "$REPO_ROOT/packaging/rpm/build-rpm.sh"
echo " Updated: packaging/rpm/build-rpm.sh"
fi
# --- Flatpak ---
if [ -f "$REPO_ROOT/packaging/flatpak/org.cryptographic_triangles.TrianglesQt.yml" ]; then
sed -i "s|/download/v[0-9.]*/|/download/v$VERSION/|g" "$REPO_ROOT/packaging/flatpak/org.cryptographic_triangles.TrianglesQt.yml"
sed -i "s/Cryptographic-Triangles-v[0-9.]*-linux/Cryptographic-Triangles-v$VERSION-linux/g" "$REPO_ROOT/packaging/flatpak/org.cryptographic_triangles.TrianglesQt.yml"
echo " Updated: packaging/flatpak/org.cryptographic_triangles.TrianglesQt.yml"
fi
# --- Debian ---
if [ -f "$REPO_ROOT/packaging/debian/build-deb.sh" ]; then
sed -i "s/^VERSION=\"[^\"]*\"/VERSION=\"$VERSION\"/" "$REPO_ROOT/packaging/debian/build-deb.sh"
echo " Updated: packaging/debian/build-deb.sh"
fi
# --- AppImage ---
if [ -f "$REPO_ROOT/packaging/appimage/build-appimage.sh" ]; then
sed -i "s/^VERSION=\"[^\"]*\"/VERSION=\"$VERSION\"/" "$REPO_ROOT/packaging/appimage/build-appimage.sh"
echo " Updated: packaging/appimage/build-appimage.sh"
fi
echo ""
echo "Done! All files synced to v$VERSION"
echo ""
echo "Files NOT auto-updated (require manual review):"
echo " - packaging/appstream/...metainfo.xml (add new <release> entry)"
echo " - README.md (update header version)"
echo " - Documentation .md files (update download URLs if needed)"
Regular → Executable
+7 -2
View File
@@ -15,8 +15,13 @@ if [ -e "$(which git)" ]; then
# clean 'dirty' status of touched files that haven't been modified
git diff >/dev/null 2>/dev/null
# get a string like "v0.6.0-66-g59887e8-dirty"
DESC="$(git describe --dirty 2>/dev/null)"
# Try exact tag match first (when building from a release tag)
DESC="$(git describe --tags --exact-match 2>/dev/null)"
# If no exact match, fall back to git describe with commit distance
if [ -z "$DESC" ]; then
DESC="$(git describe --tags --dirty 2>/dev/null)"
fi
# get a string like "2012-04-10 16:27:19 +0200"
TIME="$(git log -n 1 --format="%ci")"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 151 KiB

+5 -5
View File
@@ -1,6 +1,6 @@
name: triangles
base: core22
version: '5.3.7'
version: '5.6.0'
summary: Cryptographic Triangles (TRI) cryptocurrency wallet
description: |
Privacy-focused cryptocurrency featuring Proof-of-Stake consensus,
@@ -51,10 +51,10 @@ apps:
parts:
triangles:
plugin: dump
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-qt
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-qt
source-type: file
organize:
Cryptographic-Triangles-v5.3.7-linux-x64-qt: bin/triangles-qt
Cryptographic-Triangles-v5.6.0-linux-x64-qt: bin/triangles-qt
stage-packages:
- libqt5widgets5
- libqt5gui5
@@ -73,10 +73,10 @@ parts:
trianglesd:
plugin: dump
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-daemon
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-daemon
source-type: file
organize:
Cryptographic-Triangles-v5.3.7-linux-x64-daemon: bin/trianglesd
Cryptographic-Triangles-v5.6.0-linux-x64-daemon: bin/trianglesd
desktop-entry:
plugin: dump
+2 -2
View File
@@ -7,8 +7,8 @@
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 5
#define CLIENT_VERSION_MINOR 4
#define CLIENT_VERSION_REVISION 1
#define CLIENT_VERSION_MINOR 5
#define CLIENT_VERSION_REVISION 6
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
+64 -13
View File
@@ -6,6 +6,7 @@
#include "walletdb.h"
#include "trianglesrpc.h"
#include "net.h"
#include "netbase.h"
#include "init.h"
#include "util.h"
#include "ui_interface.h"
@@ -357,7 +358,7 @@ std::string HelpMessage()
//" -proxy=<ip:port> " + _("Connect through socks proxy") + "\n" +
//" -socks=<n> " + _("Select the version of socks proxy to use (4-5, default: 5)") + "\n" +
" -tor=<ip:port> " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n"
" -notor " + _("Disable Tor startup and .onion connectivity") + "\n" +
" -notor " + _("Disable Tor (WARNING: wallet will not start - Tor is required)") + "\n" +
" -torsocks=<port> " + _("Set embedded or managed Tor SOCKS proxy port (default: 19099)") + "\n" +
" -torhiddenservice " + _("Enable the managed Tor hidden service (default: 1)") + "\n" +
" -torhsport=<port> " + _("Set embedded or managed Tor hidden service port (default: wallet listen port)") + "\n" +
@@ -378,6 +379,8 @@ std::string HelpMessage()
" -synctime " + _("Sync time with other nodes. Disable if time on your system is precise e.g. syncing with NTP (default: 1)") + "\n" +
" -cppolicy " + _("Sync checkpoints policy (default: strict)") + "\n" +
" -onionseed " + _("Find peers using .onion seeds (default: 1 unless -connect)") + "\n" +
" -seedurl=<host> " + _("HTTP seed list host (default: seeds.cryptographic-triangles.org)") + "\n" +
" -noseedurl " + _("Disable HTTP seed list fetch on startup") + "\n" +
" -banscore=<n> " + _("Threshold for disconnecting misbehaving peers (default: 100)") + "\n" +
" -bantime=<n> " + _("Number of seconds to keep misbehaving peers from reconnecting (default: 86400)") + "\n" +
" -maxreceivebuffer=<n> " + _("Maximum per-connection receive buffer, <n>*1000 bytes (default: 5000)") + "\n" +
@@ -420,6 +423,8 @@ std::string HelpMessage()
" -upgradewallet " + _("Upgrade wallet to latest format") + "\n" +
" -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n" +
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
" -postibdrescan " + _("Run the wallet rescan after initial sync in a background thread (default: 1)") + "\n" +
" -zapwallettxes " + _("Delete all wallet transactions and only recover from blockchain on startup") + "\n" +
" -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + "\n" +
" -checkblocks=<n> " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" +
" -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" +
@@ -574,6 +579,11 @@ bool AppInit2()
SoftSetBoolArg("-rescan", true);
}
if (GetBoolArg("-zapwallettxes")) {
// Zap all tx from wallet: rescan to rebuild from blockchain
SoftSetBoolArg("-rescan", true);
}
// ********************************************************* Step 3: parameter-to-internal-flags
fDebug = GetBoolArg("-debug");
@@ -629,6 +639,10 @@ bool AppInit2()
fConfChange = GetBoolArg("-confchange", false);
fEnforceCanonical = GetBoolArg("-enforcecanonical", true);
fAddressIndex = GetBoolArg("-addressindex", false);
if (fAddressIndex)
printf("Address index enabled\n");
if (mapArgs.count("-mininput"))
{
if (!ParseMoney(mapArgs["-mininput"], nMinimumInputValue))
@@ -713,6 +727,13 @@ bool AppInit2()
return false;
}
if (GetBoolArg("-zapwallettxes") && fs::exists(GetDataDir() / strWalletFileName))
{
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
if (!CWalletDB::ZapWalletTx(strWalletFileName))
return InitError(_("Error: could not zap wallet transactions"));
}
if (fs::exists(GetDataDir() / strWalletFileName))
{
CDBEnv::VerifyResult r = bitdb.Verify(strWalletFileName, CWalletDB::Recover);
@@ -737,9 +758,9 @@ bool AppInit2()
//if (nSocksVersion != 4 && nSocksVersion != 5)
// return InitError(strprintf(_("Unknown -socks proxy version requested: %i"), nSocksVersion));
// Network selection: enable all networks (IPv4, IPv6, Tor)
// Tor is always enabled; clearnet is also allowed for seed node discovery
// Users can restrict to Tor-only with -onlynet=tor
// Network selection: Tor-native mode
// All traffic routes through embedded Tor. Clearnet (IPv4/IPv6) is disabled
// after Tor starts successfully. Only .onion peers are accepted.
if (mapArgs.count("-onlynet")) {
std::set<enum Network> nets;
for (std::string snet : mapMultiArgs["-onlynet"]) {
@@ -827,8 +848,22 @@ bool AppInit2()
StartupPerfLog("network_init", GetTimeMillis() - nStart, strprintf("listen=%d seednodes=%" PRIszu, !fNoListen, mapMultiArgs["-seednode"].size()));
// ********************************************************* Step 6b: bootstrap download (daemon)
// Automatic: if data dir has no blockchain, bootstrap without asking.
// Can also be forced with -bootstrap flag, or disabled with -nobootstrap.
#ifndef QT_GUI
if (GetBoolArg("-bootstrap", false))
{
bool wantsBootstrap = GetBoolArg("-bootstrap", false);
bool noBootstrap = GetBoolArg("-nobootstrap", false);
fs::path dataPath = GetDataDir();
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataPath);
if (needsBootstrap && !noBootstrap) {
printf("Bootstrap: no blockchain data found — downloading automatically.\n");
printf("Bootstrap: (use -nobootstrap to skip)\n");
wantsBootstrap = true;
}
if (wantsBootstrap)
{
int64_t nBootstrapStart = GetTimeMillis();
fs::path dataPath = GetDataDir();
@@ -864,6 +899,7 @@ bool AppInit2()
StartupPerfLog("bootstrap_download", GetTimeMillis() - nBootstrapStart,
strprintf("host=%s success=%d", host.c_str(), success));
}
} // end bootstrap scope
#endif
// ********************************************************* Step 7: load blockchain
@@ -1045,7 +1081,7 @@ bool AppInit2()
printf("Rescanning last %i blocks (from block %i)...\n", pindexBest->nHeight - pindexRescan->nHeight, pindexRescan->nHeight);
nStart = GetTimeMillis();
bool fScannedWithIndex = false;
if (GetBoolArg("-addressindex", false) && !GetBoolArg("-rescan"))
if (fAddressIndex && !GetBoolArg("-rescan"))
{
CTxDB txdb("r");
int nAddressIndexStartHeight = 0;
@@ -1124,9 +1160,29 @@ bool AppInit2()
if (torStarted) {
printf("Tor process running, SOCKS proxy at %s\n",
CTorEmbedded::GetInstance()->GetSocksProxy().c_str());
// TOR-NATIVE MODE: Force all traffic through embedded Tor
int socksPort = CTorEmbedded::GetInstance()->GetSocksPort();
CService torProxyAddr("127.0.0.1", socksPort);
SetProxy(NET_IPV4, torProxyAddr, 5);
SetProxy(NET_IPV6, torProxyAddr, 5);
SetProxy(NET_TOR, torProxyAddr, 5);
SetNameProxy(torProxyAddr, 5);
// Disable clearnet reachability - ONION ONLY
SetReachable(NET_IPV4, false);
SetReachable(NET_IPV6, false);
SetReachable(NET_TOR, true);
printf("TOR-NATIVE MODE: All network traffic forced through Tor\n");
printf(" Clearnet disabled - .onion addresses only\n");
#ifdef USE_UPNP
fUseUPnP = false;
#endif
} else {
printf("WARNING: Tor not available. .onion peers will not be reachable.\n");
printf(" Clearnet connections will still work normally.\n");
return InitError(_("Tor failed to start. Triangles requires Tor to operate."));
}
// Initialize Tor V3 identity (Ed25519 keys, onion address)
@@ -1298,11 +1354,6 @@ bool AppInit2()
}
#endif
// ********************************************************* Step 11.6: Address index
fAddressIndex = GetBoolArg("-addressindex", false);
if (fAddressIndex)
printf("Address index enabled\n");
// ********************************************************* Step 11.7: SSE notification queue
if (GetBoolArg("-ssenotify", false))
{
+404 -213
View File
@@ -110,11 +110,54 @@ struct CHeaderSyncNode
static std::map<uint256, CHeaderSyncNode> mapHeaderSync;
static uint256 hashBestHeaderSync = 0;
static CCriticalSection cs_PostIbdWork;
static bool fPostIbdWorkStarted = false;
static const unsigned int MAX_HEADER_SYNC_CACHE = 50000;
static const unsigned int HEADER_DOWNLOAD_WINDOW = 128;
static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 30 * 1000000;
static void ThreadPostIbdWork(void* parg)
{
RenameThread("Triangles-postibd");
try
{
if (!fShutdown && pwalletMain && GetBoolArg("-postibdrescan", true))
{
printf("Starting post-IBD wallet rescan from genesis in background...\n");
uiInterface.InitMessage(_("Rescanning wallet in background..."));
int nFound = 0;
bool fUsedIndex = false;
if (fAddressIndex)
{
fUsedIndex = pwalletMain->ScanForWalletTransactionsFromIndex(pindexGenesisBlock, true, &nFound);
if (!fUsedIndex)
printf("Indexed wallet rescan failed, falling back to full rescan.\n");
}
if (!fUsedIndex)
nFound = pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true);
printf("Post-IBD wallet rescan complete: %d transactions found (indexed=%d)\n", nFound, fUsedIndex);
}
if (!fShutdown && fSecMsgEnabled)
{
printf("Starting post-IBD secure message chain scan in background...\n");
uiInterface.InitMessage(_("Scanning for secure messages in background..."));
SecureMsgScanBlockChain();
printf("Post-IBD secure message chain scan complete\n");
}
}
catch (std::exception& e)
{
PrintExceptionContinue(&e, "ThreadPostIbdWork()");
}
catch (...)
{
PrintExceptionContinue(NULL, "ThreadPostIbdWork()");
}
}
static uint256 GetHeaderSyncTrust(unsigned int nBits)
{
CBigNum bnTarget;
@@ -639,12 +682,15 @@ bool CTransaction::AreInputsStandard(const MapPrevTx& mapInputs) const
for (unsigned int i = 0; i < vin.size(); i++)
{
const CTxOut& prev = GetOutputFor(vin[i], mapInputs);
MapPrevTx::const_iterator mi = mapInputs.find(vin[i].prevout);
if (mi == mapInputs.end())
return false;
const CUtxoEntry& entry = mi->second;
vector<vector<unsigned char> > vSolutions;
txnouttype whichType;
// get the scriptPubKey corresponding to this input:
const CScript& prevScript = prev.scriptPubKey;
const CScript& prevScript = entry.scriptPubKey;
if (!Solver(prevScript, whichType, vSolutions))
return false;
int nArgsExpected = ScriptSigArgsExpected(whichType, vSolutions);
@@ -908,9 +954,9 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs,
if (fCheckInputs)
{
MapPrevTx mapInputs;
map<uint256, CTxIndex> mapUnused;
MapPrevTx mapEmpty; // no pending UTXOs for mempool acceptance
bool fInvalid = false;
if (!tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid))
if (!tx.FetchInputs(txdb, mapEmpty, false, false, mapInputs, fInvalid))
{
if (fInvalid)
return error("CTxMemPool::accept() : FetchInputs found invalid tx %s", hash.ToString().substr(0,10).c_str());
@@ -964,7 +1010,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs,
// Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
if (!tx.ConnectInputs(txdb, mapInputs, mapUnused, CDiskTxPos(1,1,1), pindexBest, false, false))
if (!tx.ConnectInputs(txdb, mapInputs, pindexBest, false, false))
{
return error("CTxMemPool::accept() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str());
}
@@ -1486,41 +1532,16 @@ void CBlock::UpdateTime(const CBlockIndex* pindexPrev)
bool CTransaction::DisconnectInputs(CTxDB& txdb)
{
// Relinquish previous transactions' spent pointers
if (!IsCoinBase())
{
for (const CTxIn& txin : vin)
{
COutPoint prevout = txin.prevout;
// Get prev txindex from disk
CTxIndex txindex;
if (!txdb.ReadTxIndex(prevout.hash, txindex))
return error("DisconnectInputs() : ReadTxIndex failed");
if (prevout.n >= txindex.vSpent.size())
return error("DisconnectInputs() : prevout.n out of range");
// Mark outpoint as not spent
txindex.vSpent[prevout.n].SetNull();
// Write back
if (!txdb.UpdateTxIndex(prevout.hash, txindex))
return error("DisconnectInputs() : UpdateTxIndex failed");
}
}
// Remove transaction from index
// This can fail if a duplicate of this transaction was in a chain that got
// reorganized away. This is only possible if this transaction was completely
// spent, so erasing it would be a no-op anyway.
// Remove transaction position index entry.
// UTXO undo (restoring spent outputs, removing created outputs) is
// handled by DisconnectBlock's UTXO section.
txdb.EraseTxIndex(*this);
return true;
}
bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTestPool,
bool CTransaction::FetchInputs(CTxDB& txdb, const MapPrevTx& mapPendingUtxos,
bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid)
{
// FetchInputs can return false either because we just haven't seen some inputs
@@ -1535,61 +1556,96 @@ bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTes
for (unsigned int i = 0; i < vin.size(); i++)
{
COutPoint prevout = vin[i].prevout;
if (inputsRet.count(prevout.hash))
if (inputsRet.count(prevout))
continue; // Got it already
// Read txindex
CTxIndex& txindex = inputsRet[prevout.hash].first;
bool fFound = true;
if ((fBlock || fMiner) && mapTestPool.count(prevout.hash))
// Check pending UTXOs from earlier transactions in the same block
MapPrevTx::const_iterator mi = mapPendingUtxos.find(prevout);
if (mi != mapPendingUtxos.end())
{
// Get txindex from current proposed changes
txindex = mapTestPool.find(prevout.hash)->second;
inputsRet[prevout] = mi->second;
continue;
}
else
{
// Read txindex from txdb
fFound = txdb.ReadTxIndex(prevout.hash, txindex);
}
if (!fFound && (fBlock || fMiner))
return fMiner ? false : error("FetchInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
// Read txPrev
CTransaction& txPrev = inputsRet[prevout.hash].second;
if (!fFound || txindex.pos == CDiskTxPos(1,1,1))
// Read from UTXO database
CUtxoEntry entry;
if (txdb.ReadUtxo(prevout.hash, prevout.n, entry))
{
// Get prev tx from single transactions in memory
inputsRet[prevout] = entry;
continue;
}
// Lazy fallback: try old CTxIndex path (for databases upgrading from pre-UTXO format)
{
CTxIndex txindex;
if (txdb.ReadTxIndex(prevout.hash, txindex))
{
LOCK(mempool.cs);
if (!mempool.exists(prevout.hash))
return error("FetchInputs() : %s mempool Tx prev not found %s", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
txPrev = mempool.lookup(prevout.hash);
}
if (!fFound)
txindex.vSpent.resize(txPrev.vout.size());
}
else
{
// Get prev tx from disk
if (!txPrev.ReadFromDisk(txindex.pos))
return error("FetchInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str());
}
}
CTransaction txPrev;
if (txPrev.ReadFromDisk(txindex.pos))
{
if (prevout.n < txPrev.vout.size())
{
CUtxoEntry backfill;
backfill.nValue = txPrev.vout[prevout.n].nValue;
backfill.scriptPubKey = txPrev.vout[prevout.n].scriptPubKey;
backfill.fCoinBase = txPrev.IsCoinBase();
backfill.fCoinStake = txPrev.IsCoinStake();
backfill.nTxTime = txPrev.nTime;
backfill.nHeight = 0; // conservative default
// Make sure all prevout.n indexes are valid:
for (unsigned int i = 0; i < vin.size(); i++)
{
const COutPoint prevout = vin[i].prevout;
assert(inputsRet.count(prevout.hash) != 0);
const CTxIndex& txindex = inputsRet[prevout.hash].first;
const CTransaction& txPrev = inputsRet[prevout.hash].second;
if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
{
// Revisit this if/when transaction replacement is implemented and allows
// adding inputs:
fInvalid = true;
return DoS(100, error("FetchInputs() : %s prevout.n out of range %d %" PRIszu " %" PRIszu " prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str()));
// Try to recover exact block height from block index
CBlock blockHeader;
if (blockHeader.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
{
std::map<uint256, CBlockIndex*>::iterator bmi = mapBlockIndex.find(blockHeader.GetHash());
if (bmi != mapBlockIndex.end())
backfill.nHeight = bmi->second->nHeight;
}
// Check if this output was already spent (vSpent in old format)
if (prevout.n < txindex.vSpent.size() && !txindex.vSpent[prevout.n].IsNull())
{
// Already spent — don't return it as available
}
else
{
// Backfill to UTXO DB for future lookups
txdb.WriteUtxo(prevout.hash, prevout.n, backfill);
inputsRet[prevout] = backfill;
continue;
}
}
}
}
}
// Not in UTXO DB or old index — check mempool
{
LOCK(mempool.cs);
if (mempool.exists(prevout.hash))
{
const CTransaction& txPrev = mempool.lookup(prevout.hash);
if (prevout.n < txPrev.vout.size())
{
CUtxoEntry mempoolEntry;
mempoolEntry.nValue = txPrev.vout[prevout.n].nValue;
mempoolEntry.nHeight = 0; // not yet in a block
mempoolEntry.scriptPubKey = txPrev.vout[prevout.n].scriptPubKey;
mempoolEntry.fCoinBase = txPrev.IsCoinBase();
mempoolEntry.fCoinStake = txPrev.IsCoinStake();
mempoolEntry.nTxTime = txPrev.nTime;
inputsRet[prevout] = mempoolEntry;
continue;
}
}
}
// Input not found anywhere
if (fBlock || fMiner)
return fMiner ? false : error("FetchInputs() : %s prev output %s:%d not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str(), prevout.n);
// For orphan detection in AcceptToMemoryPool
return false;
}
return true;
@@ -1597,15 +1653,11 @@ bool CTransaction::FetchInputs(CTxDB& txdb, const map<uint256, CTxIndex>& mapTes
const CTxOut& CTransaction::GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const
{
MapPrevTx::const_iterator mi = inputs.find(input.prevout.hash);
if (mi == inputs.end())
throw std::runtime_error("CTransaction::GetOutputFor() : prevout.hash not found");
const CTransaction& txPrev = (mi->second).second;
if (input.prevout.n >= txPrev.vout.size())
throw std::runtime_error("CTransaction::GetOutputFor() : prevout.n out of range");
return txPrev.vout[input.prevout.n];
// Legacy adapter: constructs a temporary CTxOut from CUtxoEntry.
// Only used by AreInputsStandard which needs a CTxOut reference.
(void)input;
(void)inputs;
throw std::runtime_error("CTransaction::GetOutputFor() : use UTXO entries directly");
}
int64_t CTransaction::GetValueIn(const MapPrevTx& inputs) const
@@ -1616,10 +1668,12 @@ int64_t CTransaction::GetValueIn(const MapPrevTx& inputs) const
int64_t nResult = 0;
for (unsigned int i = 0; i < vin.size(); i++)
{
nResult += GetOutputFor(vin[i], inputs).nValue;
MapPrevTx::const_iterator mi = inputs.find(vin[i].prevout);
if (mi == inputs.end())
throw std::runtime_error("CTransaction::GetValueIn() : input not found");
nResult += mi->second.nValue;
}
return nResult;
}
unsigned int CTransaction::GetP2SHSigOpCount(const MapPrevTx& inputs) const
@@ -1630,20 +1684,22 @@ unsigned int CTransaction::GetP2SHSigOpCount(const MapPrevTx& inputs) const
unsigned int nSigOps = 0;
for (unsigned int i = 0; i < vin.size(); i++)
{
const CTxOut& prevout = GetOutputFor(vin[i], inputs);
if (prevout.scriptPubKey.IsPayToScriptHash())
nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig);
MapPrevTx::const_iterator mi = inputs.find(vin[i].prevout);
if (mi == inputs.end())
continue;
const CScript& scriptPubKey = mi->second.scriptPubKey;
if (scriptPubKey.IsPayToScriptHash())
nSigOps += scriptPubKey.GetSigOpCount(vin[i].scriptSig);
}
return nSigOps;
}
bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, map<uint256, CTxIndex>& mapTestPool, const CDiskTxPos& posThisTx,
bool CTransaction::ConnectInputs(CTxDB& txdb, const MapPrevTx& inputs,
const CBlockIndex* pindexBlock, bool fBlock, bool fMiner)
{
// Take over previous transactions' spent pointers
// fBlock is true when this is called from AcceptBlock when a new best-block is added to the blockchain
// fMiner is true when called from the internal triangles miner
// ... both are false when called from CTransaction::AcceptToMemoryPool
// Validate inputs against UTXO entries and verify signatures.
// Double-spend is impossible here: FetchInputs only returns entries that exist
// in the UTXO DB (unspent) or mapPendingUtxos (created earlier in this block).
if (!IsCoinBase())
{
int64_t nValueIn = 0;
@@ -1651,64 +1707,44 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, map<uint256, CTx
for (unsigned int i = 0; i < vin.size(); i++)
{
COutPoint prevout = vin[i].prevout;
assert(inputs.count(prevout.hash) > 0);
CTxIndex& txindex = inputs[prevout.hash].first;
CTransaction& txPrev = inputs[prevout.hash].second;
if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %" PRIszu " %" PRIszu " prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str()));
MapPrevTx::const_iterator mi = inputs.find(prevout);
if (mi == inputs.end())
return DoS(100, error("ConnectInputs() : %s input %s:%d not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str(), prevout.n));
const CUtxoEntry& entry = mi->second;
// If prev is coinbase or coinstake, check that it's matured
if (txPrev.IsCoinBase() || txPrev.IsCoinStake())
for (const CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < nCoinbaseMaturity; pindex = pindex->pprev)
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
return error("ConnectInputs() : tried to spend %s at depth %d", txPrev.IsCoinBase() ? "coinbase" : "coinstake", pindexBlock->nHeight - pindex->nHeight);
if (entry.fCoinBase || entry.fCoinStake)
{
if (pindexBlock->nHeight - entry.nHeight < nCoinbaseMaturity)
return error("ConnectInputs() : tried to spend %s at depth %d", entry.fCoinBase ? "coinbase" : "coinstake", pindexBlock->nHeight - entry.nHeight);
}
// triangles: check transaction timestamp
if (txPrev.nTime > nTime)
if (entry.nTxTime > nTime)
return DoS(100, error("ConnectInputs() : transaction timestamp earlier than input transaction"));
// Check for negative or overflow input values
nValueIn += txPrev.vout[prevout.n].nValue;
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
nValueIn += entry.nValue;
if (!MoneyRange(entry.nValue) || !MoneyRange(nValueIn))
return DoS(100, error("ConnectInputs() : txin values out of range"));
}
// The first loop above does all the inexpensive checks.
// Only if ALL inputs pass do we perform expensive ECDSA signature checks.
// Helps prevent CPU exhaustion attacks.
for (unsigned int i = 0; i < vin.size(); i++)
{
COutPoint prevout = vin[i].prevout;
assert(inputs.count(prevout.hash) > 0);
CTxIndex& txindex = inputs[prevout.hash].first;
CTransaction& txPrev = inputs[prevout.hash].second;
// Check for conflicts (double-spend)
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
// for an attacker to attempt to split the network.
if (!txindex.vSpent[prevout.n].IsNull())
return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str());
const CUtxoEntry& entry = inputs.find(prevout)->second;
// Skip ECDSA signature verification when connecting blocks (fBlock=true)
// before the last blockchain checkpoint. This is safe because block merkle hashes are
// still computed and checked, and any change will be caught at the next checkpoint.
if (!(fBlock && (nBestHeight < Checkpoints::GetTotalBlocksEstimate())))
{
// Verify signature
if (!VerifySignature(txPrev, *this, i, 0))
{
return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
}
}
// Mark outpoints as spent
txindex.vSpent[prevout.n] = posThisTx;
// Write back
if (fBlock || fMiner)
{
mapTestPool[prevout.hash] = txindex;
// Verify signature using scriptPubKey from UTXO entry
if (!VerifyScript(vin[i].scriptSig, entry.scriptPubKey, *this, i, 0))
return DoS(100, error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str()));
}
}
@@ -1819,6 +1855,45 @@ bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex)
if (!vtx[i].DisconnectInputs(txdb))
return false;
// Undo UTXO entries for this block (reverse of ConnectBlock's UTXO writes)
for (int i = (int)vtx.size()-1; i >= 0; i--)
{
const CTransaction& tx = vtx[i];
uint256 txhash = tx.GetHash();
// Erase outputs this block created
for (unsigned int k = 0; k < tx.vout.size(); k++)
{
if (!tx.vout[k].IsEmpty())
txdb.EraseUtxo(txhash, k);
}
// Restore inputs this block spent (read prev tx from disk to rebuild UTXO entry)
if (!tx.IsCoinBase())
{
for (const CTxIn& txin : tx.vin)
{
CTransaction txPrev;
CTxIndex txindex;
if (txdb.ReadDiskTx(txin.prevout.hash, txPrev, txindex))
{
if (txin.prevout.n < txPrev.vout.size())
{
const CTxOut& prevout = txPrev.vout[txin.prevout.n];
CUtxoEntry utxo;
utxo.nValue = prevout.nValue;
utxo.nHeight = 0; // approximation; exact height not critical for restored UTXOs
utxo.scriptPubKey = prevout.scriptPubKey;
utxo.fCoinBase = txPrev.IsCoinBase();
utxo.fCoinStake = txPrev.IsCoinStake();
utxo.nTxTime = txPrev.nTime;
txdb.WriteUtxo(txin.prevout.hash, txin.prevout.n, utxo);
}
}
}
}
}
// Undo address index entries for this block
if (fAddressIndex)
{
@@ -1923,7 +1998,8 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
else
nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size());
map<uint256, CTxIndex> mapQueuedChanges;
map<uint256, CTxIndex> mapQueuedChanges; // tx position index (for getrawtransaction)
MapPrevTx mapPendingUtxos; // in-block UTXO tracking
int64_t nFees = 0;
int64_t nValueIn = 0;
int64_t nValueOut = 0;
@@ -1937,33 +2013,43 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
if (!fJustCheck)
nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
// Fast path: below checkpoint, skip all input validation and spent-tracking.
// Just record where each transaction lives on disk (txindex).
// Record tx position for getrawtransaction (both fast and full paths)
mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size());
// Fast path: below checkpoint, skip all input validation.
// Track pending UTXOs so later txs in the same block can find inputs.
if (fAssumeValid)
{
mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size());
// Add outputs to pending UTXOs
for (unsigned int k = 0; k < tx.vout.size(); k++)
{
if (!tx.vout[k].IsEmpty())
{
CUtxoEntry entry;
entry.nValue = tx.vout[k].nValue;
entry.nHeight = pindex->nHeight;
entry.scriptPubKey = tx.vout[k].scriptPubKey;
entry.fCoinBase = tx.IsCoinBase();
entry.fCoinStake = tx.IsCoinStake();
entry.nTxTime = tx.nTime;
mapPendingUtxos[COutPoint(hashTx, k)] = entry;
}
}
// Remove spent inputs from pending UTXOs
if (!tx.IsCoinBase())
for (const CTxIn& txin : tx.vin)
mapPendingUtxos.erase(txin.prevout);
continue;
}
// Full validation path (above checkpoint)
// Do not allow blocks that contain transactions which 'overwrite' older transactions,
// unless those are already completely spent.
// If such overwrites are allowed, coinbases and transactions depending upon those
// can be duplicated to remove the ability to spend the first instance -- even after
// being sent to another address.
// See BIP30 and http://r6.ca/blog/20120206T005236Z.html for more information.
// This logic is not necessary for memory pool transactions, as AcceptToMemoryPool
// already refuses previously-known transaction ids entirely.
// This rule was originally applied all blocks whose timestamp was after March 15, 2012, 0:00 UTC.
// Now that the whole chain is irreversibly beyond that time it is applied to all blocks except the
// two in the chain that violate it. This prevents exploiting the issue against nodes in their
// initial block download.
CTxIndex txindexOld;
if (txdb.ReadTxIndex(hashTx, txindexOld)) {
for (CDiskTxPos &pos : txindexOld.vSpent)
if (pos.IsNull())
return false;
// BIP30: check for duplicate transaction with unspent outputs.
// With UTXO model, if any output of this txid exists in the UTXO DB, it's a duplicate.
for (unsigned int k = 0; k < tx.vout.size(); k++)
{
if (!tx.vout[k].IsEmpty() && txdb.HaveUtxo(hashTx, k))
return false;
}
nSigOps += tx.GetLegacySigOpCount();
@@ -1976,7 +2062,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
else
{
bool fInvalid;
if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs, fInvalid))
if (!tx.FetchInputs(txdb, mapPendingUtxos, true, false, mapInputs, fInvalid))
return false;
// Add in sigops done by pay-to-script-hash inputs;
@@ -1995,11 +2081,29 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
if (tx.IsCoinStake())
nStakeReward = nTxValueOut - nTxValueIn;
if (!tx.ConnectInputs(txdb, mapInputs, mapQueuedChanges, posThisTx, pindex, true, false))
if (!tx.ConnectInputs(txdb, mapInputs, pindex, true, false))
return false;
}
mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size());
// Add this tx's outputs to pending UTXOs for later txs in the block
for (unsigned int k = 0; k < tx.vout.size(); k++)
{
if (!tx.vout[k].IsEmpty())
{
CUtxoEntry entry;
entry.nValue = tx.vout[k].nValue;
entry.nHeight = pindex->nHeight;
entry.scriptPubKey = tx.vout[k].scriptPubKey;
entry.fCoinBase = tx.IsCoinBase();
entry.fCoinStake = tx.IsCoinStake();
entry.nTxTime = tx.nTime;
mapPendingUtxos[COutPoint(hashTx, k)] = entry;
}
}
// Remove spent inputs from pending UTXOs
if (!tx.IsCoinBase())
for (const CTxIn& txin : tx.vin)
mapPendingUtxos.erase(txin.prevout);
}
if (!fAssumeValid)
@@ -2043,8 +2147,44 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
return error("ConnectBlock() : UpdateTxIndex failed");
}
// Update address index (skip during IBD - will be rebuilt on next start with -reindex)
if (fAddressIndex && !fIsInitialDownload)
// Write UTXO database entries: add new outputs, erase spent inputs.
// Runs for both fAssumeValid (fast) and full validation paths.
for (unsigned int i = 0; i < vtx.size(); i++)
{
const CTransaction& tx = vtx[i];
uint256 hashTx = tx.GetHash();
// Add new outputs to UTXO set
for (unsigned int k = 0; k < tx.vout.size(); k++)
{
const CTxOut& txout = tx.vout[k];
if (txout.IsEmpty())
continue;
CUtxoEntry utxo;
utxo.nValue = txout.nValue;
utxo.nHeight = pindex->nHeight;
utxo.scriptPubKey = txout.scriptPubKey;
utxo.fCoinBase = tx.IsCoinBase();
utxo.fCoinStake = tx.IsCoinStake();
utxo.nTxTime = tx.nTime;
if (!txdb.WriteUtxo(hashTx, k, utxo))
return error("ConnectBlock() : WriteUtxo failed");
}
// Erase spent inputs from UTXO set
if (!tx.IsCoinBase())
{
for (const CTxIn& txin : tx.vin)
{
if (!txdb.EraseUtxo(txin.prevout.hash, txin.prevout.n))
return error("ConnectBlock() : EraseUtxo failed");
}
}
}
// Update address index
if (fAddressIndex)
{
for (unsigned int i = 0; i < vtx.size(); i++)
{
@@ -2344,11 +2484,22 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
// Log every 5000 blocks during sync, every block once caught up
if (nBestHeight % 5000 == 0 || !IsInitialBlockDownload())
printf("SetBestChain: new best=%s height=%d trust=%s blocktrust=%" PRId64 " date=%s\n",
{
static int64_t nLastLogTime = 0;
static int nLastLogHeight = 0;
int64_t nNow = GetTimeMillis();
double dRate = 0;
if (nLastLogTime > 0 && nNow > nLastLogTime)
dRate = (double)(nBestHeight - nLastLogHeight) * 1000.0 / (double)(nNow - nLastLogTime);
printf("SetBestChain: new best=%s height=%d trust=%s blocktrust=%" PRId64 " date=%s %.1f blk/s\n",
hashBestChain.ToString().substr(0,20).c_str(), nBestHeight,
CBigNum(nBestChainTrust).ToString().c_str(),
nBestBlockTrust.Get64(),
DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str());
DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str(),
dRate);
nLastLogTime = nNow;
nLastLogHeight = nBestHeight;
}
if (fDebug)
printf("Stake checkpoint: %x\n", pindexBest->nStakeModifierChecksum);
@@ -2406,23 +2557,23 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
const CBlockLocator locator(pindexBest);
::SetBestChain(locator);
// Wallet rescan: SyncWithWallets was skipped during IBD, so scan
// the entire chain to pick up all wallet transactions.
if (pwalletMain)
// Run expensive post-IBD scans in the background so reaching tip
// is not blocked by wallet/message index rebuild work.
bool fStartPostIbdWork = false;
{
printf("Starting post-IBD wallet rescan from genesis...\n");
uiInterface.InitMessage(_("Rescanning wallet..."));
int nFound = pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true);
printf("Post-IBD wallet rescan complete: %d transactions found\n", nFound);
LOCK(cs_PostIbdWork);
if (!fPostIbdWorkStarted)
{
fPostIbdWorkStarted = true;
fStartPostIbdWork = true;
}
}
// Secure messaging: scan chain for public keys needed to decrypt messages
if (fSecMsgEnabled)
if (fStartPostIbdWork && !NewThread(ThreadPostIbdWork, NULL))
{
printf("Starting post-IBD secure message chain scan...\n");
uiInterface.InitMessage(_("Scanning for secure messages..."));
SecureMsgScanBlockChain();
printf("Post-IBD secure message chain scan complete\n");
LOCK(cs_PostIbdWork);
fPostIbdWorkStarted = false;
printf("Warning: post-IBD background work thread could not be started; scans skipped.\n");
}
}
fWasInitialDownload = fIsInitialDownload;
@@ -2448,26 +2599,47 @@ bool CTransaction::GetCoinAge(CTxDB& txdb, uint64_t& nCoinAge) const
for (const CTxIn& txin : vin)
{
// First try finding the previous transaction in database
CTransaction txPrev;
CTxIndex txindex;
if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
continue; // previous transaction not in main chain
if (nTime < txPrev.nTime)
// Look up the UTXO entry for this input
CUtxoEntry utxo;
if (!txdb.ReadUtxo(txin.prevout.hash, txin.prevout.n, utxo))
{
// Lazy fallback: try old CTxIndex path
CTxIndex txindexFallback;
if (!txdb.ReadTxIndex(txin.prevout.hash, txindexFallback))
continue;
CTransaction txPrev;
if (!txPrev.ReadFromDisk(txindexFallback.pos))
continue;
if (txin.prevout.n >= txPrev.vout.size())
continue;
utxo.nValue = txPrev.vout[txin.prevout.n].nValue;
utxo.scriptPubKey = txPrev.vout[txin.prevout.n].scriptPubKey;
utxo.fCoinBase = txPrev.IsCoinBase();
utxo.fCoinStake = txPrev.IsCoinStake();
utxo.nTxTime = txPrev.nTime;
utxo.nHeight = 0;
}
if (nTime < utxo.nTxTime)
return false; // Transaction timestamp violation
// Read block header
// Read block header to check min age.
// Use the tx position index to find the block file/position.
CTxIndex txindex;
if (!txdb.ReadTxIndex(txin.prevout.hash, txindex))
continue;
CBlock block;
if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
return false; // unable to read block of previous transaction
if (block.GetBlockTime() + nStakeMinAge > nTime)
continue; // only count coins meeting min age requirement
int64_t nValueIn = txPrev.vout[txin.prevout.n].nValue;
bnCentSecond += CBigNum(nValueIn) * (nTime-txPrev.nTime) / CENT;
int64_t nValueIn = utxo.nValue;
bnCentSecond += CBigNum(nValueIn) * (nTime - utxo.nTxTime) / CENT;
if (fDebug && GetBoolArg("-printcoinage"))
printf("coin age nValueIn=%" PRId64 " nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString().c_str());
printf("coin age nValueIn=%" PRId64 " nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - utxo.nTxTime, bnCentSecond.ToString().c_str());
}
CBigNum bnCoinDay = bnCentSecond * CENT / (24 * 60 * 60);
@@ -2568,7 +2740,17 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const u
// New best — keep the batch open so SetBestChain can add ConnectBlock
// writes to the same transaction, cutting the per-block commit count in half.
// v5.4: deterministic tiebreaker — when two chains have equal trust,
// all nodes agree on the one whose tip has the lower block hash.
// This prevents permanent forks from PoS blocks with identical difficulty.
bool fNewBest = false;
if (pindexNew->nChainTrust > nBestChainTrust)
fNewBest = true;
else if (pindexNew->nChainTrust == nBestChainTrust && pindexBest &&
pindexNew->GetBlockHash() < pindexBest->GetBlockHash())
fNewBest = true;
if (fNewBest)
{
if (!SetBestChain(txdb, pindexNew))
return false;
@@ -2719,7 +2901,7 @@ bool CBlock::AcceptBlock()
if (nHeight % 10000 == 0 || nHeight > 2186900)
printf("ProcessBlock(): Check proof-of-stake/work OK for block %d\n", nHeight);
// Check timestamp against prev
if (GetBlockTime() <= pindexPrev->GetPastTimeLimit() || FutureDrift(GetBlockTime()) < pindexPrev->GetBlockTime())
if (GetBlockTime() <= pindexPrev->GetPastTimeLimit() || FutureDrift(GetBlockTime(), nHeight) < pindexPrev->GetBlockTime())
return error("AcceptBlock() : block's timestamp is too early");
// Check that all transactions are finalized
@@ -2778,9 +2960,6 @@ bool CBlock::AcceptBlock()
pnode->PushInventory(CInv(MSG_BLOCK, hash));
}
// triangles: check pending sync-checkpoint
Checkpoints::AcceptPendingSyncCheckpoint();
return true;
}
@@ -2819,7 +2998,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
// triangles: check proof-of-stake
// Limited duplicity on stake: prevents block flood attack
// Duplicate stake allowed only when there is orphan child block
if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());
// Preliminary checks
@@ -2833,12 +3012,12 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
}
// Anti-spam: reject blocks with insufficient difficulty to prevent memory flooding.
// Use sync checkpoint as reference; fall back to chain tip if checkpoint is genesis.
CBlockIndex* pcheckpoint = Checkpoints::GetLastSyncCheckpoint();
if (!pcheckpoint || pcheckpoint->nHeight == 0)
// Use the most recent hardened checkpoint we know about; fall back to the chain tip.
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex);
if (!pcheckpoint)
pcheckpoint = pindexBest;
if (pcheckpoint && pblock->hashPrevBlock != hashBestChain && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
if (pcheckpoint && pblock->hashPrevBlock != hashBestChain)
{
int64_t deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime;
CBigNum bnNewBlock;
@@ -2867,10 +3046,6 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
}
}
// triangles: ask for pending sync-checkpoint if any
if (!IsInitialBlockDownload())
Checkpoints::AskForPendingSyncCheckpoint(pfrom);
// If don't already have its previous block, shunt it off to holding area until we get it
if (!mapBlockIndex.count(pblock->hashPrevBlock))
{
@@ -2881,7 +3056,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
{
// Limited duplicity on stake: prevents block flood attack
// Duplicate stake allowed only when there is orphan child block
if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str());
else
setStakeSeenOrphan.insert(pblock2->GetProofOfStake());
@@ -2969,10 +3144,6 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
nQueued, hash.ToString().substr(0,20).c_str());
}
// triangles: if responsible for sync-checkpoint send it
if (pfrom && !CSyncCheckpoint::strMasterPrivKey.empty())
Checkpoints::SendSyncCheckpoint(Checkpoints::AutoSelectSyncCheckpoint());
return true;
}
@@ -2999,13 +3170,13 @@ bool CBlock::SignBlock(CWallet& wallet, int64_t nFees)
{
if (wallet.CreateCoinStake(wallet, nBits, nSearchTime-nLastCoinStakeSearchTime, nFees, txCoinStake, key))
{
if (txCoinStake.nTime >= max(pindexBest->GetPastTimeLimit()+1, PastDrift(pindexBest->GetBlockTime())))
if (txCoinStake.nTime >= max(pindexBest->GetPastTimeLimit()+1, PastDrift(pindexBest->GetBlockTime(), pindexBest->nHeight + 1)))
{
// make sure coinstake would meet timestamp protocol
// as it would be the same as the block timestamp
vtx[0].nTime = nTime = txCoinStake.nTime;
nTime = max(pindexBest->GetPastTimeLimit()+1, GetMaxTransactionTime());
nTime = max(GetBlockTime(), PastDrift(pindexBest->GetBlockTime()));
nTime = max(GetBlockTime(), PastDrift(pindexBest->GetBlockTime(), pindexBest->nHeight + 1));
// we have to make sure that we have no future timestamps in
// our transactions set
@@ -3537,15 +3708,37 @@ bool FastImportBlockFile()
// Write block index to batch
txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
// Build tx index entries
// Build tx index + UTXO entries
unsigned int nTxPos = nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION)
- (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(block.vtx.size());
for (unsigned int i = 0; i < block.vtx.size(); i++)
{
const CTransaction& tx = block.vtx[i];
uint256 hashTx = tx.GetHash();
CDiskTxPos posThisTx(1, nBlockPos, nTxPos);
txdb.UpdateTxIndex(tx.GetHash(), CTxIndex(posThisTx, tx.vout.size()));
txdb.UpdateTxIndex(hashTx, CTxIndex(posThisTx, tx.vout.size()));
nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
// UTXO entries
if (!tx.IsCoinBase())
{
for (const CTxIn& txin : tx.vin)
txdb.EraseUtxo(txin.prevout.hash, txin.prevout.n);
}
for (unsigned int k = 0; k < tx.vout.size(); k++)
{
if (!tx.vout[k].IsEmpty())
{
CUtxoEntry utxo;
utxo.nValue = tx.vout[k].nValue;
utxo.nHeight = pindexNew->nHeight;
utxo.scriptPubKey = tx.vout[k].scriptPubKey;
utxo.fCoinBase = tx.IsCoinBase();
utxo.fCoinStake = tx.IsCoinStake();
utxo.nTxTime = tx.nTime;
txdb.WriteUtxo(hashTx, k, utxo);
}
}
}
// Update best chain
@@ -3811,11 +4004,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// blocks from the fastest available source.
static int nAskedForBlocks = 0;
bool fIBD = IsInitialBlockDownload();
bool fBehindPeer = (pfrom->nStartingHeight > nBestHeight);
bool fShouldAsk = !pfrom->fClient && !pfrom->fOneShot &&
(pfrom->nStartingHeight > (nBestHeight - 144)) &&
(pfrom->nVersion < NOBLKS_VERSION_START ||
pfrom->nVersion >= NOBLKS_VERSION_END) &&
(fIBD || nAskedForBlocks < 1 || vNodes.size() <= 1);
(fIBD || nAskedForBlocks < 1 || vNodes.size() <= 1 || fBehindPeer);
printf("IBD-DIAG: version handler: peer=%s height=%d ourHeight=%d fClient=%d fOneShot=%d shouldAsk=%d nAskedForBlocks=%d IBD=%d\n",
pfrom->addr.ToString().c_str(), pfrom->nStartingHeight, nBestHeight,
pfrom->fClient, pfrom->fOneShot, fShouldAsk, nAskedForBlocks, fIBD);
@@ -3849,9 +4043,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
cPeerBlockCounts.input(pfrom->nStartingHeight);
// triangles: ask for pending sync-checkpoint if any
if (!IsInitialBlockDownload())
Checkpoints::AskForPendingSyncCheckpoint(pfrom);
}
+79 -26
View File
@@ -29,6 +29,7 @@ class CNode;
static const int CUTOFF_POW_BLOCK = 9000;
static const int CRAPCHAIN_CUTOFF_BLOCK = 17691; // pre-Pharao (version 4) blockchain until block 17691
static const int FORK_HEIGHT_V5 = 17651; // v5 hard fork: decentralization + Tor v3 (next block after last checkpoint)
static const int FORK_HEIGHT_V5_4 = 2186941; // v5.4: tighter timestamps, deterministic fork resolution
static const unsigned int MAX_BLOCK_SIZE = 1000000;
static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
@@ -39,7 +40,7 @@ static const unsigned int MAX_ORPHAN_BLOCKS_IBD = 4000;
static const unsigned int MAX_INV_SZ = 50000;
static const int64_t MIN_TX_FEE = (1 * CENT) / 100;
static const int64_t MIN_RELAY_TX_FEE = (1 * CENT) / 100;
static const int64_t MAX_MONEY = 222222 * COIN;
static const int64_t MAX_MONEY = 2222222 * COIN;
static const int64_t COIN_YEAR_REWARD = 33 * CENT; // 33% per year
static const int64_t MAX_TRI_PROOF_OF_STAKE = 0.33 * COIN;
static const int MODIFIER_INTERVAL_SWITCH = 1;
@@ -56,8 +57,11 @@ static const int fHaveUPnP = false;
static const uint256 hashGenesisBlockOfficial("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
static const uint256 hashGenesisBlockTestNet ("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
inline int64_t PastDrift(int64_t nTime) { return nTime - 10 * 60; } // up to 10 minutes from the past
inline int64_t FutureDrift(int64_t nTime) { return nTime + 10 * 60; } // up to 10 minutes from the future
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 3 * 60 : 10 * 60; }
inline int64_t PastDrift(int64_t nTime, int nHeight) { return nTime - GetMaxTimeDrift(nHeight); }
inline int64_t FutureDrift(int64_t nTime, int nHeight) { return nTime + GetMaxTimeDrift(nHeight); }
inline int64_t PastDrift(int64_t nTime) { return PastDrift(nTime, nBestHeight); }
inline int64_t FutureDrift(int64_t nTime) { return FutureDrift(nTime, nBestHeight); }
extern CScript COINBASE_FLAGS;
@@ -427,7 +431,51 @@ enum GetMinFee_mode
GMF_SEND,
};
typedef std::map<uint256, std::pair<CTxIndex, CTransaction> > MapPrevTx;
/** A single unspent transaction output entry in the UTXO database.
* Keyed by (txhash, output_index). Erased when spent.
*/
class CUtxoEntry
{
public:
int64_t nValue; // output value in satoshis
int nHeight; // block height where output was created
CScript scriptPubKey; // output script (needed for sig verification)
bool fCoinBase; // from a coinbase transaction
bool fCoinStake; // from a coinstake transaction
unsigned int nTxTime; // transaction timestamp (needed for PoS coin age)
CUtxoEntry()
{
SetNull();
}
IMPLEMENT_SERIALIZE
(
READWRITE(nValue);
READWRITE(nHeight);
READWRITE(scriptPubKey);
READWRITE(fCoinBase);
READWRITE(fCoinStake);
READWRITE(nTxTime);
)
void SetNull()
{
nValue = -1;
nHeight = 0;
scriptPubKey.clear();
fCoinBase = false;
fCoinStake = false;
nTxTime = 0;
}
bool IsNull() const
{
return (nValue == -1);
}
};
typedef std::map<COutPoint, CUtxoEntry> MapPrevTx;
/** The basic transaction that is broadcasted on the network and contained in
* blocks. A transaction can contain multiple inputs and outputs.
@@ -668,32 +716,28 @@ public:
bool ReadFromDisk(COutPoint prevout);
bool DisconnectInputs(CTxDB& txdb);
/** Fetch from memory and/or disk. inputsRet keys are transaction hashes.
/** Fetch UTXO entries for all inputs from the UTXO database or mempool.
@param[in] txdb Transaction database
@param[in] mapTestPool List of pending changes to the transaction index database
@param[in] fBlock True if being called to add a new best-block to the chain
@param[in] fMiner True if being called by CreateNewBlock
@param[out] inputsRet Pointers to this transaction's inputs
@param[out] fInvalid returns true if transaction is invalid
@return Returns true if all inputs are in txdb or mapTestPool
@param[in] txdb Transaction database
@param[in] mapPendingUtxos UTXOs created by earlier transactions in the same block
@param[in] fBlock True if being called to add a new best-block to the chain
@param[in] fMiner True if being called by CreateNewBlock
@param[out] inputsRet UTXO entries for this transaction's inputs (keyed by COutPoint)
@param[out] fInvalid returns true if transaction is invalid
@return Returns true if all inputs are found
*/
bool FetchInputs(CTxDB& txdb, const std::map<uint256, CTxIndex>& mapTestPool,
bool FetchInputs(CTxDB& txdb, const MapPrevTx& mapPendingUtxos,
bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid);
/** Sanity check previous transactions, then, if all checks succeed,
mark them as spent by this transaction.
/** Validate inputs against UTXO entries and verify signatures.
@param[in] inputs Previous transactions (from FetchInputs)
@param[out] mapTestPool Keeps track of inputs that need to be updated on disk
@param[in] posThisTx Position of this transaction on disk
@param[in] pindexBlock
@param[in] fBlock true if called from ConnectBlock
@param[in] fMiner true if called from CreateNewBlock
@param[in] inputs UTXO entries for inputs (from FetchInputs)
@param[in] pindexBlock Block being connected
@param[in] fBlock true if called from ConnectBlock
@param[in] fMiner true if called from CreateNewBlock
@return Returns true if all checks succeed
*/
bool ConnectInputs(CTxDB& txdb, MapPrevTx inputs,
std::map<uint256, CTxIndex>& mapTestPool, const CDiskTxPos& posThisTx,
bool ConnectInputs(CTxDB& txdb, const MapPrevTx& inputs,
const CBlockIndex* pindexBlock, bool fBlock, bool fMiner);
bool ClientConnectInputs();
bool CheckTransaction() const;
@@ -767,9 +811,10 @@ public:
/** A txdb record that contains the disk location of a transaction and the
* locations of transactions that spend its outputs. vSpent is really only
* used as a flag, but having the location is very helpful for debugging.
/** A txdb record that contains the disk location of a transaction.
* Used for getrawtransaction and wallet position lookups.
* vSpent is legacy (kept for serialization compat) — spent state is
* tracked by the UTXO set (CUtxoEntry) since dbformat=3.
*/
class CTxIndex
{
@@ -1360,6 +1405,10 @@ public:
uint256 hashPrev;
uint256 hashNext;
// When true, nChainTrust is included in the on-disk serialization.
// Set by LoadBlockIndex based on the DB format version before deserializing.
static bool fSerializeChainTrust;
CDiskBlockIndex()
{
hashPrev = 0;
@@ -1407,6 +1456,10 @@ public:
READWRITE(nBits);
READWRITE(nNonce);
READWRITE(blockHash);
// DB format v2+: persist chain trust to skip expensive recalculation on startup
if (fSerializeChainTrust)
READWRITE(nChainTrust);
)
uint256 GetBlockHash() const
+10 -147
View File
@@ -17,41 +17,8 @@ using namespace std;
//
int static FormatHashBlocks(void* pbuffer, unsigned int len)
{
unsigned char* pdata = (unsigned char*)pbuffer;
unsigned int blocks = 1 + ((len + 8) / 64);
unsigned char* pend = pdata + 64 * blocks;
memset(pdata + len, 0, 64 * blocks - len);
pdata[len] = 0x80;
unsigned int bits = len * 8;
pend[-1] = (bits >> 0) & 0xff;
pend[-2] = (bits >> 8) & 0xff;
pend[-3] = (bits >> 16) & 0xff;
pend[-4] = (bits >> 24) & 0xff;
return blocks;
}
static const unsigned int pSHA256InitState[8] =
{0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
void SHA256Transform(void* pstate, void* pinput, const void* pinit)
{
SHA256_CTX ctx;
unsigned char data[64];
SHA256_Init(&ctx);
for (int i = 0; i < 16; i++)
((uint32_t*)data)[i] = ByteReverse(((uint32_t*)pinput)[i]);
for (int i = 0; i < 8; i++)
ctx.h[i] = ((uint32_t*)pinit)[i];
SHA256_Update(&ctx, data, sizeof(data));
for (int i = 0; i < 8; i++)
((uint32_t*)pstate)[i] = ctx.h[i];
}
// PoW mining helpers (SHA256Transform, FormatHashBlocks, FormatHashBuffers,
// IncrementExtraNonce, CheckWork) removed - PoW ended at block 9000.
// Some explaining would be appreciated
class COrphan
@@ -247,7 +214,6 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
}
// Collect transactions into block
map<uint256, CTxIndex> mapTestPool;
uint64_t nBlockSize = 1000;
uint64_t nBlockTx = 0;
int nBlockSigOps = 100;
@@ -299,10 +265,10 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
// Connecting shouldn't fail due to dependency on other memory pool transactions
// because we're already processing them in order of dependency
map<uint256, CTxIndex> mapTestPoolTmp(mapTestPool);
MapPrevTx mapInputs;
MapPrevTx mapEmpty;
bool fInvalid;
if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid))
if (!tx.FetchInputs(txdb, mapEmpty, false, true, mapInputs, fInvalid))
continue;
int64_t nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
@@ -313,10 +279,8 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
continue;
if (!tx.ConnectInputs(txdb, mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true))
if (!tx.ConnectInputs(txdb, mapInputs, pindexPrev, false, true))
continue;
mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(1,1,1), tx.vout.size());
swap(mapTestPool, mapTestPoolTmp);
// Added
pblock->vtx.push_back(tx);
@@ -365,7 +329,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
// Fill in header
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
pblock->nTime = max(pindexPrev->GetPastTimeLimit()+1, pblock->GetMaxTransactionTime());
pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime()));
pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime(), pindexPrev->nHeight + 1));
if (!fProofOfStake)
pblock->UpdateTime(pindexPrev);
pblock->nNonce = 0;
@@ -375,110 +339,6 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
}
void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
{
// Update nExtraNonce
static uint256 hashPrevBlock;
if (hashPrevBlock != pblock->hashPrevBlock)
{
nExtraNonce = 0;
hashPrevBlock = pblock->hashPrevBlock;
}
++nExtraNonce;
unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required for block.version=2
pblock->vtx[0].vin[0].scriptSig = (CScript() << nHeight << CBigNum(nExtraNonce)) + COINBASE_FLAGS;
assert(pblock->vtx[0].vin[0].scriptSig.size() <= 100);
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
}
void FormatHashBuffers(CBlock* pblock, char* pmidstate, char* pdata, char* phash1)
{
//
// Pre-build hash buffers
//
struct
{
struct unnamed2
{
int nVersion;
uint256 hashPrevBlock;
uint256 hashMerkleRoot;
unsigned int nTime;
unsigned int nBits;
unsigned int nNonce;
}
block;
unsigned char pchPadding0[64];
uint256 hash1;
unsigned char pchPadding1[64];
}
tmp;
memset(&tmp, 0, sizeof(tmp));
tmp.block.nVersion = pblock->nVersion;
tmp.block.hashPrevBlock = pblock->hashPrevBlock;
tmp.block.hashMerkleRoot = pblock->hashMerkleRoot;
tmp.block.nTime = pblock->nTime;
tmp.block.nBits = pblock->nBits;
tmp.block.nNonce = pblock->nNonce;
FormatHashBlocks(&tmp.block, sizeof(tmp.block));
FormatHashBlocks(&tmp.hash1, sizeof(tmp.hash1));
// Byte swap all the input buffer
for (unsigned int i = 0; i < sizeof(tmp)/4; i++)
((unsigned int*)&tmp)[i] = ByteReverse(((unsigned int*)&tmp)[i]);
// Precalc the first half of the first hash, which stays constant
SHA256Transform(pmidstate, &tmp.block, pSHA256InitState);
memcpy(pdata, &tmp.block, 128);
memcpy(phash1, &tmp.hash1, 64);
}
bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey)
{
uint256 hash = pblock->GetHash();
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
if(!pblock->IsProofOfWork())
return error("CheckWork() : %s is not a proof-of-work block", hash.GetHex().c_str());
if (hash > hashTarget && pblock->IsProofOfWork())
return error("CheckWork() : proof-of-work not meeting target");
//// debug print
printf("CheckWork() : new proof-of-work block found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
pblock->print();
printf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue).c_str());
// Found a solution
{
LOCK(cs_main);
if (pblock->hashPrevBlock != hashBestChain)
return error("CheckWork() : generated block is stale");
// Remove key from key pool
reservekey.KeepKey();
// Track how many getdata requests this block gets
{
LOCK(wallet.cs_wallet);
wallet.mapRequestCount[hash] = 0;
}
// Process this block the same as if we had received it from another node
if (!ProcessBlock(NULL, pblock))
return error("CheckWork() : ProcessBlock, block not accepted");
}
return true;
}
bool CheckStake(CBlock* pblock, CWallet& wallet)
{
uint256 proofHash = 0, hashTarget = 0;
@@ -574,7 +434,10 @@ void StakeMiner(CWallet *pwallet)
int64_t nFees;
unique_ptr<CBlock> pblock(CreateNewBlock(pwallet, true, &nFees));
if (!pblock.get())
return;
{
MilliSleep(5000);
continue;
}
// Try to sign the block
if (pblock->SignBlock(*pwallet, nFees))
-12
View File
@@ -12,21 +12,9 @@
/* Generate a new block, without valid proof-of-work */
CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake=false, int64_t* pFees = 0);
/** Modify the extranonce in a block */
void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce);
/** Do mining precalculation */
void FormatHashBuffers(CBlock* pblock, char* pmidstate, char* pdata, char* phash1);
/** Check mined proof-of-work block */
bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey);
/** Check mined proof-of-stake block */
bool CheckStake(CBlock* pblock, CWallet& wallet);
/** Base sha256 mining transform */
void SHA256Transform(void* pstate, void* pinput, const void* pinit);
#endif // TRIANGLES_MINER_H
+263 -95
View File
@@ -13,6 +13,11 @@
#include "ui_interface.h"
#include "onionseed.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sstream>
#ifdef WIN32
#include <string.h>
#endif
@@ -41,8 +46,8 @@ void ThreadOpenAddedConnections2(void* parg);
#ifdef USE_UPNP
void ThreadMapPort2(void* parg);
#endif
void ThreadDNSAddressSeed(void* parg);
void ThreadDNSAddressSeed2(void* parg);
void ThreadHTTPSeedFetch(void* parg);
void ThreadHTTPSeedFetch2(void* parg);
bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false);
@@ -487,6 +492,14 @@ CNode* FindNode(const CService& addr)
CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
{
// TOR-NATIVE: Reject all non-.onion addresses
std::string addrStr = pszDest ? std::string(pszDest) : addrConnect.ToStringIP();
if (addrStr.find(".onion") == std::string::npos) {
if (fDebug)
printf("ConnectNode(): REJECTED non-onion address: %s (Tor-native mode)\n", addrStr.c_str());
return NULL;
}
if (pszDest == NULL) {
if (IsLocal(addrConnect))
return NULL;
@@ -1339,40 +1352,29 @@ void ThreadOnionSeed(void* parg)
// Load hardcoded .onion seeds (if any)
static const char *(*strOnionSeed)[1] = fTestNet ? strTestNetOnionSeed : strMainNetOnionSeed;
int found = 0;
printf("Loading addresses from .onion seeds\n");
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != NULL; seed_idx++) {
CNetAddr parsed;
if (
!parsed.SetSpecial(
strOnionSeed[seed_idx][0]
)
) {
if (!parsed.SetSpecial(strOnionSeed[seed_idx][0]))
throw runtime_error("ThreadOnionSeed() : invalid .onion seed");
}
int nOneDay = 24*3600;
CAddress addr = CAddress(CService(parsed, GetDefaultPort()));
addr.nTime = GetTime() - 3*nOneDay - GetRand(4*nOneDay); // use a random age between 3 and 7 days old
found++;
addr.nTime = GetTime() - 3*nOneDay - GetRand(4*nOneDay);
addrman.Add(addr, parsed);
found++;
}
printf("%d addresses found from .onion seeds\n", found);
printf("%d addresses from hardcoded .onion seeds\n", found);
// Also fetch dynamic seeds from HTTP seed list
// This is the primary discovery mechanism — seeds.cryptographic-triangles.org
ThreadHTTPSeedFetch2(NULL);
printf("ThreadOnionSeed: seeding complete\n");
}
@@ -1386,9 +1388,9 @@ void ThreadOnionSeed(void* parg)
// Hardcoded seeds removed - peer discovery is now fully dynamic via HTTP seed list.
// See: seeds.cryptographic-triangles.org
unsigned int pnSeed[] = {
0xCE58E9C2, // DNS2-OpenClaw: 194.233.88.206
0x13A7D04A, // DNS3-Sami: 74.208.167.19
};
void DumpAddresses()
@@ -1430,56 +1432,222 @@ void ThreadDumpAddress(void* parg)
printf("ThreadDumpAddress exited\n");
}
void ThreadDNSAddressSeed2(void* parg)
void ThreadHTTPSeedFetch2(void* parg)
{
static const char* strDNSSeed[] = {
"seed1.cryptographic-triangles.org",
"seed2.cryptographic-triangles.org",
"seed3.cryptographic-triangles.org",
"backup-seed.cryptographic-triangles.org",
};
static const char* DEFAULT_SEED_URL_HOST = "seeds.cryptographic-triangles.org";
static const char* DEFAULT_SEED_URL_PATH = "/seeds.txt";
static const int HTTPS_PORT = 443;
printf("Loading addresses from DNS seeds...\n");
int found = 0;
std::string seedHost = GetArg("-seedurl", DEFAULT_SEED_URL_HOST);
std::string seedPath = DEFAULT_SEED_URL_PATH;
for (unsigned int seed_idx = 0; seed_idx < ARRAYLEN(strDNSSeed); seed_idx++)
{
if (fShutdown)
// Allow full URL override: -seedurl=myhost.com/path/seeds.txt
size_t slashPos = seedHost.find('/');
if (slashPos != std::string::npos) {
seedPath = seedHost.substr(slashPos);
seedHost = seedHost.substr(0, slashPos);
}
printf("Fetching seed list from https://%s%s (via Tor)...\n", seedHost.c_str(), seedPath.c_str());
SSL_CTX* ctx = NULL;
SSL* ssl = NULL;
SOCKET hSocket = INVALID_SOCKET;
try {
// Connect through Tor SOCKS proxy using existing proxy-aware socket infrastructure
CService addrResolved;
std::string connectDest = seedHost + ":" + std::to_string(HTTPS_PORT);
if (!ConnectSocketByName(addrResolved, hSocket, connectDest.c_str(), HTTPS_PORT, nConnectTimeout)) {
printf("HTTPS seed fetch: cannot connect to %s through Tor proxy\n", seedHost.c_str());
return;
}
vector<CNetAddr> vaddr;
if (LookupHost(strDNSSeed[seed_idx], vaddr))
// Set up TLS over the connected socket
ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
printf("HTTPS seed fetch: SSL_CTX_new failed\n");
closesocket(hSocket);
return;
}
// Use system default CA certificates for verification
SSL_CTX_set_default_verify_paths(ctx);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
ssl = SSL_new(ctx);
if (!ssl) {
printf("HTTPS seed fetch: SSL_new failed\n");
SSL_CTX_free(ctx);
closesocket(hSocket);
return;
}
// Set SNI hostname (required for Caddy/Let's Encrypt)
SSL_set_tlsext_host_name(ssl, seedHost.c_str());
SSL_set_fd(ssl, (int)hSocket);
int ret = SSL_connect(ssl);
if (ret != 1) {
int sslErr = SSL_get_error(ssl, ret);
unsigned long errCode = ERR_get_error();
char errBuf[256];
ERR_error_string_n(errCode, errBuf, sizeof(errBuf));
printf("HTTPS seed fetch: TLS handshake failed (ssl_err=%d): %s\n", sslErr, errBuf);
SSL_free(ssl);
SSL_CTX_free(ctx);
closesocket(hSocket);
return;
}
printf("HTTPS seed fetch: TLS connection established to %s\n", seedHost.c_str());
// Send HTTP request over TLS
std::string request =
"GET " + seedPath + " HTTP/1.1\r\n"
"Host: " + seedHost + "\r\n"
"Connection: close\r\n"
"User-Agent: Triangles\r\n"
"\r\n";
int nSent = 0;
int nLen = request.size();
while (nSent < nLen) {
int nBytes = SSL_write(ssl, request.c_str() + nSent, nLen - nSent);
if (nBytes <= 0) {
printf("HTTPS seed fetch: SSL_write failed\n");
SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(ctx);
closesocket(hSocket);
return;
}
nSent += nBytes;
}
// Read response over TLS
std::string response;
char buf[4096];
while (true) {
int nBytes = SSL_read(ssl, buf, sizeof(buf));
if (nBytes <= 0)
break;
response.append(buf, nBytes);
}
SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(ctx);
closesocket(hSocket);
ssl = NULL;
ctx = NULL;
hSocket = INVALID_SOCKET;
if (response.empty()) {
printf("HTTPS seed fetch: empty response from %s\n", seedHost.c_str());
return;
}
// Parse HTTP response - find end of headers
size_t headerEnd = response.find("\r\n\r\n");
if (headerEnd == std::string::npos) {
printf("HTTPS seed fetch: malformed response (no header terminator)\n");
return;
}
// Check status code
std::string statusLine = response.substr(0, response.find("\r\n"));
if (statusLine.find("200") == std::string::npos) {
printf("HTTPS seed fetch: %s from %s\n", statusLine.c_str(), seedHost.c_str());
return;
}
std::string body = response.substr(headerEnd + 4);
// Parse one address per line: "address:port" or just "address"
int found = 0;
std::istringstream lines(body);
std::string line;
while (std::getline(lines, line))
{
for (CNetAddr& ip : vaddr)
{
CAddress addr(CService(ip, GetDefaultPort()));
if (fShutdown)
return;
// Trim whitespace and carriage returns
while (!line.empty() && (line.back() == '\r' || line.back() == ' ' || line.back() == '\t'))
line.pop_back();
while (!line.empty() && (line.front() == ' ' || line.front() == '\t'))
line.erase(line.begin());
if (line.empty() || line[0] == '#')
continue;
// Parse address:port
std::string addrStr = line;
int port = GetDefaultPort();
// For .onion addresses, the last colon before port is after ".onion"
size_t onionPos = addrStr.find(".onion:");
if (onionPos != std::string::npos) {
port = atoi(addrStr.substr(onionPos + 7).c_str());
addrStr = addrStr.substr(0, onionPos + 6); // keep ".onion"
} else if (addrStr.find(".onion") == std::string::npos) {
// Clearnet address - find last colon for port
size_t colonPos = addrStr.rfind(':');
if (colonPos != std::string::npos) {
port = atoi(addrStr.substr(colonPos + 1).c_str());
addrStr = addrStr.substr(0, colonPos);
}
}
if (port <= 0 || port > 65535)
port = GetDefaultPort();
CNetAddr parsed;
bool resolved = parsed.SetSpecial(addrStr);
if (!resolved) {
std::vector<CNetAddr> vIP;
if (LookupHost(addrStr.c_str(), vIP, 1, false) && !vIP.empty()) {
parsed = vIP[0];
resolved = true;
}
}
if (resolved) {
CAddress addr(CService(parsed, port));
addr.nTime = GetTime() - 3*24*60*60; // 3 days ago
addrman.Add(addr, CNetAddr(strDNSSeed[seed_idx], true));
addrman.Add(addr, CNetAddr("https-seed", true));
found++;
}
}
}
printf("%d addresses found from DNS seeds\n", found);
printf("%d addresses found from HTTPS seed list (%s)\n", found, seedHost.c_str());
} catch (std::exception& e) {
printf("HTTPS seed fetch failed: %s\n", e.what());
if (ssl) { SSL_shutdown(ssl); SSL_free(ssl); }
if (ctx) SSL_CTX_free(ctx);
if (hSocket != INVALID_SOCKET) closesocket(hSocket);
}
}
void ThreadDNSAddressSeed(void* parg)
void ThreadHTTPSeedFetch(void* parg)
{
RenameThread("Triangles-dnsseed");
RenameThread("Triangles-httpseed");
try
{
vnThreadsRunning[THREAD_DNSSEED]++;
ThreadDNSAddressSeed2(parg);
vnThreadsRunning[THREAD_DNSSEED]--;
vnThreadsRunning[THREAD_HTTPSEED]++;
ThreadHTTPSeedFetch2(parg);
vnThreadsRunning[THREAD_HTTPSEED]--;
}
catch (std::exception& e) {
vnThreadsRunning[THREAD_DNSSEED]--;
PrintException(&e, "ThreadDNSAddressSeed()");
vnThreadsRunning[THREAD_HTTPSEED]--;
PrintException(&e, "ThreadHTTPSeedFetch()");
} catch (...) {
vnThreadsRunning[THREAD_DNSSEED]--;
PrintException(NULL, "ThreadDNSAddressSeed()");
vnThreadsRunning[THREAD_HTTPSEED]--;
PrintException(NULL, "ThreadHTTPSeedFetch()");
}
printf("ThreadDNSAddressSeed exited\n");
printf("ThreadHTTPSeedFetch exited\n");
}
void ThreadOpenConnections(void* parg)
@@ -1526,18 +1694,35 @@ void static ThreadStakeMiner(void* parg)
{
printf("ThreadStakeMiner started\n");
CWallet* pwallet = (CWallet*)parg;
try
int nConsecutiveErrors = 0;
while (!fShutdown)
{
vnThreadsRunning[THREAD_STAKE_MINER]++;
StakeMiner(pwallet);
vnThreadsRunning[THREAD_STAKE_MINER]--;
}
catch (std::exception& e) {
vnThreadsRunning[THREAD_STAKE_MINER]--;
PrintException(&e, "ThreadStakeMiner()");
} catch (...) {
vnThreadsRunning[THREAD_STAKE_MINER]--;
PrintException(NULL, "ThreadStakeMiner()");
try
{
vnThreadsRunning[THREAD_STAKE_MINER]++;
StakeMiner(pwallet);
vnThreadsRunning[THREAD_STAKE_MINER]--;
break; // normal exit
}
catch (std::exception& e) {
vnThreadsRunning[THREAD_STAKE_MINER]--;
nConsecutiveErrors++;
printf("ThreadStakeMiner() exception: %s (attempt %d)\n", e.what(), nConsecutiveErrors);
if (nConsecutiveErrors >= 10) {
printf("ThreadStakeMiner() too many consecutive errors, giving up\n");
break;
}
MilliSleep(5000); // wait 5 seconds before retrying
} catch (...) {
vnThreadsRunning[THREAD_STAKE_MINER]--;
nConsecutiveErrors++;
printf("ThreadStakeMiner() unknown exception (attempt %d)\n", nConsecutiveErrors);
if (nConsecutiveErrors >= 10) {
printf("ThreadStakeMiner() too many consecutive errors, giving up\n");
break;
}
MilliSleep(5000);
}
}
printf("ThreadStakeMiner exiting, %d threads remaining\n", vnThreadsRunning[THREAD_STAKE_MINER]);
}
@@ -1586,30 +1771,8 @@ void ThreadOpenConnections2(void* parg)
if (fShutdown)
return;
// Add hardcoded seed nodes when we have no connections.
// Original check (addrman.size()==0) was too conservative - stale entries
// in peers.dat would prevent fallback to working hardcoded IPs forever.
{
LOCK(cs_vNodes);
bool fNoOutbound = true;
for (CNode* pnode : vNodes) {
if (!pnode->fInbound) { fNoOutbound = false; break; }
}
if (fNoOutbound && (GetTime() - nStart > 10) && !fTestNet)
{
std::vector<CAddress> vAdd;
for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++)
{
struct in_addr ip;
memcpy(&ip, &pnSeed[i], sizeof(ip));
CAddress addr(CService(ip, GetDefaultPort()));
addr.nTime = GetTime() - GetRand(60*60); // seen recently
vAdd.push_back(addr);
}
addrman.Add(vAdd, CNetAddr("127.0.0.1"));
printf("No outbound connections after 10s, added %d hardcoded seeds\n", (int)vAdd.size());
}
}
// Hardcoded seed fallback removed - peer discovery is now fully dynamic
// via HTTP seed list from seeds.cryptographic-triangles.org
//
// Choose an address to connect to based on most recently seen
@@ -2112,9 +2275,14 @@ void StartNode(void* parg)
if (fUseUPnP)
MapPort();
// DNS seed lookup
if (!NewThread(ThreadDNSAddressSeed, NULL))
printf("Error: NewThread(ThreadDNSAddressSeed) failed\n");
// HTTP seed list fetch — only as a standalone thread if onion seeding is disabled,
// since ThreadOnionSeed already calls ThreadHTTPSeedFetch2 internally.
if (GetBoolArg("-onionseed", true))
printf("HTTP seed fetch handled by onion seed thread\n");
else if (GetBoolArg("-noseedurl", false))
printf("HTTP seed fetch disabled\n");
else if (!NewThread(ThreadHTTPSeedFetch, NULL))
printf("Error: NewThread(ThreadHTTPSeedFetch) failed\n");
// Send and receive from sockets, accept connections
if (!NewThread(ThreadSocketHandler, NULL))
@@ -2172,7 +2340,7 @@ bool StopNode()
#ifdef USE_UPNP
if (vnThreadsRunning[THREAD_UPNP] > 0) printf("ThreadMapPort still running\n");
#endif
if (vnThreadsRunning[THREAD_DNSSEED] > 0) printf("ThreadDNSAddressSeed still running\n");
if (vnThreadsRunning[THREAD_HTTPSEED] > 0) printf("ThreadHTTPSeedFetch still running\n");
if (vnThreadsRunning[THREAD_ADDEDCONNECTIONS] > 0) printf("ThreadOpenAddedConnections still running\n");
if (vnThreadsRunning[THREAD_DUMPADDRESS] > 0) printf("ThreadDumpAddresses still running\n");
if (vnThreadsRunning[THREAD_STAKE_MINER] > 0) printf("ThreadStakeMiner still running\n");
+1 -1
View File
@@ -103,7 +103,7 @@ enum threadId
THREAD_MESSAGEHANDLER,
THREAD_RPCLISTENER,
THREAD_UPNP,
THREAD_DNSSEED,
THREAD_HTTPSEED,
THREAD_ADDEDCONNECTIONS,
THREAD_DUMPADDRESS,
THREAD_RPCHANDLER,
+13 -225
View File
@@ -1,256 +1,44 @@
// Copyright (c) 2024 Triangles developers
// Network Bootstrap Implementation for Triangles v5.0.0.0
// Network Bootstrap Implementation for Triangles
// Distributed under the MIT/X11 software license
#include "net_bootstrap.h"
#include "main.h"
#include "net.h"
#include "util.h"
#include "init.h"
#include "tor/onion_v3.h"
namespace NetBootstrap {
// Forward declarations
bool InitializeLegacyBootstrap();
bool InitializeMixedBootstrap();
bool InitializeTorOnlyBootstrap();
// Global variable for tracking network activity
int64_t nTimeBestReceived = 0;
BootstrapMode GetBootstrapMode() {
// Determine bootstrap mode based on configuration
bool torEnabled = GetBoolArg("-tor", false) || GetBoolArg("-proxy", false);
bool onlyTor = GetBoolArg("-onlynet", false) && GetArg("-onlynet", "") == "tor";
if (onlyTor) {
printf("Network Bootstrap: Tor-only mode selected\n");
return BOOTSTRAP_TOR_ONLY;
} else if (torEnabled) {
printf("Network Bootstrap: Mixed Tor + clearnet mode selected\n");
return BOOTSTRAP_TOR_MIXED;
} else {
printf("Network Bootstrap: Legacy clearnet mode selected\n");
return BOOTSTRAP_LEGACY;
}
}
bool InitializeNetworkBootstrap(BootstrapMode mode) {
printf("Initializing network bootstrap (mode: %d)...\n", mode);
try {
switch (mode) {
case BOOTSTRAP_LEGACY:
return InitializeLegacyBootstrap();
case BOOTSTRAP_TOR_MIXED:
return InitializeMixedBootstrap();
case BOOTSTRAP_TOR_ONLY:
return InitializeTorOnlyBootstrap();
default:
printf("ERROR: Unknown bootstrap mode: %d\n", mode);
return false;
}
} catch (const std::exception& e) {
printf("ERROR: Exception in network bootstrap: %s\n", e.what());
return false;
}
}
bool InitializeLegacyBootstrap() {
printf("Initializing legacy network bootstrap for old wallet compatibility...\n");
// Use DNS seeds and hardcoded IP addresses
// This ensures old wallets can still connect
// Add DNS seed nodes
for (int i = 0; strDNSSeed[i] != NULL; i++) {
printf("Adding DNS seed: %s\n", strDNSSeed[i]);
// DNS resolution will be handled by the existing network code
}
// Add hardcoded IP seed nodes
for (int i = 0; i < ARRAYLEN(pnSeed); i++) {
if (pnSeed[i] != 0) {
printf("Adding IP seed: 0x%08x\n", pnSeed[i]);
// IP seeds will be processed by existing network code
}
}
printf("Legacy bootstrap initialized successfully\n");
return true;
}
bool InitializeMixedBootstrap() {
printf("Initializing mixed Tor + clearnet bootstrap...\n");
// Initialize legacy bootstrap first
if (!InitializeLegacyBootstrap()) {
printf("ERROR: Failed to initialize legacy bootstrap\n");
return false;
}
// Initialize Tor v3 functionality
if (!InitTorV3()) {
printf("WARNING: Failed to initialize Tor v3, continuing with clearnet only\n");
return true; // Don't fail completely, just continue without Tor
}
// Get Tor manager instance
CTorV3Manager* torManager = CTorV3Manager::GetInstance();
if (!torManager) {
printf("WARNING: Could not get Tor manager instance\n");
return true;
}
// Connect to Tor seed nodes
std::vector<std::string> torSeeds = torManager->GetKnownSeederNodes();
printf("Found %d Tor seed nodes\n", (int)torSeeds.size());
for (const std::string& seed : torSeeds) {
printf("Attempting to connect to Tor seed: %s\n", seed.c_str());
torManager->ConnectToSeederNode(seed);
}
printf("Mixed bootstrap initialized successfully\n");
return true;
}
bool InitializeTorOnlyBootstrap() {
printf("Initializing Tor-only bootstrap...\n");
// Initialize Tor v3 functionality
if (!InitTorV3()) {
printf("ERROR: Failed to initialize Tor v3 for Tor-only mode\n");
return false;
}
// Get Tor manager instance
CTorV3Manager* torManager = CTorV3Manager::GetInstance();
if (!torManager) {
printf("ERROR: Could not get Tor manager instance for Tor-only mode\n");
return false;
}
// Enable Tor
torManager->SetTorEnabled(true);
// Create hidden service if configured
if (GetBoolArg("-torhiddenservice", false)) {
int hiddenServicePort = GetArg("-torhiddenserviceport", NetworkConfig::TOR_HIDDEN_SERVICE_PORT);
if (!torManager->CreateWalletHiddenService(hiddenServicePort)) {
printf("WARNING: Failed to create hidden service\n");
} else {
printf("Created hidden service on port %d\n", hiddenServicePort);
// Register as seeder if enabled
if (GetBoolArg("-torseeder", false)) {
std::string onionAddress = torManager->GetWalletOnionAddress();
if (!onionAddress.empty()) {
torManager->RegisterAsSeederNode(onionAddress, hiddenServicePort);
printf("Registered as Tor seeder: %s:%d\n", onionAddress.c_str(), hiddenServicePort);
}
}
}
}
// Connect to Tor seed nodes
std::vector<std::string> torSeeds = torManager->GetKnownSeederNodes();
printf("Found %d Tor seed nodes for Tor-only mode\n", (int)torSeeds.size());
if (torSeeds.empty()) {
printf("WARNING: No Tor seed nodes available for Tor-only mode\n");
return false;
}
int connected = 0;
for (const std::string& seed : torSeeds) {
printf("Attempting to connect to Tor seed: %s\n", seed.c_str());
if (torManager->ConnectToSeederNode(seed)) {
connected++;
}
}
if (connected == 0) {
printf("ERROR: Failed to connect to any Tor seed nodes\n");
return false;
}
printf("Tor-only bootstrap initialized successfully (%d connections)\n", connected);
return true;
}
bool IsOldWalletCompatible(int protocolVersion) {
return protocolVersion >= NetworkConfig::MIN_PROTOCOL_VERSION_OLD_WALLET;
}
std::vector<std::string> GetCompatibleSeedNodes(int protocolVersion) {
std::vector<std::string> seeds;
if (protocolVersion >= NetworkConfig::CURRENT_PROTOCOL_VERSION) {
// Modern wallet - can use all seed types
// Add Tor v3 seeds
CTorV3Manager* torManager = CTorV3Manager::GetInstance();
if (torManager) {
std::vector<std::string> torSeeds = torManager->GetKnownSeederNodes();
seeds.insert(seeds.end(), torSeeds.begin(), torSeeds.end());
}
// Add DNS seeds
for (int i = 0; strDNSSeed[i] != NULL; i++) {
seeds.push_back(std::string(strDNSSeed[i]));
}
} else if (IsOldWalletCompatible(protocolVersion)) {
// Old but compatible wallet - use DNS seeds only
for (int i = 0; strDNSSeed[i] != NULL; i++) {
seeds.push_back(std::string(strDNSSeed[i]));
}
}
return seeds;
}
NetworkHealth GetNetworkHealth() {
NetworkHealth health;
health.connectedPeers = 0;
health.torPeers = 0;
health.clearnetPeers = 0;
health.isBootstrapped = false;
health.isSyncing = false;
health.lastBlockTime = 0;
// Count connected peers
{
LOCK(cs_vNodes);
health.connectedPeers = vNodes.size();
// Count Tor vs clearnet peers
for (CNode* pnode : vNodes) {
std::string addr = pnode->addr.ToString();
if (addr.find(".onion") != std::string::npos) {
health.torPeers++;
} else {
health.clearnetPeers++;
}
}
}
// Check if we're bootstrapped (have at least 3 connections)
// Bootstrapped if at least 3 connections
health.isBootstrapped = (health.connectedPeers >= 3);
// Check sync status
extern int nBestHeight;
extern int64_t nTimeBestReceived;
health.isSyncing = (nTimeBestReceived > 0 &&
GetTime() - nTimeBestReceived < 3600); // Synced within last hour
health.lastBlockTime = nTimeBestReceived;
// Sync status (nTimeBestReceived declared in main.h)
health.isSyncing = (::nTimeBestReceived > 0 &&
GetTime() - ::nTimeBestReceived < 3600);
health.lastBlockTime = ::nTimeBestReceived;
return health;
}
} // namespace NetBootstrap
} // namespace NetBootstrap
+16 -57
View File
@@ -1,5 +1,5 @@
// Copyright (c) 2024 Triangles developers
// Network Bootstrap Configuration for Triangles v5.0.0.0
// Network Bootstrap Configuration for Triangles
// Distributed under the MIT/X11 software license
#ifndef TRIANGLES_NET_BOOTSTRAP_H
@@ -9,86 +9,45 @@
#include <string>
#include <vector>
// Network bootstrap configuration for preserving old wallets
// while enabling modern Tor v3 functionality
// Tor-native network bootstrap configuration.
// All connections route through embedded Tor. Clearnet is disabled.
namespace NetBootstrap {
// DNS seed nodes for initial bootstrap (clearnet fallback)
// Unused attribute prevents warnings
static const char* strDNSSeed[] __attribute__((unused)) = {
"seed1.cryptographic-triangles.org",
"seed2.cryptographic-triangles.org",
"seed3.cryptographic-triangles.org",
"backup-seed.cryptographic-triangles.org",
NULL
};
// Legacy IP seed nodes for old wallet compatibility
// These should be actual IP addresses of stable nodes
static const unsigned int pnSeed[] __attribute__((unused)) = {
// Format: IP addresses in network byte order (little-endian)
// For IP a.b.c.d: (d << 24) | (c << 16) | (b << 8) | a
0xCE58E9C2, // DNS2-OpenClaw: 194.233.88.206
0x13A7D04A, // DNS3-Sami: 74.208.167.19
};
// Network protocol compatibility settings
struct NetworkConfig {
// Preserve compatibility with old wallets
static const int MIN_PROTOCOL_VERSION_OLD_WALLET = 70200;
static const int CURRENT_PROTOCOL_VERSION = 70205;
// Network ports (preserved from existing configuration)
// Network ports
static const int DEFAULT_PORT_MAINNET = 24112;
static const int DEFAULT_PORT_TESTNET = 24111;
static const int DEFAULT_RPC_PORT_MAINNET = 19112;
static const int DEFAULT_RPC_PORT_TESTNET = 19111;
// Connection limits for different node types
static const int MAX_CONNECTIONS_DEFAULT = 125;
static const int MAX_CONNECTIONS_TOR_ONLY = 64;
// Connection limits
static const int MAX_CONNECTIONS_DEFAULT = 64;
static const int MAX_OUTBOUND_CONNECTIONS = 8;
// Tor-specific configuration
// Tor configuration
static const int TOR_HIDDEN_SERVICE_PORT = 24112;
static constexpr const char* TOR_PROXY_DEFAULT = "127.0.0.1:9050";
// Network timeouts and retry settings
// Timeouts
static const int CONNECTION_TIMEOUT_SECONDS = 30;
static const int PEER_DISCOVERY_INTERVAL_SECONDS = 3600; // 1 hour
static const int SEEDER_ANNOUNCEMENT_INTERVAL_SECONDS = 1800; // 30 minutes
static const int PEER_DISCOVERY_INTERVAL_SECONDS = 3600;
};
// Bootstrap sequence for different wallet types
enum BootstrapMode {
BOOTSTRAP_LEGACY, // Old wallets without Tor
BOOTSTRAP_TOR_MIXED, // Mixed clearnet + Tor
BOOTSTRAP_TOR_ONLY // Tor-only mode
};
// Get bootstrap configuration based on wallet capabilities
BootstrapMode GetBootstrapMode();
// Initialize network bootstrap based on configuration
bool InitializeNetworkBootstrap(BootstrapMode mode);
// Backward compatibility helpers
bool IsOldWalletCompatible(int protocolVersion);
std::vector<std::string> GetCompatibleSeedNodes(int protocolVersion);
// Network health monitoring
struct NetworkHealth {
int connectedPeers;
int torPeers;
int clearnetPeers;
bool isBootstrapped;
bool isSyncing;
int64_t lastBlockTime;
};
NetworkHealth GetNetworkHealth();
} // namespace NetBootstrap
#endif // TRIANGLES_NET_BOOTSTRAP_H
#endif // TRIANGLES_NET_BOOTSTRAP_H
+10 -5
View File
@@ -6,6 +6,7 @@
#include "netbase.h"
#include "util.h"
#include "sync.h"
#include <openssl/evp.h>
#ifndef WIN32
#include <sys/fcntl.h>
@@ -860,15 +861,19 @@ std::string CNetAddr::ToStringIP() const
unsigned char addr35[35];
memcpy(addr35, tor_v3_pubkey, 32);
// Compute checksum: SHA3-256(".onion checksum" || pubkey || version)[:2]
// For now use a simplified checksum from the stored data
unsigned char checksumInput[15 + 32 + 1];
memcpy(checksumInput, ".onion checksum", 15);
memcpy(checksumInput + 15, tor_v3_pubkey, 32);
checksumInput[47] = 0x03; // version
// SHA-256 as fallback (SHA3-256 via tor_crypto_compat.h for full impl)
uint256 hash = Hash(checksumInput, checksumInput + 48);
addr35[32] = ((unsigned char*)&hash)[0];
addr35[33] = ((unsigned char*)&hash)[1];
unsigned char sha3hash[32];
unsigned int sha3len = 0;
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(mdctx, EVP_sha3_256(), NULL);
EVP_DigestUpdate(mdctx, checksumInput, 48);
EVP_DigestFinal_ex(mdctx, sha3hash, &sha3len);
EVP_MD_CTX_free(mdctx);
addr35[32] = sha3hash[0];
addr35[33] = sha3hash[1];
addr35[34] = 0x03; // version
return EncodeBase32(addr35, 35) + ".onion";
}
+7 -5
View File
@@ -2,12 +2,14 @@
#ifndef TRIANGLES_ONIONSEED_H
#define TRIANGLES_ONIONSEED_H
// hidden service seeds
// v5 hard fork: v2 onion seeds removed (Tor v2 deprecated Oct 2021)
// v3 onion seeds will be added when bootstrap nodes are deployed
// Hardcoded onion seed nodes for initial peer discovery.
// Also fetched dynamically via https://seeds.cryptographic-triangles.org/seeds.txt
static const char *strMainNetOnionSeed[][1] = {
{"gxvrhv3qitnc6kobrhsrse46bmcfitnybapor3or3oczzuxn6hfzxyid.onion"},
{"futmtrvh6j34t7s6yjdxfia6iwuyfzwh4k5eqfof5kfhoqk3xmi3qoqd.onion"},
{"jbpfhe7zw3qm67wy3j2ayysp3mnrjobopthnko3b3sgahqtecblwqmid.onion"},
{"uddaxjbo3lh2zskg7w6gwln4ty5cel7q4c5jbx7fdtv6zf2j47gdlyad.onion"},
{"el5sirhhleecuctpeeprelzubpqmoqivvra3rzlwbjttinxa4fq3wnid.onion"},
{"sj5dhybnlp3v4y5niyc5unrnd6s43lyx5ibup7rolyosjbi2u2hsbvyd.onion"},
{"i3kr5meha7se4ns3wss3h7v46m6uksfzv4wrohdqxpj6n35wyo2bvlid.onion"},
{NULL}
};
+181 -4
View File
@@ -16,6 +16,8 @@
#include <boost/filesystem.hpp>
#include <set>
IntroDialog::IntroDialog(QWidget *parent) :
QDialog(parent)
{
@@ -190,6 +192,24 @@ bool IntroDialog::pickDataDirectory()
settings.setValue("strDataDir", dataDir);
}
// Check for pending data directory migration
if (settings.value("fPendingDataDirMigration", false).toBool()) {
QString oldDir = settings.value("strDataDirPrevious", "").toString();
if (!oldDir.isEmpty() && oldDir != dataDir) {
if (!migrateDataDirectory(oldDir, dataDir)) {
// Migration failed - revert to old directory
QMessageBox::warning(0, "Triangles",
QString("Data directory migration failed.\nContinuing with the previous directory:\n%1")
.arg(oldDir));
dataDir = oldDir;
settings.setValue("strDataDir", oldDir);
}
}
// Clear migration state regardless
settings.remove("strDataDirPrevious");
settings.setValue("fPendingDataDirMigration", false);
}
// If the saved path is the default, don't set -datadir (let normal defaults work)
QString defaultDir = QString::fromStdString(GetDefaultDataDir().string());
if (dataDir != defaultDir) {
@@ -205,9 +225,22 @@ bool IntroDialog::pickDataDirectory()
return false;
}
// Offer bootstrap download on each startup (unless user checked "don't ask again")
// Auto-bootstrap: if no blockchain data exists, download automatically.
// If data exists, offer optional re-download (unless user checked "don't ask again").
fs::path dataDirPath(dataDir.toStdString());
if (!settings.value("bootstrapDontAsk", false).toBool())
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataDirPath);
bool userWantsBootstrap = false;
if (needsBootstrap)
{
// No blockchain data — bootstrap automatically, just inform the user
QMessageBox::information(0, "Triangles",
"No blockchain data found.\n\n"
"Downloading the latest blockchain snapshot automatically.\n"
"This will only take a few minutes.");
userWantsBootstrap = true;
}
else if (!settings.value("bootstrapDontAsk", false).toBool())
{
QMessageBox msgBox;
msgBox.setWindowTitle("Triangles");
@@ -227,7 +260,10 @@ bool IntroDialog::pickDataDirectory()
if (dontAskBox->isChecked())
settings.setValue("bootstrapDontAsk", true);
if (ret == QMessageBox::Yes)
userWantsBootstrap = (ret == QMessageBox::Yes);
}
if (userWantsBootstrap)
{
std::string host = Bootstrap::DEFAULT_HOST;
std::string strError;
@@ -271,7 +307,148 @@ bool IntroDialog::pickDataDirectory()
progress.setValue(100);
}
}
}
return true;
}
static void copyDirectoryRecursive(const boost::filesystem::path& src,
const boost::filesystem::path& dst)
{
namespace fs = boost::filesystem;
fs::create_directories(dst);
for (fs::directory_iterator it(src), end; it != end; ++it) {
fs::path dstChild = dst / it->path().filename();
if (fs::is_directory(it->path())) {
copyDirectoryRecursive(it->path(), dstChild);
} else {
fs::copy_file(it->path(), dstChild, fs::copy_options::overwrite_existing);
}
}
}
bool IntroDialog::migrateDataDirectory(const QString& oldPath, const QString& newPath)
{
namespace fs = boost::filesystem;
fs::path srcDir(oldPath.toStdString());
fs::path dstDir(newPath.toStdString());
if (!fs::exists(srcDir) || !fs::is_directory(srcDir))
return false;
// Create destination directory
try {
fs::create_directories(dstDir);
} catch (const fs::filesystem_error& e) {
printf("Migration: Cannot create destination directory: %s\n", e.what());
return false;
}
// Check free space
try {
quint64 srcSize = 0;
for (fs::recursive_directory_iterator it(srcDir), end; it != end; ++it) {
if (fs::is_regular_file(*it))
srcSize += fs::file_size(*it);
}
fs::space_info si = fs::space(dstDir);
if (si.available < srcSize + (50 * 1024 * 1024)) { // 50MB headroom
printf("Migration: Insufficient disk space. Need %llu, have %llu\n",
(unsigned long long)srcSize, (unsigned long long)si.available);
return false;
}
} catch (const fs::filesystem_error& e) {
printf("Migration: Cannot check disk space: %s\n", e.what());
return false;
}
// Files/directories to skip during copy
static const std::set<std::string> skipFiles = {
".lock",
"debug.log",
"db.log",
};
// Show progress dialog
QProgressDialog progress("Moving data directory...", QString(), 0, 0, 0);
progress.setWindowTitle("Triangles - Data Migration");
progress.setWindowModality(Qt::ApplicationModal);
progress.setMinimumDuration(0);
progress.setCancelButton(0);
progress.show();
QApplication::processEvents();
// Phase 1: Copy wallet.dat FIRST (most critical file)
fs::path walletSrc = srcDir / "wallet.dat";
fs::path walletDst = dstDir / "wallet.dat";
if (fs::exists(walletSrc)) {
progress.setLabelText("Copying wallet.dat...");
QApplication::processEvents();
try {
// Copy to temp name first, then rename for atomicity
fs::path walletTmp = dstDir / "wallet.dat.migrating";
fs::copy_file(walletSrc, walletTmp, fs::copy_options::overwrite_existing);
// Verify copy by checking file size
if (fs::file_size(walletTmp) != fs::file_size(walletSrc)) {
fs::remove(walletTmp);
printf("Migration: wallet.dat copy size mismatch!\n");
return false;
}
// Rename into place
if (fs::exists(walletDst))
fs::remove(walletDst);
fs::rename(walletTmp, walletDst);
} catch (const fs::filesystem_error& e) {
printf("Migration: Failed to copy wallet.dat: %s\n", e.what());
return false; // Abort - wallet is critical
}
}
// Phase 2: Copy everything else
int filesCopied = 0;
try {
for (fs::directory_iterator it(srcDir), end; it != end; ++it) {
std::string filename = it->path().filename().string();
// Skip special files
if (skipFiles.count(filename))
continue;
// Skip wallet.dat (already copied)
if (filename == "wallet.dat")
continue;
fs::path dst = dstDir / filename;
progress.setLabelText(QString("Copying %1...").arg(QString::fromStdString(filename)));
QApplication::processEvents();
if (fs::is_directory(it->path())) {
copyDirectoryRecursive(it->path(), dst);
} else {
fs::copy_file(it->path(), dst, fs::copy_options::overwrite_existing);
}
filesCopied++;
}
} catch (const fs::filesystem_error& e) {
// Non-wallet copy failure: log but don't abort
// Chain data can be re-synced; wallet was already safely copied
printf("Migration: Warning: failed to copy some files: %s\n", e.what());
}
// Phase 3: Rename old wallet.dat as safety backup (don't delete old dir)
try {
if (fs::exists(walletSrc)) {
fs::rename(walletSrc, srcDir / "wallet.dat.bak-migrated");
}
} catch (...) {
// Not critical
}
progress.close();
printf("Migration: Successfully copied %d items from %s to %s\n",
filesCopied, srcDir.string().c_str(), dstDir.string().c_str());
return true;
}
+7
View File
@@ -25,6 +25,13 @@ public:
*/
static bool pickDataDirectory();
/**
* Migrate data directory contents from oldPath to newPath.
* Returns true on success, false on failure.
* Shows a progress dialog during the copy.
*/
static bool migrateDataDirectory(const QString& oldPath, const QString& newPath);
private slots:
void on_browseButton_clicked();
void on_defaultRadio_toggled(bool checked);
+202 -1
View File
@@ -7,12 +7,21 @@
#include "optionsmodel.h"
#include "dialog_move_handler.h"
#include "init.h"
#include "util.h"
#include <boost/filesystem.hpp>
#include <QDir>
#include <QFileDialog>
#include <QGroupBox>
#include <QIntValidator>
#include <QLocale>
#include <QMessageBox>
#include <QProcess>
#include <QRegExp>
#include <QRegExpValidator>
#include <QSettings>
OptionsDialog::OptionsDialog(QWidget *parent) :
QDialog(parent),
@@ -21,12 +30,54 @@ OptionsDialog::OptionsDialog(QWidget *parent) :
mapper(0),
fRestartWarningDisplayed_Proxy(false),
fRestartWarningDisplayed_Lang(false),
fProxyIpValid(true)
fProxyIpValid(true),
dataDirPath(0),
dataDirFreeSpaceLabel(0)
{
ui->setupUi(this);
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Window);
ui->wCaption->installEventFilter(new DialogMoveHandler(this));
/* Data Directory section in Main tab */
m_currentDataDir = QString::fromStdString(GetDataDir(false).string());
m_pendingDataDir.clear();
QGroupBox *groupDataDir = new QGroupBox(tr("Data Directory"), this);
groupDataDir->setStyleSheet(
"QGroupBox { border: 1px solid #61280E; margin-top: 8px; padding-top: 16px; color: #f26522; }"
"QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px; }");
QVBoxLayout *dataDirLayout = new QVBoxLayout(groupDataDir);
QHBoxLayout *dataDirPathLayout = new QHBoxLayout();
dataDirPath = new QLineEdit(m_currentDataDir, groupDataDir);
dataDirPath->setReadOnly(true);
dataDirPath->setStyleSheet("QLineEdit { background-color: #1c1c1c; border: 1px solid #f26522; color: #f26522; padding: 2px; }");
QPushButton *dataDirBrowseButton = new QPushButton(tr("Browse..."), groupDataDir);
dataDirBrowseButton->setStyleSheet(
"QPushButton { background-color: #000; color: #f26522; border: 1px solid #f26522; padding: 2px 12px; min-height: 20px; }"
"QPushButton:hover { background-color: #61280E; }"
"QPushButton:pressed:flat { color: #000; background-color: #f26522; }");
dataDirPathLayout->addWidget(dataDirPath);
dataDirPathLayout->addWidget(dataDirBrowseButton);
dataDirLayout->addLayout(dataDirPathLayout);
dataDirFreeSpaceLabel = new QLabel(groupDataDir);
dataDirFreeSpaceLabel->setStyleSheet("color: #999; font-size: 11px;");
dataDirLayout->addWidget(dataDirFreeSpaceLabel);
// Insert into Main tab layout, before the vertical spacer (last item)
QVBoxLayout *mainTabLayout = qobject_cast<QVBoxLayout*>(ui->tabWidget->widget(0)->layout());
if (mainTabLayout) {
int spacerIndex = mainTabLayout->count() - 1; // vertical spacer is last
mainTabLayout->insertWidget(spacerIndex, groupDataDir);
}
connect(dataDirBrowseButton, SIGNAL(clicked()), this, SLOT(on_dataDirBrowseButton_clicked()));
updateDataDirFreeSpace();
/* Network elements init */
#ifndef USE_UPNP
ui->mapPortUpnp->setEnabled(false);
@@ -188,6 +239,8 @@ void OptionsDialog::setSaveButtonState(bool fState)
void OptionsDialog::on_okButton_clicked()
{
mapper->submit();
if (handleDataDirChange())
return; // restart flow handles closing
accept();
}
@@ -199,6 +252,7 @@ void OptionsDialog::on_cancelButton_clicked()
void OptionsDialog::on_applyButton_clicked()
{
mapper->submit();
handleDataDirChange();
disableApplyButton();
}
@@ -303,3 +357,150 @@ bool OptionsDialog::eventFilter(QObject *object, QEvent *event)
}
return QDialog::eventFilter(object, event);
}
void OptionsDialog::on_dataDirBrowseButton_clicked()
{
QString dir = QFileDialog::getExistingDirectory(
this, tr("Choose data directory"), m_currentDataDir);
if (!dir.isEmpty() && dir != m_currentDataDir)
{
m_pendingDataDir = dir;
dataDirPath->setText(dir);
updateDataDirFreeSpace();
enableApplyButton();
}
}
void OptionsDialog::updateDataDirFreeSpace()
{
namespace fs = boost::filesystem;
QString path = dataDirPath->text();
fs::path fsPath(path.toStdString());
try {
while (!fsPath.empty() && !fs::exists(fsPath))
fsPath = fsPath.parent_path();
if (!fsPath.empty()) {
fs::space_info si = fs::space(fsPath);
double freeGB = (double)si.available / (1024.0 * 1024.0 * 1024.0);
dataDirFreeSpaceLabel->setText(
tr("Free space: %1 GB").arg(QString::number(freeGB, 'f', 2)));
} else {
dataDirFreeSpaceLabel->setText(tr("Cannot determine free space"));
}
} catch (const fs::filesystem_error &) {
dataDirFreeSpaceLabel->setText(tr("Cannot determine free space"));
}
}
quint64 OptionsDialog::calculateDirSize(const QString& path)
{
namespace fs = boost::filesystem;
quint64 totalSize = 0;
try {
for (fs::recursive_directory_iterator it(path.toStdString()), end; it != end; ++it) {
if (fs::is_regular_file(*it))
totalSize += fs::file_size(*it);
}
} catch (...) {}
return totalSize;
}
bool OptionsDialog::handleDataDirChange()
{
if (m_pendingDataDir.isEmpty() || m_pendingDataDir == m_currentDataDir)
return false;
namespace fs = boost::filesystem;
fs::path destPath(m_pendingDataDir.toStdString());
// Check destination is writable
try {
fs::create_directories(destPath);
} catch (const fs::filesystem_error& e) {
QMessageBox::critical(this, tr("Error"),
tr("Cannot create directory: %1").arg(QString::fromStdString(e.what())));
m_pendingDataDir.clear();
dataDirPath->setText(m_currentDataDir);
updateDataDirFreeSpace();
return false;
}
// Check free space vs current data dir size
quint64 dataDirSize = calculateDirSize(m_currentDataDir);
try {
fs::space_info si = fs::space(destPath);
quint64 required = dataDirSize + (dataDirSize / 10); // 10% headroom
if (si.available < required) {
QMessageBox::critical(this, tr("Insufficient Space"),
tr("The destination has %1 MB free but the data directory requires approximately %2 MB.")
.arg(si.available / (1024*1024))
.arg(required / (1024*1024)));
m_pendingDataDir.clear();
dataDirPath->setText(m_currentDataDir);
updateDataDirFreeSpace();
return false;
}
} catch (const fs::filesystem_error&) {
// If we can't check space, proceed anyway
}
// Save migration state to QSettings
QSettings settings;
settings.setValue("strDataDirPrevious", m_currentDataDir);
settings.setValue("strDataDir", m_pendingDataDir);
settings.setValue("fPendingDataDirMigration", true);
// Ask about restart
QMessageBox msgBox(this);
msgBox.setWindowFlags(Qt::FramelessWindowHint);
msgBox.setWindowTitle(tr("Data Directory Changed"));
msgBox.setText(tr("The data directory will be moved from:\n%1\n\nTo:\n%2\n\n"
"This will happen when the wallet restarts.")
.arg(m_currentDataDir).arg(m_pendingDataDir));
msgBox.setIcon(QMessageBox::Information);
msgBox.setIconPixmap(QPixmap(":/msgbox/information"));
msgBox.setStyleSheet("QMessageBox { border: 2px solid #f26522; background-color: #000; color: #f26522; }");
QPushButton *restartBtn = msgBox.addButton(tr("Restart Now"), QMessageBox::AcceptRole);
QPushButton *laterBtn = msgBox.addButton(tr("Later"), QMessageBox::RejectRole);
QString btnStyle =
"QPushButton { background-color: #000; color: #f26522; border: 1px solid #f26522; "
"min-width: 120px; max-width: 120px; max-height: 20px; min-height: 20px; }"
"QPushButton:hover { background-color: #61280E; }"
"QPushButton:pressed:flat { color: #000; background-color: #f26522; }";
restartBtn->setStyleSheet(btnStyle);
laterBtn->setStyleSheet(btnStyle);
msgBox.exec();
if (msgBox.clickedButton() == restartBtn) {
performRestart();
return true;
}
return false;
}
void OptionsDialog::performRestart()
{
// Launch a new instance of ourselves
QString exePath = QApplication::applicationFilePath();
QStringList args = QApplication::arguments();
args.removeFirst(); // remove argv[0]
// Remove any existing -datadir argument so the new instance
// reads strDataDir from QSettings and performs migration
QMutableStringListIterator it(args);
while (it.hasNext()) {
QString arg = it.next();
if (arg.startsWith("-datadir") || arg.startsWith("/datadir"))
it.remove();
}
// Start new process detached so it survives our shutdown
QProcess::startDetached(exePath, args);
// Close dialog and trigger wallet shutdown
accept();
StartShutdown();
}
+15
View File
@@ -3,6 +3,9 @@
#include <QDialog>
class QLineEdit;
class QLabel;
namespace Ui {
class OptionsDialog;
}
@@ -45,17 +48,29 @@ private slots:
void updateDisplayUnit();
void handleProxyIpValid(QValidatedLineEdit *object, bool fState);
void applyTorDefaults(bool enabled);
void on_dataDirBrowseButton_clicked();
void updateDataDirFreeSpace();
signals:
void proxyIpValid(QValidatedLineEdit *object, bool fValid);
private:
bool handleDataDirChange();
void performRestart();
quint64 calculateDirSize(const QString& path);
Ui::OptionsDialog *ui;
OptionsModel *model;
MonitoredDataMapper *mapper;
bool fRestartWarningDisplayed_Proxy;
bool fRestartWarningDisplayed_Lang;
bool fProxyIpValid;
// Data directory widgets (built programmatically)
QLineEdit *dataDirPath;
QLabel *dataDirFreeSpaceLabel;
QString m_currentDataDir;
QString m_pendingDataDir;
};
#endif // OPTIONSDIALOG_H
Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 20 KiB

+5 -433
View File
@@ -108,438 +108,10 @@ Value getstakinginfo(const Array& params, bool fHelp)
return obj;
}
Value getworkex(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 2)
throw runtime_error(
"getworkex [data, coinbase]\n"
"If [data, coinbase] is not specified, returns extended work data.\n"
);
if (vNodes.empty())
throw JSONRPCError(-9, "Triangles is not connected!");
if (IsInitialBlockDownload())
throw JSONRPCError(-10, "Triangles is downloading blocks...");
if (pindexBest->nHeight >= CUTOFF_POW_BLOCK)
throw JSONRPCError(RPC_MISC_ERROR, "No more PoW blocks");
typedef map<uint256, pair<CBlock*, CScript> > mapNewBlock_t;
static mapNewBlock_t mapNewBlock;
static vector<CBlock*> vNewBlock;
static CReserveKey reservekey(pwalletMain);
if (params.size() == 0)
{
// Update block
static unsigned int nTransactionsUpdatedLast;
static CBlockIndex* pindexPrev;
static int64_t nStart;
static CBlock* pblock;
if (pindexPrev != pindexBest ||
(nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60))
{
if (pindexPrev != pindexBest)
{
// Deallocate old blocks since they're obsolete now
mapNewBlock.clear();
for (CBlock* pblock : vNewBlock)
delete pblock;
vNewBlock.clear();
}
nTransactionsUpdatedLast = nTransactionsUpdated;
pindexPrev = pindexBest;
nStart = GetTime();
// Create new block
pblock = CreateNewBlock(pwalletMain);
if (!pblock)
throw JSONRPCError(-7, "Out of memory");
vNewBlock.push_back(pblock);
}
// Update nTime
pblock->nTime = max(pindexPrev->GetPastTimeLimit()+1, GetAdjustedTime());
pblock->nNonce = 0;
// Update nExtraNonce
static unsigned int nExtraNonce = 0;
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
// Save
mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, pblock->vtx[0].vin[0].scriptSig);
// Prebuild hash buffers
char pmidstate[32];
char pdata[128];
char phash1[64];
FormatHashBuffers(pblock, pmidstate, pdata, phash1);
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
CTransaction coinbaseTx = pblock->vtx[0];
std::vector<uint256> merkle = pblock->GetMerkleBranch(0);
Object result;
result.push_back(Pair("data", HexStr(BEGIN(pdata), END(pdata))));
result.push_back(Pair("target", HexStr(BEGIN(hashTarget), END(hashTarget))));
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << coinbaseTx;
result.push_back(Pair("coinbase", HexStr(ssTx.begin(), ssTx.end())));
Array merkle_arr;
for (uint256 merkleh : merkle) {
merkle_arr.push_back(HexStr(BEGIN(merkleh), END(merkleh)));
}
result.push_back(Pair("merkle", merkle_arr));
return result;
}
else
{
// Parse parameters
vector<unsigned char> vchData = ParseHex(params[0].get_str());
vector<unsigned char> coinbase;
if(params.size() == 2)
coinbase = ParseHex(params[1].get_str());
if (vchData.size() != 128)
throw JSONRPCError(-8, "Invalid parameter");
CBlock* pdata = (CBlock*)&vchData[0];
// Byte reverse
for (int i = 0; i < 128/4; i++)
((unsigned int*)pdata)[i] = ByteReverse(((unsigned int*)pdata)[i]);
// Get saved block
if (!mapNewBlock.count(pdata->hashMerkleRoot))
return false;
CBlock* pblock = mapNewBlock[pdata->hashMerkleRoot].first;
pblock->nTime = pdata->nTime;
pblock->nNonce = pdata->nNonce;
if(coinbase.size() == 0)
pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second;
else
// Deserialize custom coinbase transaction from miner
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0];
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
return CheckWork(pblock, *pwalletMain, reservekey);
}
}
Value getwork(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 1)
throw runtime_error(
"getwork [data]\n"
"If [data] is not specified, returns formatted hash data to work on:\n"
" \"midstate\" : precomputed hash state after hashing the first half of the data (DEPRECATED)\n" // deprecated
" \"data\" : block data\n"
" \"hash1\" : formatted hash buffer for second hash (DEPRECATED)\n" // deprecated
" \"target\" : little endian hash target\n"
"If [data] is specified, tries to solve the block and returns true if it was successful.");
if (vNodes.empty())
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Triangles is not connected!");
if (IsInitialBlockDownload())
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Triangles is downloading blocks...");
if (pindexBest->nHeight >= CUTOFF_POW_BLOCK)
throw JSONRPCError(RPC_MISC_ERROR, "No more PoW blocks");
typedef map<uint256, pair<CBlock*, CScript> > mapNewBlock_t;
// NOTE: Thread safety issue - static variables accessed by multiple RPC threads
// without mutex protection. Low priority since PoW ended at block 9000 and
// getwork is rarely used. Consider adding std::mutex if usage increases.
static mapNewBlock_t mapNewBlock;
static vector<CBlock*> vNewBlock;
static CReserveKey reservekey(pwalletMain);
if (params.size() == 0)
{
// Update block
static unsigned int nTransactionsUpdatedLast;
static CBlockIndex* pindexPrev;
static int64_t nStart;
static CBlock* pblock;
if (pindexPrev != pindexBest ||
(nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60))
{
if (pindexPrev != pindexBest)
{
// Deallocate old blocks since they're obsolete now
mapNewBlock.clear();
for (CBlock* pblock : vNewBlock)
delete pblock;
vNewBlock.clear();
}
// Clear pindexPrev so future getworks make a new block, despite any failures from here on
pindexPrev = NULL;
// Store the pindexBest used before CreateNewBlock, to avoid races
nTransactionsUpdatedLast = nTransactionsUpdated;
CBlockIndex* pindexPrevNew = pindexBest;
nStart = GetTime();
// Create new block
pblock = CreateNewBlock(pwalletMain);
if (!pblock)
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
vNewBlock.push_back(pblock);
// Need to update only after we know CreateNewBlock succeeded
pindexPrev = pindexPrevNew;
}
// Update nTime
pblock->UpdateTime(pindexPrev);
pblock->nNonce = 0;
// Update nExtraNonce
static unsigned int nExtraNonce = 0;
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
// Save
mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, pblock->vtx[0].vin[0].scriptSig);
// Pre-build hash buffers
char pmidstate[32];
char pdata[128];
char phash1[64];
FormatHashBuffers(pblock, pmidstate, pdata, phash1);
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
Object result;
result.push_back(Pair("midstate", HexStr(BEGIN(pmidstate), END(pmidstate)))); // deprecated
result.push_back(Pair("data", HexStr(BEGIN(pdata), END(pdata))));
result.push_back(Pair("hash1", HexStr(BEGIN(phash1), END(phash1)))); // deprecated
result.push_back(Pair("target", HexStr(BEGIN(hashTarget), END(hashTarget))));
return result;
}
else
{
// Parse parameters
vector<unsigned char> vchData = ParseHex(params[0].get_str());
if (vchData.size() != 128)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
CBlock* pdata = (CBlock*)&vchData[0];
// Byte reverse
for (int i = 0; i < 128/4; i++)
((unsigned int*)pdata)[i] = ByteReverse(((unsigned int*)pdata)[i]);
// Get saved block
if (!mapNewBlock.count(pdata->hashMerkleRoot))
return false;
CBlock* pblock = mapNewBlock[pdata->hashMerkleRoot].first;
pblock->nTime = pdata->nTime;
pblock->nNonce = pdata->nNonce;
pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second;
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
return CheckWork(pblock, *pwalletMain, reservekey);
}
}
Value getblocktemplate(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 1)
throw runtime_error(
"getblocktemplate [params]\n"
"Returns data needed to construct a block to work on:\n"
" \"version\" : block version\n"
" \"previousblockhash\" : hash of current highest block\n"
" \"transactions\" : contents of non-coinbase transactions that should be included in the next block\n"
" \"coinbaseaux\" : data that should be included in coinbase\n"
" \"coinbasevalue\" : maximum allowable input to coinbase transaction, including the generation award and transaction fees\n"
" \"target\" : hash target\n"
" \"mintime\" : minimum timestamp appropriate for next block\n"
" \"curtime\" : current timestamp\n"
" \"mutable\" : list of ways the block template may be changed\n"
" \"noncerange\" : range of valid nonces\n"
" \"sigoplimit\" : limit of sigops in blocks\n"
" \"sizelimit\" : limit of block size\n"
" \"bits\" : compressed target of next block\n"
" \"height\" : height of the next block");
std::string strMode = "template";
if (params.size() > 0)
{
const Object& oparam = params[0].get_obj();
const Value& modeval = find_value(oparam, "mode");
if (modeval.type() == str_type)
strMode = modeval.get_str();
else if (modeval.type() == null_type)
{
/* Do nothing */
}
else
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
}
if (strMode != "template")
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
if (vNodes.empty())
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Triangles is not connected!");
if (IsInitialBlockDownload())
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Triangles is downloading blocks...");
if (pindexBest->nHeight >= CUTOFF_POW_BLOCK)
throw JSONRPCError(RPC_MISC_ERROR, "No more PoW blocks");
static CReserveKey reservekey(pwalletMain);
// Update block
static unsigned int nTransactionsUpdatedLast;
static CBlockIndex* pindexPrev;
static int64_t nStart;
static CBlock* pblock;
if (pindexPrev != pindexBest ||
(nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 5))
{
// Clear pindexPrev so future calls make a new block, despite any failures from here on
pindexPrev = NULL;
// Store the pindexBest used before CreateNewBlock, to avoid races
nTransactionsUpdatedLast = nTransactionsUpdated;
CBlockIndex* pindexPrevNew = pindexBest;
nStart = GetTime();
// Create new block
if(pblock)
{
delete pblock;
pblock = NULL;
}
pblock = CreateNewBlock(pwalletMain);
if (!pblock)
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
// Need to update only after we know CreateNewBlock succeeded
pindexPrev = pindexPrevNew;
}
// Update nTime
pblock->UpdateTime(pindexPrev);
pblock->nNonce = 0;
Array transactions;
map<uint256, int64_t> setTxIndex;
int i = 0;
CTxDB txdb("r");
for (CTransaction& tx : pblock->vtx)
{
uint256 txHash = tx.GetHash();
setTxIndex[txHash] = i++;
if (tx.IsCoinBase() || tx.IsCoinStake())
continue;
Object entry;
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << tx;
entry.push_back(Pair("data", HexStr(ssTx.begin(), ssTx.end())));
entry.push_back(Pair("hash", txHash.GetHex()));
MapPrevTx mapInputs;
map<uint256, CTxIndex> mapUnused;
bool fInvalid = false;
if (tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid))
{
entry.push_back(Pair("fee", (int64_t)(tx.GetValueIn(mapInputs) - tx.GetValueOut())));
Array deps;
for (MapPrevTx::value_type& inp : mapInputs)
{
if (setTxIndex.count(inp.first))
deps.push_back(setTxIndex[inp.first]);
}
entry.push_back(Pair("depends", deps));
int64_t nSigOps = tx.GetLegacySigOpCount();
nSigOps += tx.GetP2SHSigOpCount(mapInputs);
entry.push_back(Pair("sigops", nSigOps));
}
transactions.push_back(entry);
}
Object aux;
aux.push_back(Pair("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end())));
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
static Array aMutable;
if (aMutable.empty())
{
aMutable.push_back("time");
aMutable.push_back("transactions");
aMutable.push_back("prevblock");
}
Object result;
result.push_back(Pair("version", pblock->nVersion));
result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex()));
result.push_back(Pair("transactions", transactions));
result.push_back(Pair("coinbaseaux", aux));
result.push_back(Pair("coinbasevalue", (int64_t)pblock->vtx[0].vout[0].nValue));
result.push_back(Pair("target", hashTarget.GetHex()));
result.push_back(Pair("mintime", (int64_t)pindexPrev->GetPastTimeLimit()+1));
result.push_back(Pair("mutable", aMutable));
result.push_back(Pair("noncerange", "00000000ffffffff"));
result.push_back(Pair("sigoplimit", (int64_t)MAX_BLOCK_SIGOPS));
result.push_back(Pair("sizelimit", (int64_t)MAX_BLOCK_SIZE));
result.push_back(Pair("curtime", (int64_t)pblock->nTime));
result.push_back(Pair("bits", HexBits(pblock->nBits)));
result.push_back(Pair("height", (int64_t)(pindexPrev->nHeight+1)));
return result;
}
Value submitblock(const Array& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
throw runtime_error(
"submitblock <hex data> [optional-params-obj]\n"
"[optional-params-obj] parameter is currently ignored.\n"
"Attempts to submit new block to network.");
vector<unsigned char> blockData(ParseHex(params[0].get_str()));
CDataStream ssBlock(blockData, SER_NETWORK, PROTOCOL_VERSION);
CBlock block;
try {
ssBlock >> block;
}
catch (std::exception &e) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");
}
bool fAccepted = ProcessBlock(NULL, &block);
if (!fAccepted)
return "rejected";
return Value::null;
}
// PoW mining RPCs (getwork, getworkex, getblocktemplate, submitblock) removed.
// PoW ended at block 9000 (CUTOFF_POW_BLOCK). These dead-code mining pool
// interfaces were removed to reduce false-positive antivirus detections,
// since AV engines pattern-match nonce-incrementing loops and mining pool
// protocols as "cryptominer" signatures.
+27 -33
View File
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "net.h"
#include "addrman.h"
#include "trianglesrpc.h"
#include "alert.h"
#include "wallet.h"
@@ -13,37 +14,6 @@
using namespace json_spirit;
using namespace std;
namespace {
const char* BootstrapModeToString(NetBootstrap::BootstrapMode mode)
{
switch (mode)
{
case NetBootstrap::BOOTSTRAP_LEGACY:
return "legacy";
case NetBootstrap::BOOTSTRAP_TOR_MIXED:
return "tor_mixed";
case NetBootstrap::BOOTSTRAP_TOR_ONLY:
return "tor_only";
}
return "unknown";
}
NetBootstrap::BootstrapMode GetBootstrapModeForRPC()
{
const bool torEnabled = GetBoolArg("-tor", false) || GetBoolArg("-proxy", false);
const bool onlyTor = GetBoolArg("-onlynet", false) && GetArg("-onlynet", "") == "tor";
if (onlyTor)
return NetBootstrap::BOOTSTRAP_TOR_ONLY;
if (torEnabled)
return NetBootstrap::BOOTSTRAP_TOR_MIXED;
return NetBootstrap::BOOTSTRAP_LEGACY;
}
} // namespace
Value getnetworkinfo(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 0)
@@ -59,11 +29,10 @@ Value getnetworkinfo(const Array& params, bool fHelp)
Object healthObj;
healthObj.push_back(Pair("connectedpeers", health.connectedPeers));
healthObj.push_back(Pair("torpeers", health.torPeers));
healthObj.push_back(Pair("clearnetpeers", health.clearnetPeers));
healthObj.push_back(Pair("bootstrapped", health.isBootstrapped));
healthObj.push_back(Pair("syncing", health.isSyncing));
healthObj.push_back(Pair("lastblocktime", static_cast<boost::int64_t>(health.lastBlockTime)));
healthObj.push_back(Pair("bootstrapmode", BootstrapModeToString(GetBootstrapModeForRPC())));
healthObj.push_back(Pair("networkmode", "tor_native"));
Object obj;
obj.push_back(Pair("version", FormatFullVersion()));
@@ -199,3 +168,28 @@ Value sendalert(const Array& params, bool fHelp)
result.push_back(Pair("nCancel", alert.nCancel));
return result;
}
Value getseedlist(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 0)
throw runtime_error(
"getseedlist\n"
"Returns known .onion peer addresses from the address manager.\n"
"Used by the seed collector to build the dynamic seed list.");
vector<CAddress> vAddr = addrman.GetAddr();
Array ret;
for (const CAddress& addr : vAddr) {
if (!addr.IsTor())
continue;
Object obj;
obj.push_back(Pair("address", addr.ToStringIP()));
obj.push_back(Pair("port", (int)addr.GetPort()));
obj.push_back(Pair("lastseen", (boost::int64_t)addr.nTime));
ret.push_back(obj);
}
return ret;
}
+5 -5
View File
@@ -370,20 +370,20 @@ Value signrawtransaction(const Array& params, bool fHelp)
{
CTransaction tempTx;
MapPrevTx mapPrevTx;
MapPrevTx mapEmpty;
CTxDB txdb("r");
map<uint256, CTxIndex> unused;
bool fInvalid;
// FetchInputs aborts on failure, so we go one at a time.
tempTx.vin.push_back(mergedTx.vin[i]);
tempTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid);
tempTx.FetchInputs(txdb, mapEmpty, false, false, mapPrevTx, fInvalid);
// Copy results into mapPrevOut:
for (const CTxIn& txin : tempTx.vin)
{
const uint256& prevHash = txin.prevout.hash;
if (mapPrevTx.count(prevHash) && mapPrevTx[prevHash].second.vout.size()>txin.prevout.n)
mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey;
MapPrevTx::const_iterator mi = mapPrevTx.find(txin.prevout);
if (mi != mapPrevTx.end())
mapPrevOut[txin.prevout] = mi->second.scriptPubKey;
}
}
+2 -1
View File
@@ -1911,7 +1911,8 @@ Value clearwallettransactions(const Array& params, bool fHelp)
};
pwalletMain->mapWallet.erase(hash);
pwalletMain->NotifyTransactionChanged(pwalletMain, hash, CT_DELETED);
try { pwalletMain->NotifyTransactionChanged(pwalletMain, hash, CT_DELETED); }
catch (...) { }
nTransactions++;
};
-28
View File
@@ -6,8 +6,6 @@
#include "util.h"
#include "wallet.h"
extern void SHA256Transform(void* pstate, void* pinput, const void* pinit);
BOOST_AUTO_TEST_SUITE(miner_tests)
static
@@ -198,30 +196,4 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
pindexBest->nHeight = nHeight;
}
BOOST_AUTO_TEST_CASE(sha256transform_equality)
{
unsigned int pSHA256InitState[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
// unsigned char pstate[32];
unsigned char pinput[64];
int i;
for (i = 0; i < 32; i++) {
pinput[i] = i;
pinput[i+32] = 0;
}
uint256 hash;
SHA256Transform(&hash, pinput, pSHA256InitState);
BOOST_TEST_MESSAGE(hash.GetHex());
uint256 hash_reference("0x2df5e1c65ef9f8cde240d23cae2ec036d31a15ec64bc68f64be242b1da6631f3");
BOOST_CHECK(hash == hash_reference);
}
BOOST_AUTO_TEST_SUITE_END()
+14 -2
View File
@@ -236,7 +236,7 @@ BOOST_AUTO_TEST_CASE(switchover)
BOOST_AUTO_TEST_CASE(AreInputsStandard)
{
std::map<uint256, std::pair<CTxIndex, CTransaction> > mapInputs;
MapPrevTx mapInputs;
CBasicKeyStore keystore;
CKey key[3];
vector<CKey> keys;
@@ -273,7 +273,19 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
oneOfEleven << OP_11 << OP_CHECKMULTISIG;
txFrom.vout[5].scriptPubKey.SetDestination(oneOfEleven.GetID());
mapInputs[txFrom.GetHash()] = make_pair(CTxIndex(), txFrom);
// Populate UTXO entries for each output of txFrom
uint256 hashFrom = txFrom.GetHash();
for (unsigned int i = 0; i < txFrom.vout.size(); i++)
{
CUtxoEntry entry;
entry.nValue = txFrom.vout[i].nValue;
entry.nHeight = 1;
entry.scriptPubKey = txFrom.vout[i].scriptPubKey;
entry.fCoinBase = false;
entry.fCoinStake = false;
entry.nTxTime = 0;
mapInputs[COutPoint(hashFrom, i)] = entry;
}
CTransaction txTo;
txTo.vout.resize(1);
+30 -3
View File
@@ -61,14 +61,40 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, MapPrevTx& inputsRet)
dummyTransactions[0].vout[0].scriptPubKey << key[0].GetPubKey() << OP_CHECKSIG;
dummyTransactions[0].vout[1].nValue = 50*CENT;
dummyTransactions[0].vout[1].scriptPubKey << key[1].GetPubKey() << OP_CHECKSIG;
inputsRet[dummyTransactions[0].GetHash()] = make_pair(CTxIndex(), dummyTransactions[0]);
{
uint256 hash0 = dummyTransactions[0].GetHash();
for (unsigned int i = 0; i < dummyTransactions[0].vout.size(); i++)
{
CUtxoEntry entry;
entry.nValue = dummyTransactions[0].vout[i].nValue;
entry.nHeight = 1;
entry.scriptPubKey = dummyTransactions[0].vout[i].scriptPubKey;
entry.fCoinBase = false;
entry.fCoinStake = false;
entry.nTxTime = 0;
inputsRet[COutPoint(hash0, i)] = entry;
}
}
dummyTransactions[1].vout.resize(2);
dummyTransactions[1].vout[0].nValue = 21*CENT;
dummyTransactions[1].vout[0].scriptPubKey.SetDestination(key[2].GetPubKey().GetID());
dummyTransactions[1].vout[1].nValue = 22*CENT;
dummyTransactions[1].vout[1].scriptPubKey.SetDestination(key[3].GetPubKey().GetID());
inputsRet[dummyTransactions[1].GetHash()] = make_pair(CTxIndex(), dummyTransactions[1]);
{
uint256 hash1 = dummyTransactions[1].GetHash();
for (unsigned int i = 0; i < dummyTransactions[1].vout.size(); i++)
{
CUtxoEntry entry;
entry.nValue = dummyTransactions[1].vout[i].nValue;
entry.nHeight = 1;
entry.scriptPubKey = dummyTransactions[1].vout[i].scriptPubKey;
entry.fCoinBase = false;
entry.fCoinStake = false;
entry.nTxTime = 0;
inputsRet[COutPoint(hash1, i)] = entry;
}
}
return dummyTransactions;
}
@@ -126,7 +152,8 @@ BOOST_AUTO_TEST_CASE(test_GetThrow)
t1.vout[0].nValue = 90*CENT;
t1.vout[0].scriptPubKey << OP_1;
BOOST_CHECK_THROW(t1.AreInputsStandard(missingInputs), runtime_error);
// AreInputsStandard returns false for missing inputs (no longer throws)
BOOST_CHECK(!t1.AreInputsStandard(missingInputs));
BOOST_CHECK_THROW(t1.GetValueIn(missingInputs), runtime_error);
}
+1
Submodule src/tor/tor-src added at 894a92ac22
+2 -7
View File
@@ -253,6 +253,7 @@ static const CRPCCommand vRPCCommands[] =
{ "getblockchaininfo", &getblockchaininfo, true, false },
{ "getwalletinfo", &getwalletinfo, true, false },
{ "getnetworkinfo", &getnetworkinfo, true, false },
{ "getseedlist", &getseedlist, true, false },
{ "gettxoutsetinfo", &gettxoutsetinfo, true, false },
{ "estimatefee", &estimatefee, true, false },
{ "getaddressbalance", &getaddressbalance, true, false },
@@ -296,12 +297,8 @@ static const CRPCCommand vRPCCommands[] =
{ "listaddressgroupings", &listaddressgroupings, false, false },
{ "signmessage", &signmessage, false, false },
{ "verifymessage", &verifymessage, false, false },
{ "getwork", &getwork, true, false },
{ "getworkex", &getworkex, true, false },
{ "listaccounts", &listaccounts, false, false },
{ "settxfee", &settxfee, false, false },
{ "getblocktemplate", &getblocktemplate, true, false },
{ "submitblock", &submitblock, false, false },
{ "listsinceblock", &listsinceblock, false, false },
{ "dumpprivkey", &dumpprivkey, false, false },
{ "dumpwallet", &dumpwallet, true, false },
@@ -994,8 +991,7 @@ void JSONRequest::parse(const Value& valRequest)
if (valMethod.type() != str_type)
throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string");
strMethod = valMethod.get_str();
if (strMethod != "getwork" && strMethod != "getblocktemplate")
printf("ThreadRPCServer method=%s\n", strMethod.c_str());
printf("ThreadRPCServer method=%s\n", strMethod.c_str());
// Parse params
Value valParams = find_value(request, "params");
@@ -1390,7 +1386,6 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector<std::stri
if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]);
if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]);
if (strMethod == "walletpassphrase" && n > 2) ConvertTo<bool>(params[2]);
if (strMethod == "getblocktemplate" && n > 0) ConvertTo<Object>(params[0]);
if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]);
if (strMethod == "sendalert" && n > 2) ConvertTo<boost::int64_t>(params[2]);
+1 -4
View File
@@ -149,6 +149,7 @@ extern std::vector<unsigned char> ParseHexO(const json_spirit::Object& o, std::s
extern json_spirit::Value getconnectioncount(const json_spirit::Array& params, bool fHelp); // in rpcnet.cpp
extern json_spirit::Value getpeerinfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getnetworkinfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getseedlist(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getwalletinfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp);
@@ -160,10 +161,6 @@ extern json_spirit::Value sendalert(const json_spirit::Array& params, bool fHelp
extern json_spirit::Value getsubsidy(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getmininginfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getstakinginfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getwork(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getworkex(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getblocktemplate(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value submitblock(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getnewaddress(const json_spirit::Array& params, bool fHelp); // in rpcwallet.cpp
extern json_spirit::Value getaccountaddress(const json_spirit::Array& params, bool fHelp);
+194 -84
View File
@@ -29,6 +29,8 @@ namespace fs = boost::filesystem;
leveldb::DB *txdb; // global pointer for LevelDB object instance
bool CDiskBlockIndex::fSerializeChainTrust = false;
static leveldb::Options GetOptions() {
leveldb::Options options;
int nCacheSizeMB = GetArg("-dbcache", 2048);
@@ -365,9 +367,22 @@ bool CTxDB::LoadBlockIndex()
// from BDB.
return true;
}
// Check DB format version to determine serialization features.
int nDbFormat = 1;
ReadDbFormat(nDbFormat);
CDiskBlockIndex::fSerializeChainTrust = (nDbFormat >= 2);
if (CDiskBlockIndex::fSerializeChainTrust)
printf("LoadBlockIndex(): DB format v%d — nChainTrust persisted\n", nDbFormat);
else
printf("LoadBlockIndex(): DB format v%d — will recalculate nChainTrust (one-time upgrade)\n", nDbFormat);
// The block index is an in-memory structure that maps hashes to on-disk
// locations where the contents of the block can be found. Here, we scan it
// out of the DB and into mapBlockIndex.
int64_t nPhaseStart = GetTimeMillis();
int64_t nTotalStart = nPhaseStart;
leveldb::Iterator *iterator = pdb->NewIterator(leveldb::ReadOptions());
// Seek to start key.
CDataStream ssStartKey(SER_DISK, CLIENT_VERSION);
@@ -418,6 +433,8 @@ bool CTxDB::LoadBlockIndex()
pindexNew->nTime = diskindex.nTime;
pindexNew->nBits = diskindex.nBits;
pindexNew->nNonce = diskindex.nNonce;
// nChainTrust is populated from disk if fSerializeChainTrust, else stays 0
pindexNew->nChainTrust = diskindex.nChainTrust;
// Watch for genesis block
if (pindexGenesisBlock == NULL && blockHash == (!fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet))
@@ -428,56 +445,138 @@ bool CTxDB::LoadBlockIndex()
return error("LoadBlockIndex() : CheckIndex failed at %d", pindexNew->nHeight);
}
// triangles: build setStakeSeen
if (pindexNew->IsProofOfStake())
setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime));
// setStakeSeen is populated below for recent blocks only (Change D)
iterator->Next();
}
delete iterator;
printf("STARTUP-PERF: block_index_deserialize %" PRId64 "ms blocks=%d\n", GetTimeMillis() - nPhaseStart, nBlocksLoaded);
if (fRequestShutdown)
return true;
// Calculate nChainTrust
vector<pair<int, CBlockIndex*> > vSortedByHeight;
vSortedByHeight.reserve(mapBlockIndex.size());
for (const auto& item : mapBlockIndex)
// ---- nChainTrust: recalculate if not persisted, or verify stake modifiers ----
nPhaseStart = GetTimeMillis();
bool fNeedChainTrustRecalc = !CDiskBlockIndex::fSerializeChainTrust;
if (fNeedChainTrustRecalc)
{
CBlockIndex* pindex = item.second;
vSortedByHeight.push_back(make_pair(pindex->nHeight, pindex));
uiInterface.InitMessage(_("Calculating chain trust (one-time upgrade)..."));
vector<pair<int, CBlockIndex*> > vSortedByHeight;
vSortedByHeight.reserve(mapBlockIndex.size());
for (const auto& item : mapBlockIndex)
vSortedByHeight.push_back(make_pair(item.second->nHeight, item.second));
sort(vSortedByHeight.begin(), vSortedByHeight.end());
int nLastCheckpointHeight = Checkpoints::GetTotalBlocksEstimate();
int nProgressInterval = std::max((int)vSortedByHeight.size() / 20, 1);
int nCount = 0;
for (const auto& item : vSortedByHeight)
{
CBlockIndex* pindex = item.second;
pindex->nChainTrust = (pindex->pprev ? pindex->pprev->nChainTrust : 0) + pindex->GetBlockTrust();
if (pindex->nHeight >= nLastCheckpointHeight)
{
pindex->nStakeModifierChecksum = GetStakeModifierChecksum(pindex);
if (!CheckStakeModifierCheckpoints(pindex->nHeight, pindex->nStakeModifierChecksum))
return error("CTxDB::LoadBlockIndex() : Failed stake modifier checkpoint height=%d, modifier=0x%016"PRIx64, pindex->nHeight, pindex->nStakeModifier);
}
if (++nCount % nProgressInterval == 0)
{
std::string strMsg = strprintf(_("Calculating chain trust... (%d%%)"), nCount * 100 / vSortedByHeight.size());
uiInterface.InitMessage(strMsg);
}
}
// Upgrade: rewrite all block index entries with nChainTrust and bump format.
printf("LoadBlockIndex(): upgrading DB to format v3 (persisting nChainTrust + UTXO model)...\n");
uiInterface.InitMessage(_("Upgrading block index..."));
CDiskBlockIndex::fSerializeChainTrust = true;
leveldb::WriteBatch batch;
nCount = 0;
for (const auto& item : vSortedByHeight)
{
CBlockIndex* pindex = item.second;
CDiskBlockIndex diskindex(pindex);
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey << make_pair(string("blockindex"), *pindex->phashBlock);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue << diskindex;
batch.Put(ssKey.str(), ssValue.str());
// Flush in chunks to limit memory usage
if (++nCount % 100000 == 0)
{
pdb->Write(leveldb::WriteOptions(), &batch);
batch.Clear();
printf("LoadBlockIndex(): upgraded %d / %d block index entries\n", nCount, (int)vSortedByHeight.size());
}
}
// Write remaining entries + format version
CDataStream ssFmtKey(SER_DISK, CLIENT_VERSION);
ssFmtKey << string("dbformat");
CDataStream ssFmtValue(SER_DISK, CLIENT_VERSION);
ssFmtValue << (int)3;
batch.Put(ssFmtKey.str(), ssFmtValue.str());
leveldb::Status status = pdb->Write(leveldb::WriteOptions(), &batch);
if (!status.ok())
return error("LoadBlockIndex(): failed to write upgraded block index: %s", status.ToString().c_str());
printf("LoadBlockIndex(): DB upgraded to format v3 (%d entries rewritten)\n", nCount);
}
sort(vSortedByHeight.begin(), vSortedByHeight.end());
int nLastCheckpointHeight = Checkpoints::GetTotalBlocksEstimate();
int nProgressInterval = std::max((int)vSortedByHeight.size() / 20, 1);
int nCount = 0;
for (const auto& item : vSortedByHeight)
else
{
CBlockIndex* pindex = item.second;
pindex->nChainTrust = (pindex->pprev ? pindex->pprev->nChainTrust : 0) + pindex->GetBlockTrust();
// Only compute the expensive SHA-256 stake modifier checksum for blocks
// at or beyond the last hardcoded checkpoint. Blocks well below the
// checkpoint have already been validated — recomputing 2M+ hashes on
// every startup was the main cause of multi-minute load times.
if (pindex->nHeight >= nLastCheckpointHeight)
// nChainTrust was loaded from disk. Only need stake modifier checksums
// for blocks above the last checkpoint (typically very few or zero).
int nLastCheckpointHeight = Checkpoints::GetTotalBlocksEstimate();
bool fNeedModifierCheck = false;
for (const auto& item : mapBlockIndex)
{
pindex->nStakeModifierChecksum = GetStakeModifierChecksum(pindex);
if (!CheckStakeModifierCheckpoints(pindex->nHeight, pindex->nStakeModifierChecksum))
return error("CTxDB::LoadBlockIndex() : Failed stake modifier checkpoint height=%d, modifier=0x%016"PRIx64, pindex->nHeight, pindex->nStakeModifier);
if (item.second->nHeight >= nLastCheckpointHeight)
{
fNeedModifierCheck = true;
break;
}
}
// Report progress for UI responsiveness
if (++nCount % nProgressInterval == 0)
if (fNeedModifierCheck)
{
std::string strMsg = strprintf(_("Loading block index... (%d%%)"), nCount * 100 / vSortedByHeight.size());
uiInterface.InitMessage(strMsg);
vector<pair<int, CBlockIndex*> > vAboveCheckpoint;
for (const auto& item : mapBlockIndex)
if (item.second->nHeight >= nLastCheckpointHeight)
vAboveCheckpoint.push_back(make_pair(item.second->nHeight, item.second));
sort(vAboveCheckpoint.begin(), vAboveCheckpoint.end());
for (const auto& item : vAboveCheckpoint)
{
CBlockIndex* pindex = item.second;
pindex->nStakeModifierChecksum = GetStakeModifierChecksum(pindex);
if (!CheckStakeModifierCheckpoints(pindex->nHeight, pindex->nStakeModifierChecksum))
return error("CTxDB::LoadBlockIndex() : Failed stake modifier checkpoint height=%d, modifier=0x%016"PRIx64, pindex->nHeight, pindex->nStakeModifier);
}
}
}
printf("STARTUP-PERF: chain_trust_and_modifiers %" PRId64 "ms\n", GetTimeMillis() - nPhaseStart);
// Bump dbformat to 3 if needed (databases that already had v2 nChainTrust upgrade).
// UTXO entries are written by ConnectBlock during normal sync. For databases upgrading
// from older versions, FetchInputs has a lazy fallback to the old CTxIndex path.
if (nDbFormat < 3)
{
WriteDbFormat(3);
printf("LoadBlockIndex(): bumped dbformat to v3 (UTXO model with lazy fallback)\n");
}
// Load hashBestChain pointer to end of best chain
nPhaseStart = GetTimeMillis();
if (!ReadHashBestChain(hashBestChain))
{
if (pindexGenesisBlock == NULL)
@@ -490,6 +589,26 @@ bool CTxDB::LoadBlockIndex()
nBestHeight = pindexBest->nHeight;
nBestChainTrust = pindexBest->nChainTrust;
printf("STARTUP-PERF: best_chain %" PRId64 "ms\n", GetTimeMillis() - nPhaseStart);
// ---- setStakeSeen: only populate for recent blocks (DoS protection) ----
nPhaseStart = GetTimeMillis();
{
int nStakeSeenDepth = 500;
CBlockIndex* pindex = pindexBest;
int nLoaded = 0;
while (pindex && nLoaded < nStakeSeenDepth)
{
if (pindex->IsProofOfStake())
setStakeSeen.insert(make_pair(pindex->prevoutStake, pindex->nStakeTime));
pindex = pindex->pprev;
nLoaded++;
}
printf("LoadBlockIndex(): populated setStakeSeen with %d entries (last %d blocks)\n",
(int)setStakeSeen.size(), nLoaded);
}
printf("STARTUP-PERF: stake_seen %" PRId64 "ms\n", GetTimeMillis() - nPhaseStart);
printf("LoadBlockIndex(): hashBestChain=%s height=%d trust=%s date=%s\n",
hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str(),
DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str());
@@ -512,6 +631,7 @@ bool CTxDB::LoadBlockIndex()
nBestInvalidTrust = bnBestInvalidTrust.getuint256();
// Verify blocks in the best chain
nPhaseStart = GetTimeMillis();
int nCheckLevel = GetArg("-checklevel", 1);
int nCheckDepth = GetArg( "-checkblocks", 50);
if (nCheckDepth == 0)
@@ -563,66 +683,20 @@ bool CTxDB::LoadBlockIndex()
pindexFork = pindex->pprev;
}
}
// check level 4: check whether spent txouts were spent within the main chain
unsigned int nOutput = 0;
if (nCheckLevel>3)
// check level 4: verify spent inputs were removed from UTXO set
if (nCheckLevel>3 && !tx.IsCoinBase())
{
for (const CDiskTxPos &txpos : txindex.vSpent)
for (const CTxIn &txin : tx.vin)
{
if (!txpos.IsNull())
if (HaveUtxo(txin.prevout.hash, txin.prevout.n))
{
pair<unsigned int, unsigned int> posFind = make_pair(txpos.nFile, txpos.nBlockPos);
if (!mapBlockPos.count(posFind))
{
printf("LoadBlockIndex(): *** found bad spend at %d, hashBlock=%s, hashTx=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str(), hashTx.ToString().c_str());
pindexFork = pindex->pprev;
}
// check level 6: check whether spent txouts were spent by a valid transaction that consume them
if (nCheckLevel>5)
{
CTransaction txSpend;
if (!txSpend.ReadFromDisk(txpos))
{
printf("LoadBlockIndex(): *** cannot read spending transaction of %s:%i from disk\n", hashTx.ToString().c_str(), nOutput);
pindexFork = pindex->pprev;
}
else if (!txSpend.CheckTransaction())
{
printf("LoadBlockIndex(): *** spending transaction of %s:%i is invalid\n", hashTx.ToString().c_str(), nOutput);
pindexFork = pindex->pprev;
}
else
{
bool fFound = false;
for (const CTxIn &txin : txSpend.vin)
if (txin.prevout.hash == hashTx && txin.prevout.n == nOutput)
fFound = true;
if (!fFound)
{
printf("LoadBlockIndex(): *** spending transaction of %s:%i does not spend it\n", hashTx.ToString().c_str(), nOutput);
pindexFork = pindex->pprev;
}
}
}
printf("LoadBlockIndex(): *** spent input still in UTXO set: %s:%i in %s\n",
txin.prevout.hash.ToString().c_str(), txin.prevout.n, hashTx.ToString().c_str());
pindexFork = pindex->pprev;
}
nOutput++;
}
}
}
// check level 5: check whether all prevouts are marked spent
if (nCheckLevel>4)
{
for (const CTxIn &txin : tx.vin)
{
CTxIndex txindex;
if (ReadTxIndex(txin.prevout.hash, txindex))
if (txindex.vSpent.size()-1 < txin.prevout.n || txindex.vSpent[txin.prevout.n].IsNull())
{
printf("LoadBlockIndex(): *** found unspent prevout %s:%i in %s\n", txin.prevout.hash.ToString().c_str(), txin.prevout.n, hashTx.ToString().c_str());
pindexFork = pindex->pprev;
}
}
}
}
}
}
@@ -636,6 +710,8 @@ bool CTxDB::LoadBlockIndex()
CTxDB txdb;
block.SetBestChain(txdb, pindexFork);
}
printf("STARTUP-PERF: verify_blocks %" PRId64 "ms depth=%d level=%d\n", GetTimeMillis() - nPhaseStart, nCheckDepth, nCheckLevel);
printf("STARTUP-PERF: load_block_index_total %" PRId64 "ms\n", GetTimeMillis() - nTotalStart);
return true;
}
@@ -750,3 +826,37 @@ bool CTxDB::GetAddressTxIds(int nType, const uint160& hashBytes, int nStartHeigh
return true;
}
// ---------- UTXO database methods ----------
bool CTxDB::ReadUtxo(const uint256& hash, unsigned int n, CUtxoEntry& entry)
{
entry.SetNull();
return Read(make_pair(string("u"), make_pair(hash, n)), entry);
}
bool CTxDB::WriteUtxo(const uint256& hash, unsigned int n, const CUtxoEntry& entry)
{
return Write(make_pair(string("u"), make_pair(hash, n)), entry);
}
bool CTxDB::EraseUtxo(const uint256& hash, unsigned int n)
{
return Erase(make_pair(string("u"), make_pair(hash, n)));
}
bool CTxDB::HaveUtxo(const uint256& hash, unsigned int n)
{
if (Exists(make_pair(string("u"), make_pair(hash, n))))
return true;
// Lazy fallback: check old CTxIndex vSpent for databases upgrading from pre-UTXO format
CTxIndex txindex;
if (ReadTxIndex(hash, txindex))
{
if (n < txindex.vSpent.size() && txindex.vSpent[n].IsNull())
return true; // vSpent[n] is null = output NOT spent = UTXO exists
}
return false;
}
+17
View File
@@ -183,6 +183,17 @@ public:
return Write(std::string("version"), nVersion);
}
bool ReadDbFormat(int& nDbFormat)
{
nDbFormat = 1;
return Read(std::string("dbformat"), nDbFormat);
}
bool WriteDbFormat(int nDbFormat)
{
return Write(std::string("dbformat"), nDbFormat);
}
bool ReadTxIndex(uint256 hash, CTxIndex& txindex);
bool UpdateTxIndex(uint256 hash, const CTxIndex& txindex);
bool AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight);
@@ -220,6 +231,12 @@ public:
bool GetAddressUtxos(int nType, const uint160& hashBytes, std::vector<std::pair<COutPoint, std::pair<int64_t, int> > >& vUtxos);
bool GetAddressTxIds(int nType, const uint160& hashBytes, int nStartHeight, int nEndHeight, std::vector<uint256>& vTxIds);
// UTXO database methods
bool ReadUtxo(const uint256& hash, unsigned int n, CUtxoEntry& entry);
bool WriteUtxo(const uint256& hash, unsigned int n, const CUtxoEntry& entry);
bool EraseUtxo(const uint256& hash, unsigned int n);
bool HaveUtxo(const uint256& hash, unsigned int n);
private:
bool LoadBlockIndexGuts();
};
+3 -3
View File
@@ -30,7 +30,7 @@ static const int DATABASE_VERSION = 70509;
// network protocol versioning
//
static const int PROTOCOL_VERSION = 70206;
static const int PROTOCOL_VERSION = 70205;
// v5 hard fork: require new protocol version (disconnects old nodes)
static const int MIN_PROTO_VERSION = 70205;
@@ -52,8 +52,8 @@ static const int BIP0031_VERSION = 60000;
static const int MEMPOOL_GD_VERSION = 60002;
#define DISPLAY_VERSION_MAJOR 5
#define DISPLAY_VERSION_MINOR 3
#define DISPLAY_VERSION_REVISION 8
#define DISPLAY_VERSION_MINOR 5
#define DISPLAY_VERSION_REVISION 5
#define DISPLAY_VERSION_BUILD 0
#endif
+121 -95
View File
@@ -487,7 +487,10 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
wtx.MarkSpent(txin.prevout.n);
wtx.WriteToDisk();
if (!IsInitialBlockDownload())
NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED);
{
try { NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); }
catch (...) { }
}
}
}
}
@@ -505,7 +508,10 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
wtx.MarkUnspent(&txout - &tx.vout[0]);
wtx.WriteToDisk();
if (!IsInitialBlockDownload())
NotifyTransactionChanged(this, hash, CT_UPDATED);
{
try { NotifyTransactionChanged(this, hash, CT_UPDATED); }
catch (...) { }
}
}
}
}
@@ -638,7 +644,13 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn)
// Notify UI of new or updated transaction (skip during IBD to avoid flooding the event loop)
if (!IsInitialBlockDownload())
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
{
try {
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
} catch (...) {
// Absorb boost::bad_weak_ptr or other signal exceptions from stale slots
}
}
// notify an external script when a wallet transaction comes in or is updated
std::string strCmd = GetArg("-walletnotify", "");
@@ -959,39 +971,47 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
int ret = 0;
CBlockIndex* pindex = pindexStart;
int nScanned = 0;
int nTotal = nBestHeight - (pindexStart ? pindexStart->nHeight : 0);
if (nTotal < 1) nTotal = 1;
int64_t nLastProgressTime = GetTimeMillis();
// Cache wallet birthday outside the loop (only written during key import)
int64_t nBirthTime = nTimeFirstKey;
while (pindex)
{
LOCK(cs_wallet);
int nScanned = 0;
int nTotal = nBestHeight - (pindexStart ? pindexStart->nHeight : 0);
if (nTotal < 1) nTotal = 1;
while (pindex)
if (fShutdown)
break;
++nScanned;
// Report progress every 500ms to keep UI responsive
int64_t nNow = GetTimeMillis();
if (nNow - nLastProgressTime > 500)
{
if (fShutdown)
break;
// Report progress every 10000 blocks to keep UI responsive
if (++nScanned % 10000 == 0)
{
int nPercent = (nScanned * 100) / nTotal;
uiInterface.InitMessage(strprintf(_("Rescanning... %d%%"), nPercent));
}
// no need to read and scan block, if block was created before
// our wallet birthday (as adjusted for block time variability)
if (nTimeFirstKey && (pindex->nTime < (nTimeFirstKey - 7200))) {
pindex = pindex->pnext;
continue;
}
CBlock block;
block.ReadFromDisk(pindex, true);
for (CTransaction& tx : block.vtx)
{
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
ret++;
}
pindex = pindex->pnext;
nLastProgressTime = nNow;
int nPercent = (nScanned * 100) / nTotal;
uiInterface.InitMessage(strprintf(_("Rescanning... %d%%"), nPercent));
}
// no need to read and scan block, if block was created before
// our wallet birthday (as adjusted for block time variability)
if (nBirthTime && (pindex->nTime < (nBirthTime - 7200))) {
pindex = pindex->pnext;
continue;
}
// Read block from disk WITHOUT holding wallet lock
CBlock block;
block.ReadFromDisk(pindex, true);
// AddToWalletIfInvolvingMe acquires cs_wallet internally
for (CTransaction& tx : block.vtx)
{
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
ret++;
}
pindex = pindex->pnext;
}
return ret;
}
@@ -1095,43 +1115,27 @@ bool CWallet::ScanForWalletTransactionsFromIndex(CBlockIndex* pindexStart, bool
wtx = (*mi).second;
}
CTxIndex txindex;
if (!txdb.ReadTxIndex(hashTx, txindex))
// Check UTXO existence to update spent status.
// Spending transactions are discovered through block scanning.
if (!txdb.ContainsTx(hashTx))
continue;
if (txindex.vSpent.size() != wtx.vout.size())
{
printf("ERROR: ScanForWalletTransactionsFromIndex() : txindex.vSpent.size() %"PRIszu" != wtx.vout.size() %"PRIszu" for %s\n",
txindex.vSpent.size(), wtx.vout.size(), hashTx.ToString().c_str());
continue;
}
for (unsigned int i = 0; i < txindex.vSpent.size(); i++)
for (unsigned int i = 0; i < wtx.vout.size(); i++)
{
if (txindex.vSpent[i].IsNull() || !IsMine(wtx.vout[i]))
if (!IsMine(wtx.vout[i]))
continue;
CTransaction txSpend;
CTxIndex txindexSpend;
int nSpendHeight = 0;
if (!ReadIndexedWalletTransaction(txdb, txindex.vSpent[i], txSpend, txindexSpend, nSpendHeight))
return false;
bool fSpendExists = false;
// If UTXO doesn't exist, the output was spent
if (!txdb.HaveUtxo(hashTx, i))
{
LOCK(cs_wallet);
fSpendExists = mapWallet.count(txSpend.GetHash()) > 0;
if (!wtx.IsSpent(i))
{
CWalletTx& wtxMutable = mapWallet[hashTx];
wtxMutable.MarkSpent(i);
wtxMutable.WriteToDisk();
nFound++;
}
}
if (fSpendExists && !fUpdate)
continue;
CWalletTx wtxSpend(this, txSpend);
wtxSpend.SetMerkleBranch();
if (!AddToWallet(wtxSpend))
return false;
nFound++;
if (setQueuedTxs.insert(txSpend.GetHash()).second)
vWorkQueue.push_back(txSpend.GetHash());
}
}
@@ -1173,25 +1177,20 @@ void CWallet::ReacceptWalletTransactions()
if ((wtx.IsCoinBase() && wtx.IsSpent(0)) || (wtx.IsCoinStake() && wtx.IsSpent(1)))
continue;
CTxIndex txindex;
uint256 hashTx = wtx.GetHash();
bool fUpdated = false;
if (txdb.ReadTxIndex(wtx.GetHash(), txindex))
// Check UTXO database for spent status of our outputs
if (txdb.ContainsTx(hashTx))
{
// Update fSpent if a tx got spent somewhere else by a copy of wallet.dat
if (txindex.vSpent.size() != wtx.vout.size())
{
printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %"PRIszu" != wtx.vout.size() %"PRIszu"\n", txindex.vSpent.size(), wtx.vout.size());
continue;
}
for (unsigned int i = 0; i < txindex.vSpent.size(); i++)
for (unsigned int i = 0; i < wtx.vout.size(); i++)
{
if (wtx.IsSpent(i))
continue;
if (!txindex.vSpent[i].IsNull() && IsMine(wtx.vout[i]))
// If the UTXO doesn't exist, the output was spent
if (!txdb.HaveUtxo(hashTx, i) && IsMine(wtx.vout[i]))
{
wtx.MarkSpent(i);
fUpdated = true;
vMissingTx.push_back(txindex.vSpent[i]);
}
}
if (fUpdated)
@@ -1868,18 +1867,37 @@ bool CWallet::GetStakeWeight(const CKeyStore& keystore, uint64_t& nMinWeight, ui
if (setCoins.empty())
return false;
// Collect coin hashes under lock, then read DB outside the per-coin lock
// to avoid acquiring/releasing LOCK2 thousands of times for large wallets
struct StakeCoin {
uint256 hash;
int64_t nValue;
unsigned int nTime;
};
vector<StakeCoin> vStakeCoins;
{
LOCK(cs_wallet);
vStakeCoins.reserve(setCoins.size());
for (auto& pcoin : setCoins)
{
StakeCoin sc;
sc.hash = pcoin.first->GetHash();
sc.nValue = pcoin.first->vout[pcoin.second].nValue;
sc.nTime = pcoin.first->nTime;
vStakeCoins.push_back(sc);
}
}
CTxDB txdb("r");
for (auto pcoin : setCoins)
int64_t nNow = GetTime();
for (auto& sc : vStakeCoins)
{
CTxIndex txindex;
{
LOCK2(cs_main, cs_wallet);
if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex))
continue;
}
if (!txdb.ReadTxIndex(sc.hash, txindex))
continue;
int64_t nTimeWeight = GetWeight((int64_t)pcoin.first->nTime, (int64_t)GetTime());
CBigNum bnCoinDayWeight = CBigNum(pcoin.first->vout[pcoin.second].nValue) * nTimeWeight / COIN / (24 * 60 * 60);
int64_t nTimeWeight = GetWeight((int64_t)sc.nTime, nNow);
CBigNum bnCoinDayWeight = CBigNum(sc.nValue) * nTimeWeight / COIN / (24 * 60 * 60);
// Weight is greater than zero
if (nTimeWeight > 0)
@@ -2152,7 +2170,8 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
coin.BindWallet(this);
coin.MarkSpent(txin.prevout.n);
coin.WriteToDisk();
NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
try { NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); }
catch (...) { }
}
if (fFileBacked)
@@ -2281,8 +2300,9 @@ bool CWallet::SetAddressBookName(const CTxDestination& address, const string& st
const CTrianglesAddress& caddress = address;
SecureMsgWalletKeyChanged(caddress.ToString(), strName, nMode);
}
NotifyAddressBookChanged(this, address, strName, fOwned, nMode);
try { NotifyAddressBookChanged(this, address, strName, fOwned, nMode); }
catch (...) { }
if (!fFileBacked)
return false;
return CWalletDB(strWalletFile).WriteName(CTrianglesAddress(address).ToString(), strName);
@@ -2295,7 +2315,7 @@ bool CWallet::DelAddressBookName(const CTxDestination& address)
mapAddressBook.erase(address);
}
bool fOwned = ::IsMine(*this, address);
string sName = "";
if (fOwned)
@@ -2303,7 +2323,8 @@ bool CWallet::DelAddressBookName(const CTxDestination& address)
const CTrianglesAddress& caddress = address;
SecureMsgWalletKeyChanged(caddress.ToString(), sName, CT_DELETED);
}
NotifyAddressBookChanged(this, address, "", fOwned, CT_DELETED);
try { NotifyAddressBookChanged(this, address, "", fOwned, CT_DELETED); }
catch (...) { }
if (!fFileBacked)
return false;
@@ -2662,16 +2683,17 @@ void CWallet::FixSpentCoins(int& nMismatchFound, int64_t& nBalanceInQuestion, bo
CTxDB txdb("r");
for (CWalletTx* pcoin : vCoins)
{
// Find the corresponding transaction index
CTxIndex txindex;
if (!txdb.ReadTxIndex(pcoin->GetHash(), txindex))
uint256 hashTx = pcoin->GetHash();
if (!txdb.ContainsTx(hashTx))
continue;
for (unsigned int n=0; n < pcoin->vout.size(); n++)
{
if (IsMine(pcoin->vout[n]) && pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull()))
bool fUtxoExists = txdb.HaveUtxo(hashTx, n);
// Wallet says spent but UTXO exists (meaning it's NOT spent) — lost coin
if (IsMine(pcoin->vout[n]) && pcoin->IsSpent(n) && fUtxoExists)
{
printf("FixSpentCoins found lost coin %s TRI %s[%d], %s\n",
FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
FormatMoney(pcoin->vout[n].nValue).c_str(), hashTx.ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
nMismatchFound++;
nBalanceInQuestion += pcoin->vout[n].nValue;
if (!fCheckOnly)
@@ -2680,10 +2702,11 @@ void CWallet::FixSpentCoins(int& nMismatchFound, int64_t& nBalanceInQuestion, bo
pcoin->WriteToDisk();
}
}
else if (IsMine(pcoin->vout[n]) && !pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull()))
// Wallet says unspent but UTXO doesn't exist (meaning it IS spent) — phantom coin
else if (IsMine(pcoin->vout[n]) && !pcoin->IsSpent(n) && !fUtxoExists)
{
printf("FixSpentCoins found spent coin %s TRI %s[%d], %s\n",
FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
FormatMoney(pcoin->vout[n].nValue).c_str(), hashTx.ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
nMismatchFound++;
nBalanceInQuestion += pcoin->vout[n].nValue;
if (!fCheckOnly)
@@ -2779,7 +2802,10 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx)
// Only notify UI if this transaction is in this wallet
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(hashTx);
if (mi != mapWallet.end() && !IsInitialBlockDownload())
NotifyTransactionChanged(this, hashTx, CT_UPDATED);
{
try { NotifyTransactionChanged(this, hashTx, CT_UPDATED); }
catch (...) { }
}
}
}
+61
View File
@@ -718,3 +718,64 @@ bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename)
{
return CWalletDB::Recover(dbenv, filename, false);
}
bool CWalletDB::ZapWalletTx(const std::string& strWalletFile)
{
// Open the wallet database directly and delete all "tx" entries,
// keeping keys and other metadata intact. This strips transaction
// history while preserving private keys. A rescan will rebuild
// the transaction list from the blockchain.
printf("ZapWalletTx: erasing transaction records from %s\n", strWalletFile.c_str());
CWalletDB walletdb(strWalletFile, "r+");
if (!walletdb.pdb)
{
printf("ZapWalletTx: failed to open wallet database\n");
return false;
}
Dbc* pcursor = walletdb.GetCursor();
if (!pcursor)
{
printf("ZapWalletTx: failed to get cursor\n");
return false;
}
// First pass: collect all tx hashes to erase
std::vector<uint256> vTxHash;
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
while (true)
{
int ret = walletdb.ReadAtCursor(pcursor, ssKey, ssValue, DB_NEXT);
if (ret == DB_NOTFOUND)
break;
if (ret != 0)
{
printf("ZapWalletTx: cursor read error %d\n", ret);
pcursor->close();
return false;
}
std::string strType;
ssKey >> strType;
if (strType == "tx")
{
uint256 hash;
ssKey >> hash;
vTxHash.push_back(hash);
}
}
pcursor->close();
// Second pass: erase all collected tx entries
int nErased = 0;
for (const uint256& hash : vTxHash)
{
if (walletdb.EraseTx(hash))
nErased++;
}
printf("ZapWalletTx: erased %d of %d transaction records\n", nErased, (int)vTxHash.size());
return true;
}
+1
View File
@@ -223,6 +223,7 @@ public:
DBErrors LoadWallet(CWallet* pwallet);
static bool Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys);
static bool Recover(CDBEnv& dbenv, std::string filename);
static bool ZapWalletTx(const std::string& strWalletFile);
};
#endif // TRIANGLES_WALLETDB_H
+1 -1
View File
@@ -1,7 +1,7 @@
TEMPLATE = app
TARGET = triangles-qt
VERSION = 5.3.9.0
VERSION = 5.6.0.0
INCLUDEPATH += src src/json src/qt src/qt/plugins/mrichtexteditor
DEFINES += QT_GUI BOOST_THREAD_USE_LIB BOOST_SPIRIT_THREADSAFE BOOST_THREAD_PROVIDES_GENERIC_SHARED_MUTEX_ON_WIN BOOST_BIND_GLOBAL_PLACEHOLDERS __NO_SYSTEM_INCLUDES
CONFIG += no_include_pwd