Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e8bf45af00 | |||
| 80a39fa1de | |||
| 14abcc9746 | |||
| 096a4f9927 | |||
| c4949a6d4f | |||
| 557d5807d8 | |||
| 014947580b | |||
| 64db028788 | |||
| e1ef89a169 | |||
| 6a9b710b18 | |||
| 22e888dd47 | |||
| d8fb2b7d7d | |||
| f5a5ebb204 | |||
| 42a33457bf | |||
| 279d643582 | |||
| baa38340a6 | |||
| fb4c0708bf | |||
| cfaf742053 | |||
| 308f8a5f5c | |||
| 069f42d6d0 | |||
| 6c87931901 | |||
| b31d8d08dd | |||
| e3705a66b8 | |||
| f0a2c0e237 | |||
| 7120df3989 | |||
| a031795aea | |||
| 3eb436bcd2 | |||
| 6ca0770d72 | |||
| 7e8ae1a25b | |||
| 552809e359 | |||
| 099f78efea | |||
| 207e1ed676 | |||
| 2fba88bfc5 | |||
| 4563e7952b | |||
| ea86ab077c | |||
| a1b137a7bb | |||
| 939606a5f7 | |||
| 73cecd90d1 | |||
| c74c92c542 | |||
| 97ae675f0a | |||
| b0b591364f | |||
| bf257858a4 | |||
| 16611efe72 | |||
| 1f3deacb7a | |||
| 8847571193 | |||
| a58eb3e9ef | |||
| dfb4b221dd | |||
| b6013edbe4 | |||
| d42c5aa799 | |||
| e8b2339339 | |||
| d242c2f37e | |||
| 5ad0bb53b4 | |||
| da41cc8718 | |||
| 07e6eccc44 | |||
| f52f83dc71 | |||
| 2e19ec350d | |||
| 18db764cf5 | |||
| ca156a3c59 | |||
| 35bf69ec94 | |||
| bd4a6b7cc3 | |||
| 870fc0896d | |||
| cce120bd81 | |||
| b12fda0e3f | |||
| fc79a744ab | |||
| 7597ad10c3 | |||
| 20151a2248 | |||
| 5701545f0d | |||
| 997435c4e0 | |||
| cd1b497f0d | |||
| 7adf92df7a | |||
| 4405d34f4b | |||
| 96b549ce95 | |||
| 49cd969009 | |||
| 787721616e | |||
| 369a57c67d | |||
| c57b14f6be | |||
| ce7b276a1f | |||
| 4f3e16c935 | |||
| 2833a70a36 | |||
| aa32672208 | |||
| a9bbcd070b | |||
| 7bfc34b76b | |||
| c3f49eb558 | |||
| 71f1f3011d | |||
| 22de8630cd | |||
| da5e5f9a8a | |||
| 998bd51425 | |||
| 7d0b2806e0 | |||
| 73c183d1c0 | |||
| 65b9417c28 | |||
| ed87543153 | |||
| 6e9dbb1aa9 | |||
| a6ec711cfa | |||
| 6877aeaddb | |||
| 60067e1a88 | |||
| e91ccd8786 | |||
| 96fb7d5040 | |||
| 47cf8abbda | |||
| 2abd494fec | |||
| 378b0370e3 | |||
| 61f22fcfd4 | |||
| bf1bf393c8 | |||
| 39e244a11c | |||
| 6724dfc832 | |||
| 9dadba6b09 | |||
| c432817d5f | |||
| 7ceb1f6a4d | |||
| 1a2793bb88 | |||
| 7ee2f00224 | |||
| 383a3b8b02 | |||
| 0beca5d801 | |||
| 47bd5bf083 | |||
| 7b5b80cb3a | |||
| b50eecc56f | |||
| 8953173403 | |||
| 2f307c195c | |||
| 8c5024a78a | |||
| 3f823e8583 | |||
| a4bfc6012a | |||
| 136d446157 | |||
| 1966f49ce2 |
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(C:/msys64/msys2_shell.cmd -mingw64 -defterm -no-start -here -c \"ls /mingw64/lib/libboost_system* 2>/dev/null\")"
|
||||
"Bash(C:/msys64/msys2_shell.cmd -mingw64 -defterm -no-start -here -c \"ls /mingw64/lib/libboost_system* 2>/dev/null\")",
|
||||
"Bash(git tag:*)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"C:\\msys64\\mingw64\\bin"
|
||||
|
||||
@@ -73,7 +73,99 @@
|
||||
"Bash(PATH=\"/c/msys64/mingw64/bin:$PATH\" /e/repos/triangles/scan_chain_tip.exe:*)",
|
||||
"Bash(PATH=\"/c/msys64/mingw64/bin:$PATH\" /c/msys64/mingw64/bin/qmake.exe:*)",
|
||||
"Bash(PATH=\"/c/msys64/mingw64/bin:$PATH\" qmake-qt5:*)",
|
||||
"Bash(PATH=\"/c/msys64/mingw64/bin:$PATH\" mingw32-make:*)"
|
||||
"Bash(PATH=\"/c/msys64/mingw64/bin:$PATH\" mingw32-make:*)",
|
||||
"Bash(export PATH=\"/c/msys64/mingw64/bin:$PATH\")",
|
||||
"Bash(\"C:/msys64/mingw64/bin/qmake-qt5.exe\" triangles-qt.pro -o Makefile)",
|
||||
"Bash(gh release create:*)",
|
||||
"Bash(gh repo view:*)",
|
||||
"Bash(gh repo create:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(git branch:*)",
|
||||
"Bash(git push:*)",
|
||||
"Bash(gh repo fork:*)",
|
||||
"Bash(gh api:*)",
|
||||
"Bash(gh auth:*)",
|
||||
"Bash(1 <<'EOF'\n{\"visibility\":\"public\"}\nEOF)",
|
||||
"Bash(findstr:*)",
|
||||
"Bash(gh workflow run:*)",
|
||||
"Bash(gh run watch:*)",
|
||||
"Bash(gh run view:*)",
|
||||
"Bash(gh release view:*)",
|
||||
"Bash(gh run download:*)",
|
||||
"Bash(gh release upload:*)",
|
||||
"Bash(gh release delete-asset:*)",
|
||||
"Bash(C:/msys64/mingw64/bin/mingw32-make.exe:*)",
|
||||
"Bash(C:/msys64/usr/bin/env.exe MSYSTEM=MINGW64 PATH=\"/mingw64/bin:/usr/bin:/bin\" /usr/bin/bash -lc \"cd /e/repos/triangles/src && mingw32-make -f makefile.mingw -j8 all 2>&1 | tail -60\")",
|
||||
"Bash(C:/msys64/usr/bin/env.exe MSYSTEM=MINGW64 /usr/bin/bash -lc \"ls /mingw64/lib/libboost_system*\")",
|
||||
"Bash(C:/msys64/usr/bin/env.exe MSYSTEM=MINGW64 PATH=\"/mingw64/bin:/usr/bin:/bin\" /usr/bin/bash -lc \"cd /e/repos/triangles/src && mingw32-make -f makefile.mingw trianglesd.exe 2>&1 | tail -20\")",
|
||||
"Bash(C:/msys64/usr/bin/env.exe MSYSTEM=MINGW64 PATH=\"/mingw64/bin:/usr/bin:/bin\" /usr/bin/bash -lc \"cd /e/repos/triangles/src && mingw32-make -f makefile.mingw trianglesd.exe 2>&1 | tail -10\")",
|
||||
"Bash(gh run list:*)",
|
||||
"Bash(git rm:*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -lc \"cd /e/repos/triangles/src && make -f makefile.mingw obj/rest.o 2>&1 | head -80\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -lc \"cd /e/repos/triangles/src && make -f makefile.mingw obj/trianglesrpc.o 2>&1 | tail -20\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -lc \"cd /e/repos/triangles/src && make -f makefile.mingw 2>&1 | tail -30\")",
|
||||
"Bash(node --version:*)",
|
||||
"Bash(npm --version:*)",
|
||||
"Bash(npm install:*)",
|
||||
"Bash(npx svelte-kit sync:*)",
|
||||
"Bash(npx vite build)",
|
||||
"Bash(nslookup:*)",
|
||||
"Bash(tailscale ping:*)",
|
||||
"Bash(del /f \"%APPDATA%\\\\triangles\\\\peers.dat\")",
|
||||
"Bash(C:msys64usrbinbash.exe -l -c \"cd ''e:/repos/triangles'' && qmake triangles-qt.pro ''USE_QRCODE=1'' ''USE_UPNP=-'' 2>&1 | tail -20\")",
|
||||
"Bash(C:msys64msys2_shell.cmd -mingw64 -defterm -no-start -c \"cd /e/repos/triangles && qmake triangles-qt.pro ''USE_QRCODE=1'' ''USE_UPNP=-'' 2>&1 | tail -20\")",
|
||||
"Bash(\"C:\\\\msys64\\\\mingw64\\\\bin\\\\bash.exe\" -c \"export PATH=/mingw64/bin:/usr/bin:$PATH && cd /e/repos/triangles && qmake triangles-qt.pro ''USE_QRCODE=1'' ''USE_UPNP=-'' 2>&1\")",
|
||||
"Bash(C:/msys64/mingw64/bin/qmake.exe:*)",
|
||||
"Bash(/c/msys64/mingw64/bin/qmake-qt5.exe:*)",
|
||||
"Bash(/c/msys64/usr/bin/env.exe MSYSTEM=MINGW64 /c/msys64/usr/bin/bash.exe -l -c \"cd /e/repos/triangles && qmake triangles-qt.pro ''USE_QRCODE=1'' ''USE_UPNP=-'' 2>&1 | tail -5\")",
|
||||
"Bash(export PATH=\"/c/msys64/mingw64/bin:/c/msys64/usr/bin:$PATH\")",
|
||||
"Bash(/c/msys64/usr/bin/env.exe MSYSTEM=MINGW64 /c/msys64/usr/bin/bash.exe:*)",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"cd /e/repos/triangles && make release 2>&1 | grep -E ''error|Error|undefined|cannot find'' | head -20\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"pacman -Qs qrencode\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"pacman -S --noconfirm mingw-w64-x86_64-qrencode\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"cd /e/repos/triangles && qmake-qt5 -o Makefile triangles-qt.pro USE_QRCODE=0 USE_UPNP=- 2>&1\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"ls /mingw64/lib/libqrencode*\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"pacman -S --noconfirm mingw-w64-x86_64-cmake\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"cd /tmp && pacman -Sp mingw-w64-x86_64-qrencode 2>/dev/null\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"cd /e/repos/triangles && mkdir -p dist && cp release/triangles-qt.exe dist/ && cd dist && strip triangles-qt.exe && ls -lh triangles-qt.exe\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"cd /e/repos/triangles/dist && ldd triangles-qt.exe | grep mingw64 | awk ''{print $3}''\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c 'cd /e/repos/triangles/dist && ldd triangles-qt.exe | grep mingw64 | awk \"\"{print \\\\$3}\"\"')",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"cd /e/repos/triangles/dist && ldd triangles-qt.exe | grep mingw64\")",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c 'cd /e/repos/triangles/dist && ldd triangles-qt.exe | grep mingw64 | sed \"\"s/.*=> //\"\" | sed \"\"s/ \\(.*//\"\"> dlls.txt && while read dll; do cp \"\"$dll\"\" .; done < dlls.txt && ls *.dll | wc -l && echo \"\"DLLs copied\"\"')",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c 'cd /e/repos/triangles/dist && mkdir -p platforms && cp /mingw64/share/qt5/plugins/platforms/qwindows.dll platforms/ && echo \"\"Qt platform plugin copied\"\"')",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c 'cd /e/repos/triangles && rm -f Triangles-v5.1.8-win-x64.zip && cd dist && 7z a ../Triangles-v5.1.8-win-x64.zip triangles-qt.exe *.dll platforms/ && echo \"\"ZIP created\"\"')",
|
||||
"Bash(C:/msys64/usr/bin/bash.exe -l -c \"cd /e/repos/triangles && make -j4 2>&1 | tail -15\")",
|
||||
"Bash(pacman:*)",
|
||||
"Bash(tar:*)",
|
||||
"WebFetch(domain:src-ref.docs.torproject.org)",
|
||||
"WebFetch(domain:gitlab.torproject.org)",
|
||||
"Bash(git status:*)",
|
||||
"Bash(git stash:*)",
|
||||
"Bash(git pull:*)",
|
||||
"Bash(git stash pop:*)",
|
||||
"Bash(find:*)",
|
||||
"Read(//e/repos/triangles/**)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(qmake:*)",
|
||||
"Bash(/c/msys64/mingw64/bin/mingw32-make.exe:*)",
|
||||
"Bash(C:/msys64/msys2_shell.cmd -mingw64 -defterm -no-start -here -c \"cd /e/repos/triangles && make -j1 2>&1 | tail -30\")",
|
||||
"Bash(C:/msys64/msys2_shell.cmd -defterm -no-start -mingw64 -c \"cd /e/repos/triangles && rm -f build/main.o && mingw32-make -f Makefile.Release build/main.o 2>&1 | grep -E ''^\\(src/|.*error\\)'' | head -10\")",
|
||||
"Bash(C:/msys64/msys2_shell.cmd -defterm -no-start -mingw64 -c \"cd /e/repos/triangles && ls -la build/main.o 2>&1\")",
|
||||
"Bash(git -C \"e:\\\\repos\\\\triangles\" log --oneline -20)",
|
||||
"Bash(git -C \"e:\\\\repos\\\\triangles\" describe --tags --abbrev=0)",
|
||||
"Bash(git -C \"e:\\\\repos\\\\triangles\" rev-parse --short HEAD)",
|
||||
"Bash(PATH=\"/c/msys64/mingw64/bin:$PATH\" which make:*)",
|
||||
"Bash(export PATH=\"/mingw64/bin:$PATH\")",
|
||||
"Bash(make:*)",
|
||||
"Bash(C:/msys64/usr/bin/env.exe PATH=\"C:/msys64/mingw64/bin:C:/msys64/usr/bin\" C:/msys64/usr/bin/make.exe:*)",
|
||||
"Bash(C:/msys64/msys2_shell.cmd -mingw64 -defterm -no-start -c \"cd /e/repos/triangles && make -f Makefile.Release -j8 2>&1 | tail -40\")",
|
||||
"Bash(C:/msys64/msys2_shell.cmd -mingw64 -defterm -no-start -here -c \"make 2>&1 | tail -10\")",
|
||||
"Bash(gh pr list:*)",
|
||||
"Bash(gh pr view:*)",
|
||||
"Bash(cmd //C \"powershell -NoProfile -Command \"\"Get-Process | Where-Object { $_.Path -like ''*triangles*'' } | Format-Table Id, ProcessName, Path\"\"\")",
|
||||
"Bash(cmd //C \"tasklist /FI \"\"IMAGENAME eq triangles-qt.exe\"\"\")",
|
||||
"Bash(MSYS_NO_PATHCONV=1 tasklist:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,32 @@ on:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
VERSION: "5.1.9"
|
||||
|
||||
jobs:
|
||||
test-linux-unit:
|
||||
runs-on: ubuntu-22.04
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libboost-all-dev \
|
||||
libssl-dev libdb++-dev libleveldb-dev libevent-dev libminiupnpc-dev
|
||||
|
||||
- name: Build LevelDB
|
||||
run: |
|
||||
cd src/leveldb
|
||||
chmod +x build_detect_platform
|
||||
make clean || true
|
||||
make OPT="-O2" libleveldb.a libmemenv.a
|
||||
|
||||
- name: Build and run unit tests
|
||||
run: |
|
||||
cd src
|
||||
make -f makefile.unix test -j$(nproc)
|
||||
./test_triangles --log_level=test_suite 2>&1 || true
|
||||
|
||||
build-windows-qt:
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
@@ -41,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
|
||||
|
||||
@@ -51,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
|
||||
@@ -111,19 +219,47 @@ 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
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -150,20 +286,96 @@ 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
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -181,25 +393,115 @@ jobs:
|
||||
run: |
|
||||
cd src
|
||||
mkdir -p obj
|
||||
make -f makefile.unix -j$(nproc)
|
||||
make -f makefile.unix trianglesd -j$(nproc)
|
||||
|
||||
- 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
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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: Install dependencies
|
||||
run: |
|
||||
brew install qt@5 openssl@3 boost berkeley-db@5 leveldb libevent miniupnpc
|
||||
@@ -241,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
|
||||
@@ -264,6 +603,9 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Set VERSION from tag
|
||||
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -272,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/
|
||||
|
||||
|
||||
@@ -5,12 +5,18 @@
|
||||
*.so
|
||||
*.dylib
|
||||
*.a
|
||||
/dist/
|
||||
build/
|
||||
release/
|
||||
debug/
|
||||
/Makefile
|
||||
Makefile.Debug
|
||||
Makefile.Release
|
||||
.qmake.stash
|
||||
object_script.triangles-qt.Debug
|
||||
object_script.triangles-qt.Release
|
||||
/*.zip
|
||||
/*.tar.gz
|
||||
|
||||
# Qt
|
||||
moc_*.cpp
|
||||
@@ -52,3 +58,6 @@ triangles.conf
|
||||
*.key
|
||||
*.cert
|
||||
*.gpg
|
||||
*.o
|
||||
src/trianglesd
|
||||
src/obj/
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "src/tor/tor-src"]
|
||||
path = src/tor/tor-src
|
||||
url = https://gitlab.torproject.org/tpo/core/tor.git
|
||||
@@ -0,0 +1,94 @@
|
||||
# Triangles Codebase Cleanup Notes
|
||||
|
||||
## Overview
|
||||
Systematic code quality improvements for the Triangles cryptocurrency codebase (v5.3.4+).
|
||||
|
||||
**Goal:** Improve maintainability without changing behavior or breaking consensus.
|
||||
|
||||
## Inventory
|
||||
|
||||
### TODOs/FIXMEs Found (38 total)
|
||||
|
||||
#### High Priority (Affects Safety/Correctness)
|
||||
- `rpcmining.cpp:263` - **Thread safety issue** in mapNewBlock (static variable, no mutex)
|
||||
- `walletmodel.cpp:249` - **Potential collision** in balance calculation
|
||||
- `smessage.cpp:863, 2219, 2373` - **File size limit** (files must be split if >2GB)
|
||||
|
||||
#### Medium Priority (Encapsulation/Security)
|
||||
- `protocol.h:50, 100, 132` - Public members should be private (3 locations)
|
||||
- `wallet.h:378` - nOrderPos calculation should move elsewhere
|
||||
- `wallet.cpp:733, 1732` - Change output handling needs improvement
|
||||
- `rpcwallet.cpp:1474, 1513, 1569` - SecureString operator= missing (forced .c_str())
|
||||
|
||||
#### Low Priority (Nice-to-Have)
|
||||
- `util.cpp:1322` - Disabled feature needs verification
|
||||
- `tor/tor_embedded.cpp:209` - Tor 0.4.9+ shutdown API upgrade
|
||||
- `init.cpp:442` - Remaining sanity checks (see Bitcoin issue #4081)
|
||||
- `rpcmining.cpp:232` - DRM comment (unclear what it means)
|
||||
- `smessage.cpp:*` - Various improvements (hash inclusion, thread safety, defaults)
|
||||
- `qt/*` - UI improvements (decrypt not supported, message filtering, OSX startup)
|
||||
|
||||
#### External/Third-Party (Don't Touch)
|
||||
- `leveldb/*` - LevelDB library TODOs (upstream issues)
|
||||
|
||||
## Code Quality Issues
|
||||
|
||||
### Using namespace std (37 files)
|
||||
All in .cpp files - **this is fine for .cpp**, problematic only in headers.
|
||||
No headers have this issue, so **no action needed**.
|
||||
|
||||
### Printf/Cout Usage (56 files)
|
||||
Most cryptocurrency code uses printf for early init/error handling before logging is available.
|
||||
**Review needed:** Check if these are legitimate early-init cases or should use LogPrintf.
|
||||
|
||||
## Cleanup Plan (Safest → Riskiest)
|
||||
|
||||
### Phase 1: Documentation & Comments ✅ SAFE
|
||||
1. Document all TODOs with context (why deferred, what's needed)
|
||||
2. Add function-level comments for complex logic
|
||||
3. Improve inline comments for clarity
|
||||
|
||||
### Phase 2: Low-Risk Code Quality 🟨 MEDIUM RISK
|
||||
4. Fix compiler warnings (-Wall -Wextra)
|
||||
5. Add const correctness where missing
|
||||
6. Remove commented-out dead code
|
||||
7. Standardize code formatting (if inconsistent)
|
||||
|
||||
### Phase 3: Functional Improvements 🟥 HIGH RISK (Skip for now)
|
||||
8. Fix thread safety issue in rpcmining.cpp (requires testing)
|
||||
9. Improve protocol.h encapsulation (may affect other code)
|
||||
10. Address >2GB file handling in smessage.cpp
|
||||
|
||||
## Decisions
|
||||
|
||||
### What NOT to Change
|
||||
- **Consensus code** - main.cpp (validation), kernel.cpp (PoS), miner.cpp (staking)
|
||||
- **Serialization** - Any READWRITE, serialize/deserialize code
|
||||
- **Protocol constants** - Network message types, version numbers
|
||||
- **Third-party code** - leveldb/, tor/, sph_types.h, xxhash/, lz4/
|
||||
|
||||
### What's Safe to Change
|
||||
- Comments and documentation
|
||||
- Variable names (in non-consensus code)
|
||||
- Code organization (splitting large functions)
|
||||
- Logging statements
|
||||
- UI code (qt/)
|
||||
- RPC interface (as long as API contract preserved)
|
||||
|
||||
## Initial Cleanup (2026-03-22)
|
||||
|
||||
### Actions Taken
|
||||
1. Created this documentation file
|
||||
2. Created cleanup/desloppify branch
|
||||
3. Inventoried all TODOs/FIXMEs
|
||||
|
||||
### Next Steps
|
||||
1. Add documentation comments to TODO items
|
||||
2. Review printf/cout usage patterns
|
||||
3. Check for compiler warnings
|
||||
4. Consider low-risk improvements
|
||||
|
||||
## Notes
|
||||
- This is a Bitcoin-derived codebase, so many patterns follow Bitcoin Core conventions
|
||||
- Recent v5.3.x work already modernized to C++17 and removed Boost - good foundation
|
||||
- Code is generally well-structured; main improvements are documentation and minor cleanup
|
||||
@@ -0,0 +1,76 @@
|
||||
# Triangles Cleanup Strategy - Safe Improvements
|
||||
|
||||
**Branch:** `cleanup/safe-improvements`
|
||||
**Goal:** Improve code quality without touching consensus-critical code
|
||||
|
||||
## ✅ SAFE TO FIX
|
||||
|
||||
### 1. Compiler Warnings (Non-Consensus)
|
||||
- **C++11 literal-suffix warnings** - Add spaces between literals and suffixes
|
||||
- **Unused variables/functions** - Remove dead code (verify not consensus-critical first)
|
||||
- **Deprecated-copy warnings** - Fix CScript assignment operator if safe
|
||||
|
||||
### 2. Code Style Improvements
|
||||
- Remove `using namespace std` from headers (keep in .cpp files)
|
||||
- Standardize logging patterns
|
||||
- Improve code comments (remove unclear/misleading ones)
|
||||
- Add context to TODOs/FIXMEs
|
||||
|
||||
### 3. Documentation
|
||||
- Add inline comments for thread safety concerns
|
||||
- Document collision vulnerabilities
|
||||
- Improve function/class documentation
|
||||
|
||||
## ❌ DO NOT TOUCH
|
||||
|
||||
### Consensus-Critical Code
|
||||
- **OpenSSL SHA256/RIPEMD160 usage** - Deprecated warnings OK, do not change
|
||||
- **BN_is_prime_ex** - Crypto library deprecation, leave as-is
|
||||
- **Hash algorithms** - Third-party libraries with warnings, consensus-critical
|
||||
- **Block validation logic** - Any code affecting block/transaction validation
|
||||
- **Merkle tree construction** - Core consensus
|
||||
- **Proof-of-Work/Proof-of-Stake** - Staking/mining algorithms
|
||||
|
||||
### How to Identify Consensus Code
|
||||
- Files in `src/` related to: `main.cpp`, `main.h`, block validation, transaction validation
|
||||
- Anything in hash algorithm libraries
|
||||
- Cryptographic primitives
|
||||
- Network protocol message formats (version, serialization)
|
||||
|
||||
## Incremental Testing Strategy
|
||||
|
||||
1. **One warning category at a time**
|
||||
2. **Compile after each change**
|
||||
3. **Test basic functionality:**
|
||||
- `trianglesd getinfo`
|
||||
- `trianglesd getblockchaininfo`
|
||||
- Verify block sync works
|
||||
4. **Commit incrementally** with clear messages
|
||||
|
||||
## Warning Categories (From Build Output)
|
||||
|
||||
```
|
||||
1. C++11 literal-suffix: ~20 instances (util.h, net.h, alert.cpp)
|
||||
2. OpenSSL deprecation: SHA256, RIPEMD160 (DO NOT FIX)
|
||||
3. BN_is_prime_ex: crypto library (DO NOT FIX)
|
||||
4. Deprecated-copy: CScript assignment (REVIEW CAREFULLY)
|
||||
5. Unused variables/functions: Various (SAFE IF NOT CONSENSUS)
|
||||
```
|
||||
|
||||
## Branch History
|
||||
|
||||
- Previous work: `cleanup/desloppify` (documentation improvements, merged to master)
|
||||
- This branch: Focus on safe compiler warnings and code quality
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Before pushing each commit:
|
||||
- [ ] Code compiles successfully
|
||||
- [ ] No new warnings introduced
|
||||
- [ ] trianglesd runs without errors
|
||||
- [ ] getinfo/getblockchaininfo work
|
||||
- [ ] No consensus-critical code touched
|
||||
|
||||
---
|
||||
|
||||
**Principle:** When in doubt, don't touch it. A clean codebase is worthless if the blockchain forks.
|
||||
@@ -0,0 +1,220 @@
|
||||
# Embedded Tor Integration Guide for Triangles
|
||||
|
||||
This guide explains how to compile Tor as a static library (`libtor.a`) and link
|
||||
it directly into the Triangles wallet binary so that every node automatically
|
||||
runs a Tor hidden service without needing an external Tor installation.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
trianglesd / triangles-qt
|
||||
├── tor_embedded.cpp ← calls tor_run_main() in a background thread
|
||||
├── tor_process.cpp ← fallback: launches external tor binary (already works)
|
||||
├── onion_v3.cpp ← V3 onion address generation / SOCKS5 proxy logic
|
||||
└── libtor.a ← aggregate static Tor library (built from official source)
|
||||
```
|
||||
|
||||
When compiled with `ENABLE_TOR_EMBEDDED`, the wallet calls `tor_run_main()` from
|
||||
`tor_api.h` on a dedicated thread. This gives the wallet a SOCKS5 proxy on
|
||||
`127.0.0.1:19099` and a V3 hidden service on port 24112 (the P2P port).
|
||||
|
||||
When compiled **without** the flag, `tor_embedded.cpp` falls back to the external
|
||||
`tor_process.cpp` which searches for and launches a system `tor` binary.
|
||||
|
||||
## Step 1: Add Tor as a Git Submodule
|
||||
|
||||
```bash
|
||||
cd /path/to/triangles
|
||||
git submodule add https://gitlab.torproject.org/tpo/core/tor.git src/tor/tor-src
|
||||
cd src/tor/tor-src
|
||||
git checkout release-0.4.9 # latest stable branch as of 2026
|
||||
```
|
||||
|
||||
This puts the full Tor source at `src/tor/tor-src/`.
|
||||
Current imported checkout in this repo: `release-0.4.9` at commit `1442ca4`.
|
||||
There is also a helper build script at `src/tor/build-libtor.sh`.
|
||||
|
||||
## Step 2: Build libtor.a
|
||||
|
||||
Tor uses autotools. Build it as a static library:
|
||||
|
||||
```bash
|
||||
cd src/tor/tor-src
|
||||
|
||||
# Install Tor build dependencies
|
||||
sudo apt install autoconf automake libtool pkg-config \
|
||||
libssl-dev libevent-dev zlib1g-dev
|
||||
|
||||
# Generate configure script
|
||||
./autogen.sh
|
||||
|
||||
# Configure for static library build (disable unneeded modules)
|
||||
./configure \
|
||||
--enable-static-tor \
|
||||
--disable-module-relay \
|
||||
--disable-module-dirauth \
|
||||
--disable-asciidoc \
|
||||
--disable-manpage \
|
||||
--disable-html-manual \
|
||||
--disable-unittests \
|
||||
--disable-tool-name-check \
|
||||
--with-openssl-dir=/usr \
|
||||
--with-libevent-dir=/usr \
|
||||
--with-zlib-dir=/usr \
|
||||
--prefix=/usr/local
|
||||
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
Or from the repo root:
|
||||
```bash
|
||||
./src/tor/build-libtor.sh
|
||||
```
|
||||
|
||||
After building, the static libraries are in `src/tor/tor-src/`:
|
||||
- `libtor.a`
|
||||
- `src/lib/libtor-*.a` (multiple component libs)
|
||||
|
||||
The header `src/feature/api/tor_api.h` provides the public C API:
|
||||
```c
|
||||
tor_main_configuration_t *tor_main_configuration_new(void);
|
||||
int tor_main_configuration_set_command_line(tor_main_configuration_t *cfg,
|
||||
int argc, char *argv[]);
|
||||
int tor_run_main(const tor_main_configuration_t *);
|
||||
void tor_main_configuration_free(tor_main_configuration_t *);
|
||||
```
|
||||
|
||||
## Step 3: Build Triangles with Embedded Tor
|
||||
|
||||
### Linux (makefile.unix)
|
||||
|
||||
```bash
|
||||
cd src
|
||||
|
||||
# Point to Tor's built libraries and headers
|
||||
make -f makefile.unix \
|
||||
USE_TOR_EMBEDDED=1
|
||||
```
|
||||
|
||||
You may need to adjust the `-l` flags in the makefile depending on the exact
|
||||
library names Tor produces. Check `src/tor/tor-src/` after building:
|
||||
|
||||
```bash
|
||||
find tor/tor-src -name '*.a' | sort
|
||||
```
|
||||
|
||||
On the imported `release-0.4.9` checkout in this repo, the simplest working
|
||||
link path is the aggregate `libtor.a` plus the normal dependency libraries.
|
||||
|
||||
### Windows (triangles-qt.pro)
|
||||
|
||||
Add to `triangles-qt.pro`:
|
||||
```qmake
|
||||
qmake "USE_TOR_EMBEDDED=1" \
|
||||
"TOR_SOURCE_ROOT=src/tor/tor-src"
|
||||
```
|
||||
|
||||
Both build systems now default to:
|
||||
- source root: `src/tor/tor-src`
|
||||
- include path: `src/tor/tor-src/src/feature/api`
|
||||
- library path: `src/tor/tor-src`
|
||||
- embedded Tor library: `-ltor`
|
||||
|
||||
On Windows, the imported Tor `0.4.9.5` build also needed:
|
||||
- `-llzma`
|
||||
- `-lzstd`
|
||||
- `-liphlpapi`
|
||||
- `-lshlwapi` (already linked by Triangles)
|
||||
|
||||
## Step 4: Wire into init.cpp
|
||||
|
||||
The global hooks `StartEmbeddedTor()` and `StopEmbeddedTor()` need to be called
|
||||
from `init.cpp`. Add these calls:
|
||||
|
||||
### In AppInit2() (after network init, before starting node):
|
||||
```cpp
|
||||
#include "tor/tor_embedded.h"
|
||||
|
||||
// Near the end of AppInit2, after network initialization:
|
||||
if (!StartEmbeddedTor()) {
|
||||
printf("WARNING: Embedded Tor failed to start. .onion connectivity unavailable.\n");
|
||||
// Non-fatal: wallet works without Tor, just no .onion
|
||||
}
|
||||
```
|
||||
|
||||
### In Shutdown():
|
||||
```cpp
|
||||
StopEmbeddedTor();
|
||||
```
|
||||
|
||||
## Step 5: Configure SOCKS Proxy for Outbound Connections
|
||||
|
||||
After Tor starts, the wallet needs to route `.onion` connections through the
|
||||
SOCKS5 proxy. In `net.cpp`, after Tor is initialized:
|
||||
|
||||
```cpp
|
||||
// If embedded Tor is running, use its SOCKS proxy for .onion addresses
|
||||
CTorEmbedded* tor = CTorEmbedded::GetInstance();
|
||||
if (tor->IsRunning()) {
|
||||
// Set proxy for .onion connections
|
||||
proxyType addrProxy(CService("127.0.0.1", tor->GetSocksPort()), 5);
|
||||
SetNameProxy(addrProxy);
|
||||
}
|
||||
```
|
||||
|
||||
## Runtime Flags
|
||||
|
||||
The embedded Tor respects these command-line flags:
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `-notor` | false | Disable Tor entirely |
|
||||
| `-torsocks=PORT` | 19099 | SOCKS5 proxy port |
|
||||
| `-torhsport=PORT` | 24112 | Hidden service virtual port |
|
||||
|
||||
## File Layout After Integration
|
||||
|
||||
```
|
||||
src/tor/
|
||||
├── tor-src/ ← git submodule (official Tor repo)
|
||||
│ └── src/
|
||||
│ ├── lib/libtor-*.a
|
||||
│ └── feature/api/tor_api.h
|
||||
│ └── libtor.a
|
||||
├── tor_embedded.h ← CTorEmbedded class header
|
||||
├── tor_embedded.cpp ← implementation (calls tor_run_main)
|
||||
├── tor_process.h ← external Tor process manager (fallback)
|
||||
├── tor_process.cpp
|
||||
├── onion_v3.h ← V3 onion address utilities
|
||||
├── onion_v3.cpp
|
||||
├── anonymize.h ← data dir helpers
|
||||
├── anonymize.cpp
|
||||
└── LICENSE
|
||||
```
|
||||
|
||||
## Reference: How VERGE (XVG) Does It
|
||||
|
||||
VERGE uses the same pattern. Their implementation is at:
|
||||
- `src/torcontroller.cpp` (~100 lines)
|
||||
- They use `tor_main()` (older API, pre-0.4.5)
|
||||
- Git submodule at `src/tor/` pointing to `release-0.4.8` branch
|
||||
- Build Tor as part of their `depends/` system
|
||||
|
||||
Key difference: modern Tor (0.4.5+) uses `tor_run_main()` with a configuration
|
||||
object instead of raw `tor_main(int argc, char** argv)`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Tor fails to bootstrap**: Check firewall rules. Tor needs outbound TCP to the
|
||||
Tor network (ports 80, 443, 9001, 9030).
|
||||
|
||||
**Link errors with libtor**: Prefer the aggregate `libtor.a` from the top level
|
||||
of the Tor build tree. On the imported Windows/MSYS2 build in this repo, the
|
||||
minimal verified link set was:
|
||||
```
|
||||
-ltor -levent -lssl -lcrypto -lz -llzma -lzstd -lws2_32 -liphlpapi -lshlwapi
|
||||
```
|
||||
|
||||
**OpenSSL version mismatch**: Both Tor and Triangles must link against the same
|
||||
OpenSSL version (3.x). If Tor was built against a different OpenSSL, rebuild it
|
||||
with the same `--with-openssl-dir`.
|
||||
@@ -0,0 +1,398 @@
|
||||
# Triangles Bootstrap Server Setup - DNS2
|
||||
|
||||
**For:** Krystie (@Krystie7777bot)
|
||||
**Server:** DNS2 (194.233.88.206) - Ubuntu
|
||||
**Date:** March 2026
|
||||
|
||||
---
|
||||
|
||||
## What This Server Does
|
||||
|
||||
Your server is the **bootstrap server** for the Triangles network. When someone opens a fresh Triangles wallet:
|
||||
|
||||
1. The wallet connects to `bootstrap.cryptographic-triangles.org` on **port 80**
|
||||
2. If that fails, it falls back to your IP directly: `194.233.88.206` on **port 80**
|
||||
3. It downloads `/filelist.txt` to see which blockchain files are available
|
||||
4. It downloads each file listed (mainly `blk0001.dat`, the entire blockchain)
|
||||
5. The user is now synced and ready to go
|
||||
|
||||
Your IP is hardcoded in the wallet. If your server is down, new users can't bootstrap.
|
||||
|
||||
Your server also runs the Triangles daemon so it doubles as a seed node on **port 24112**.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install nginx
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y nginx curl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Download the Daemon
|
||||
|
||||
No building required. Download the pre-built Linux binary from GitHub:
|
||||
|
||||
```bash
|
||||
cd /tmp
|
||||
curl -L -o trianglesd https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-daemon
|
||||
chmod +x trianglesd
|
||||
sudo mv trianglesd /usr/local/bin/
|
||||
```
|
||||
|
||||
Verify it works:
|
||||
|
||||
```bash
|
||||
trianglesd --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Configure the Daemon
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.triangles
|
||||
|
||||
RPC_PASS=$(openssl rand -hex 32)
|
||||
|
||||
cat > ~/.triangles/triangles.conf << EOF
|
||||
port=24112
|
||||
listen=1
|
||||
maxconnections=125
|
||||
rpcport=19112
|
||||
rpcuser=trianglesrpc
|
||||
rpcpassword=$RPC_PASS
|
||||
rpcallowip=127.0.0.1
|
||||
server=1
|
||||
externalip=194.233.88.206
|
||||
addnode=74.208.167.19
|
||||
txindex=1
|
||||
daemon=1
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Get the Blockchain Data
|
||||
|
||||
OpenClaw will send you `blk0001.dat` (or a tarball containing it). Put it in `~/.triangles/`:
|
||||
|
||||
```bash
|
||||
cd ~/.triangles
|
||||
# If you received a tarball:
|
||||
tar xzf /path/to/blockchain-data.tar.gz
|
||||
# Or if you received blk0001.dat directly:
|
||||
cp /path/to/blk0001.dat ~/.triangles/
|
||||
```
|
||||
|
||||
After this step you should have:
|
||||
|
||||
```
|
||||
~/.triangles/blk0001.dat
|
||||
~/.triangles/triangles.conf
|
||||
```
|
||||
|
||||
Do NOT copy someone else's `wallet.dat` unless you intend to use that wallet.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Open Firewall Ports
|
||||
|
||||
You need **two** ports open:
|
||||
|
||||
```bash
|
||||
sudo ufw allow 80/tcp comment "Bootstrap HTTP server"
|
||||
sudo ufw allow 24112/tcp comment "Triangles P2P"
|
||||
sudo ufw enable
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
Verify both show ALLOW:
|
||||
|
||||
```
|
||||
80/tcp ALLOW Anywhere # Bootstrap HTTP server
|
||||
24112/tcp ALLOW Anywhere # Triangles P2P
|
||||
```
|
||||
|
||||
Do NOT open 19112 (RPC).
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Test the Daemon
|
||||
|
||||
```bash
|
||||
trianglesd
|
||||
```
|
||||
|
||||
Wait 10 seconds, then:
|
||||
|
||||
```bash
|
||||
trianglesd getinfo
|
||||
```
|
||||
|
||||
Look for:
|
||||
- `"blocks"` around 2,186,940 or higher
|
||||
- `"connections"` should become 1+ within a couple minutes
|
||||
|
||||
If it works, stop it:
|
||||
|
||||
```bash
|
||||
trianglesd stop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Set Up the Daemon as a systemd Service
|
||||
|
||||
```bash
|
||||
sudo tee /etc/systemd/system/trianglesd.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Triangles Daemon
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
ExecStart=/usr/local/bin/trianglesd -daemon -datadir=/root/.triangles
|
||||
ExecStop=/usr/local/bin/trianglesd -datadir=/root/.triangles stop
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
TimeoutStopSec=120
|
||||
LimitNOFILE=65536
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable trianglesd
|
||||
sudo systemctl start trianglesd
|
||||
```
|
||||
|
||||
If you're running as a non-root user, change `/root/.triangles` to `/home/youruser/.triangles`.
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
sudo systemctl status trianglesd
|
||||
trianglesd getinfo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Set Up the Bootstrap File Server
|
||||
|
||||
This is the main event.
|
||||
|
||||
### 8a. Create the bootstrap directory and tarball
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/triangles-bootstrap
|
||||
|
||||
# Create the compressed tarball from the blockchain data
|
||||
# Only blk0001.dat is needed - the wallet builds its own block index after download
|
||||
cd ~/.triangles
|
||||
tar czf /tmp/bootstrap.tar.gz blk0001.dat
|
||||
sudo mv /tmp/bootstrap.tar.gz /var/www/triangles-bootstrap/
|
||||
|
||||
# Also create the legacy fallback files (for older wallet versions)
|
||||
sudo cp ~/.triangles/blk0001.dat /var/www/triangles-bootstrap/
|
||||
sudo tee /var/www/triangles-bootstrap/filelist.txt << 'EOF'
|
||||
blk0001.dat
|
||||
EOF
|
||||
|
||||
sudo chown -R www-data:www-data /var/www/triangles-bootstrap
|
||||
```
|
||||
|
||||
The wallet tries to download `bootstrap.tar.gz` first (compressed, faster). If that's missing, it falls back to downloading `blk0001.dat` directly using `filelist.txt`. After download, the wallet automatically imports the blocks and builds its own index.
|
||||
|
||||
### 8b. Configure nginx
|
||||
|
||||
```bash
|
||||
sudo rm -f /etc/nginx/sites-enabled/default
|
||||
|
||||
sudo tee /etc/nginx/sites-available/triangles-bootstrap << 'EOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name bootstrap.cryptographic-triangles.org 194.233.88.206;
|
||||
|
||||
root /var/www/triangles-bootstrap;
|
||||
|
||||
location / {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
send_timeout 600s;
|
||||
keepalive_timeout 600s;
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo ln -sf /etc/nginx/sites-available/triangles-bootstrap /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
```
|
||||
|
||||
That should print `syntax is ok` and `test is successful`. Then:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable nginx
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
### 8c. Verify it works
|
||||
|
||||
```bash
|
||||
# Should print "blk0001.dat"
|
||||
curl http://localhost/filelist.txt
|
||||
|
||||
# Should show HTTP 200 and a Content-Length
|
||||
curl -I http://localhost/blk0001.dat
|
||||
```
|
||||
|
||||
### 8d. Test from outside
|
||||
|
||||
Ask OpenClaw to test from another machine:
|
||||
|
||||
```bash
|
||||
curl -I http://194.233.88.206/bootstrap.tar.gz
|
||||
curl http://194.233.88.206/filelist.txt
|
||||
```
|
||||
|
||||
If both return HTTP 200, the bootstrap server is live.
|
||||
|
||||
---
|
||||
|
||||
## Step 9: Keeping Bootstrap Data Fresh
|
||||
|
||||
Periodically rebuild the tarball from the latest blockchain data:
|
||||
|
||||
```bash
|
||||
sudo systemctl stop trianglesd
|
||||
cd ~/.triangles
|
||||
tar czf /tmp/bootstrap.tar.gz blk0001.dat
|
||||
sudo mv /tmp/bootstrap.tar.gz /var/www/triangles-bootstrap/
|
||||
sudo cp ~/.triangles/blk0001.dat /var/www/triangles-bootstrap/
|
||||
sudo chown -R www-data:www-data /var/www/triangles-bootstrap
|
||||
sudo systemctl start trianglesd
|
||||
```
|
||||
|
||||
Or set up a weekly cron job:
|
||||
|
||||
```bash
|
||||
sudo tee /etc/cron.d/triangles-bootstrap-update << 'EOF'
|
||||
0 4 * * 0 root systemctl stop trianglesd && cd /root/.triangles && tar czf /tmp/bootstrap.tar.gz blk0001.dat && mv /tmp/bootstrap.tar.gz /var/www/triangles-bootstrap/ && cp /root/.triangles/blk0001.dat /var/www/triangles-bootstrap/ && chown -R www-data:www-data /var/www/triangles-bootstrap && systemctl start trianglesd
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 10: Tor Hidden Service (Optional)
|
||||
|
||||
```bash
|
||||
sudo apt install -y tor
|
||||
```
|
||||
|
||||
Add to `/etc/tor/torrc`:
|
||||
|
||||
```
|
||||
HiddenServiceDir /var/lib/tor/triangles/
|
||||
HiddenServiceVersion 3
|
||||
HiddenServicePort 24112 127.0.0.1:24112
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart tor
|
||||
sudo cat /var/lib/tor/triangles/hostname
|
||||
```
|
||||
|
||||
Send the `.onion` address to OpenClaw, add `externalip=YOUR_ONION_ADDRESS.onion` to `triangles.conf`, and restart the daemon.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Bootstrap server isn't working
|
||||
|
||||
```bash
|
||||
sudo systemctl status nginx
|
||||
sudo ss -tlnp | grep :80
|
||||
ls -lh /var/www/triangles-bootstrap/
|
||||
curl http://localhost/filelist.txt
|
||||
sudo tail -30 /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
### Daemon has 0 connections
|
||||
|
||||
```bash
|
||||
sudo ss -tlnp | grep 24112
|
||||
sudo ufw status
|
||||
trianglesd addnode 74.208.167.19 add
|
||||
```
|
||||
|
||||
### Daemon won't start
|
||||
|
||||
```bash
|
||||
tail -100 ~/.triangles/debug.log
|
||||
ps aux | grep trianglesd
|
||||
ls ~/.triangles/.lock
|
||||
```
|
||||
|
||||
### "Error loading block database"
|
||||
|
||||
```bash
|
||||
rm -rf ~/.triangles/txleveldb/
|
||||
sudo systemctl restart trianglesd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | Where / Value |
|
||||
|------|---------------|
|
||||
| **Bootstrap files** | `/var/www/triangles-bootstrap/` |
|
||||
| **bootstrap.tar.gz** | `/var/www/triangles-bootstrap/bootstrap.tar.gz` |
|
||||
| **filelist.txt** | `/var/www/triangles-bootstrap/filelist.txt` (legacy fallback) |
|
||||
| **blk0001.dat (web)** | `/var/www/triangles-bootstrap/blk0001.dat` (legacy fallback) |
|
||||
| **nginx config** | `/etc/nginx/sites-available/triangles-bootstrap` |
|
||||
| **nginx logs** | `/var/log/nginx/error.log` |
|
||||
| Daemon binary | `/usr/local/bin/trianglesd` |
|
||||
| Data directory | `~/.triangles/` |
|
||||
| Config file | `~/.triangles/triangles.conf` |
|
||||
| Debug log | `~/.triangles/debug.log` |
|
||||
| P2P port | **24112** (must be open) |
|
||||
| HTTP port | **80** (must be open) |
|
||||
| RPC port | 19112 (localhost only) |
|
||||
| Restart daemon | `sudo systemctl restart trianglesd` |
|
||||
| Restart nginx | `sudo systemctl restart nginx` |
|
||||
| Other seed node | 74.208.167.19 (DNS3-Sami) |
|
||||
| Contact | OpenClaw on Telegram |
|
||||
|
||||
---
|
||||
|
||||
## You're Done
|
||||
|
||||
Once you've completed all the steps, your server is:
|
||||
|
||||
1. **A seed node** — other wallets discover and connect to you on port 24112
|
||||
2. **A bootstrap server** — new wallets download the blockchain from you on port 80
|
||||
|
||||
Send OpenClaw your `.onion` address (if you set up Tor) so it can be added to the wallet's onion seed list.
|
||||
|
||||
To confirm everything is running:
|
||||
|
||||
```bash
|
||||
# Daemon healthy?
|
||||
trianglesd getinfo
|
||||
|
||||
# nginx serving files?
|
||||
curl -I http://localhost/bootstrap.tar.gz
|
||||
|
||||
# Ports open externally?
|
||||
sudo ss -tlnp | grep -E ':(80|24112)\b'
|
||||
```
|
||||
|
||||
If all three check out, you're live on the Triangles network.
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Generated by qmake (3.1) (Qt 5.15.18)
|
||||
# Project: triangles-qt.pro
|
||||
# Template: app
|
||||
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
|
||||
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro
|
||||
#############################################################################
|
||||
|
||||
MAKEFILE = Makefile
|
||||
@@ -156,7 +156,7 @@ Makefile: triangles-qt.pro C:/msys64/mingw64/share/qt5/mkspecs/win32-g++/qmake.c
|
||||
C:/msys64/mingw64/lib/qtmain.prl \
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf \
|
||||
src/qt/triangles.qrc
|
||||
$(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
|
||||
$(QMAKE) -o Makefile triangles-qt.pro
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/spec_pre.prf:
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/qdevice.pri:
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/device_config.prf:
|
||||
@@ -244,7 +244,7 @@ C:/msys64/mingw64/lib/qtmain.prl:
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf:
|
||||
src/qt/triangles.qrc:
|
||||
qmake: FORCE
|
||||
@$(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
|
||||
@$(QMAKE) -o Makefile triangles-qt.pro
|
||||
|
||||
qmake_all: FORCE
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ make -j$(nproc) -f makefile.unix USE_UPNP=0
|
||||
strip trianglesd
|
||||
```
|
||||
|
||||
Run the unit test suite:
|
||||
```bash
|
||||
make -C src -f makefile.unix test
|
||||
```
|
||||
|
||||
### Linux (AlmaLinux 9 / RHEL 9)
|
||||
|
||||
Install dependencies:
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# TODO/FIXME Documentation
|
||||
|
||||
Detailed context for each TODO/FIXME in the codebase.
|
||||
|
||||
## Critical (Needs Attention)
|
||||
|
||||
### src/rpcmining.cpp:263 - Thread Safety Issue
|
||||
```cpp
|
||||
static mapNewBlock_t mapNewBlock; // FIXME: thread safety
|
||||
```
|
||||
**Issue:** Static variable accessed by multiple RPC threads without mutex protection.
|
||||
**Impact:** Potential race condition in getwork RPC (used for mining).
|
||||
**Status:** Low priority - PoW mining ended at block 9000, this code path rarely used.
|
||||
**Fix:** Add std::mutex and lock_guard if getwork usage increases.
|
||||
|
||||
### src/qt/walletmodel.cpp:249 - Collision Risk
|
||||
```cpp
|
||||
if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future
|
||||
```
|
||||
**Issue:** Balance check may have edge case causing transaction collisions.
|
||||
**Context:** In createTransaction fee calculation loop.
|
||||
**Status:** Needs investigation - unclear what "collisions" means here.
|
||||
**Fix:** Review Bitcoin Core's current implementation of this logic.
|
||||
|
||||
### src/smessage.cpp - File Size Limits
|
||||
```cpp
|
||||
// Lines 863, 2219, 2373: "TODO files must be split if > 2GB"
|
||||
```
|
||||
**Issue:** Secure message storage files not split when exceeding 2GB.
|
||||
**Impact:** May fail on 32-bit systems or with large message volumes.
|
||||
**Status:** Low priority - unlikely to reach 2GB in practice.
|
||||
**Fix:** Implement file rotation when approaching 2GB limit.
|
||||
|
||||
## Medium Priority (Encapsulation/API)
|
||||
|
||||
### src/protocol.h - Make Members Private
|
||||
```cpp
|
||||
// Lines 50, 100, 132: "TODO: make private (improves encapsulation)"
|
||||
```
|
||||
**Issue:** CAddress, CInv, CMessageHeader have public data members.
|
||||
**Impact:** Poor encapsulation, harder to maintain invariants.
|
||||
**Status:** Deferred - would require extensive refactoring.
|
||||
**Fix:** Add getter/setter methods, make members private, update all call sites.
|
||||
|
||||
### src/wallet.h:378 - nOrderPos Calculation
|
||||
```cpp
|
||||
nOrderPos = -1; // TODO: calculate elsewhere
|
||||
```
|
||||
**Issue:** Transaction ordering position calculated in constructor.
|
||||
**Impact:** Minor - works but not ideal separation of concerns.
|
||||
**Status:** Deferred - no functional issue.
|
||||
**Fix:** Move calculation to WalletDB when transaction is added.
|
||||
|
||||
### src/rpcwallet.cpp / src/qt/askpassphrasedialog.cpp - SecureString Conversion
|
||||
**Issue:** Password-handling paths were converting through `.c_str()` because `SecureString`
|
||||
did not have a convenient conversion helper from `std::string`.
|
||||
**Impact:** Unnecessary C-string shims in sensitive code paths.
|
||||
**Status:** Resolved.
|
||||
**Fix:** Added `MakeSecureString(const std::string&)` in `src/allocators.h` and updated
|
||||
the wallet RPC and passphrase dialog call sites to use it directly.
|
||||
|
||||
## Low Priority (Nice-to-Have)
|
||||
|
||||
### src/util.cpp:1322 - Disabled Feature
|
||||
```cpp
|
||||
// TODO: This is currently disabled because it needs to be verified to work
|
||||
```
|
||||
**Context:** File descriptor management code.
|
||||
**Status:** Intentionally disabled pending verification.
|
||||
**Fix:** Test thoroughly, then enable if needed.
|
||||
|
||||
### src/tor/tor_embedded.cpp:209 - Tor Shutdown API
|
||||
```cpp
|
||||
// TODO: Tor 0.4.9+ may add tor_api_shutdown(), use it when available
|
||||
```
|
||||
**Context:** Embedded Tor cleanup.
|
||||
**Status:** Waiting for upstream Tor API.
|
||||
**Fix:** Check Tor 0.4.9+ releases for new API, integrate when stable.
|
||||
|
||||
### src/init.cpp:442 - Sanity Checks
|
||||
```cpp
|
||||
// TODO: remaining sanity checks, see #4081
|
||||
```
|
||||
**Context:** Bitcoin Core issue #4081 - additional startup sanity checks.
|
||||
**Status:** Deferred - core checks already in place.
|
||||
**Fix:** Review Bitcoin Core's current sanity check implementation.
|
||||
|
||||
### src/rpcmining.cpp:232 - DRM Comment
|
||||
```cpp
|
||||
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0]; // FIXME - DRM!
|
||||
```
|
||||
**Issue:** Unclear what "DRM" means here - likely "Data Race Maybe"?
|
||||
**Status:** Needs clarification from original author.
|
||||
**Fix:** Investigate if there's an actual issue, otherwise remove comment.
|
||||
|
||||
## Deferred (External/Low Impact)
|
||||
|
||||
### LevelDB TODOs (src/leveldb/*)
|
||||
**Status:** Upstream LevelDB issues - don't modify embedded library.
|
||||
**Action:** None - track upstream LevelDB project.
|
||||
|
||||
### Qt TODOs (src/qt/*)
|
||||
**Status:** UI improvements, not critical.
|
||||
**Action:** Track as nice-to-have enhancements.
|
||||
|
||||
### Secure Message TODOs (src/smessage.cpp)
|
||||
Multiple minor improvements suggested:
|
||||
- Include hash in certain operations
|
||||
- Improve thread shutdown
|
||||
- Set default recv/recvAnon behavior
|
||||
- Update outbox after PoW completes
|
||||
|
||||
**Status:** Non-critical enhancements.
|
||||
**Action:** Consider for future encrypted messaging upgrades.
|
||||
|
||||
## Summary
|
||||
|
||||
**Critical:** 3 items (thread safety, balance collision, file limits)
|
||||
**Medium:** 6 items (encapsulation, SecureString)
|
||||
**Low:** 5 items (disabled features, upstream APIs)
|
||||
**Deferred:** ~24 items (external libs, minor enhancements)
|
||||
|
||||
**Recommendation:** Focus on documenting critical items in code comments, defer fixes until specific issues arise.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -0,0 +1,59 @@
|
||||
# Embedded Tor Rebase Notes
|
||||
|
||||
This repository currently contains a legacy Tor source snapshot under
|
||||
`src/tor/`, but the wallet target does not build most of that tree.
|
||||
|
||||
## Current state
|
||||
|
||||
- The vendored Tor headers report `0.2.5.1-alpha-dev` in:
|
||||
- `src/tor/orconfig_linux.h`
|
||||
- `src/tor/orconfig_apple.h`
|
||||
- `src/tor/orconfig_win32.h`
|
||||
- The Qt wallet target currently builds only these Tor-related sources:
|
||||
- `src/tor_embed_hooks.cpp`
|
||||
- `src/tor/onion_v3.cpp`
|
||||
- `src/tor/tor_process.cpp`
|
||||
- This means the large legacy `src/tor/` tree is mostly dormant from the
|
||||
wallet build's perspective.
|
||||
|
||||
## Rebase target
|
||||
|
||||
- Target upstream Tor line: `0.4.9.x`
|
||||
- Imported source tree: `src/tor/tor-src`
|
||||
- Imported branch: `release-0.4.9`
|
||||
- Imported commit: `1442ca4`
|
||||
|
||||
## Why this matters
|
||||
|
||||
Attempting to "upgrade embedded Tor" by rebasing the entire old source tree in
|
||||
place is unnecessarily expensive if the wallet is only relying on:
|
||||
|
||||
- process management for a bundled Tor executable
|
||||
- Tor v3 onion address/key handling
|
||||
- a few local embedding hooks
|
||||
|
||||
The migration should preserve the embedded product experience while reducing
|
||||
coupling to legacy upstream Tor internals.
|
||||
|
||||
## Strategy
|
||||
|
||||
1. Keep the product-level embedding model.
|
||||
- The wallet can still ship with Tor and launch it automatically.
|
||||
2. Separate Triangles-owned glue from vendored Tor code.
|
||||
- `src/tor_embed_hooks.*` now holds local process/bootstrap helpers that
|
||||
previously lived under `src/tor/anonymize.*`.
|
||||
3. Treat `src/tor/onion_v3.cpp` and `src/tor/tor_process.cpp` as the active
|
||||
compatibility boundary.
|
||||
4. Re-vendor a newer upstream Tor snapshot only after deciding whether the
|
||||
product truly needs upstream Tor source in-tree or only a bundled Tor
|
||||
runtime plus the wallet's own v3/onion management code.
|
||||
|
||||
## Immediate next tasks
|
||||
|
||||
1. Audit whether any live build target still includes legacy `src/tor/*.c`
|
||||
sources beyond the current wallet target.
|
||||
2. Decide whether `onion_v3.cpp` should remain wallet-owned code or be reduced
|
||||
further in favor of runtime Tor control/provisioning.
|
||||
3. Add build metadata recording the intended upstream Tor version and source.
|
||||
4. If full upstream vendoring is still required, import a fresh `0.4.8.19`
|
||||
tree side-by-side instead of trying to patch the legacy `0.2.5.1` tree.
|
||||
@@ -3,7 +3,7 @@
|
||||
# Run on a Linux x64 system with appimagetool installed
|
||||
set -e
|
||||
|
||||
VERSION="5.1.5"
|
||||
VERSION="5.6.0"
|
||||
APPDIR="Triangles-x86_64.AppDir"
|
||||
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
|
||||
|
||||
@@ -17,7 +17,7 @@ mkdir -p "$APPDIR/usr/share/icons/hicolor/256x256/apps"
|
||||
|
||||
# Download binary
|
||||
echo "Downloading triangles-qt..."
|
||||
curl -L -o "$APPDIR/usr/bin/triangles-qt" "${RELEASE_URL}/triangles-qt-linux"
|
||||
curl -L -o "$APPDIR/usr/bin/triangles-qt" "${RELEASE_URL}/Cryptographic-Triangles-v${VERSION}-linux-x64-qt"
|
||||
chmod +x "$APPDIR/usr/bin/triangles-qt"
|
||||
|
||||
# Create desktop entry
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>org.cryptographic_triangles.TrianglesQt</id>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<name>Cryptographic Triangles</name>
|
||||
<summary>TRI cryptocurrency wallet with staking and encrypted messaging</summary>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
Cryptographic Triangles is a privacy-focused cryptocurrency wallet featuring
|
||||
Proof-of-Stake consensus, Tor v3 onion routing, and built-in encrypted messaging.
|
||||
</p>
|
||||
<p>Features:</p>
|
||||
<ul>
|
||||
<li>Proof-of-Stake with 33% annual staking rewards</li>
|
||||
<li>Hash9 algorithm (13-step hash cascade)</li>
|
||||
<li>Encrypted peer-to-peer messaging (SmsgMessage)</li>
|
||||
<li>Tor v3 integration for anonymous transactions</li>
|
||||
<li>Full node with built-in block explorer</li>
|
||||
</ul>
|
||||
</description>
|
||||
|
||||
<launchable type="desktop-id">org.cryptographic_triangles.TrianglesQt.desktop</launchable>
|
||||
|
||||
<icon type="stock">org.cryptographic_triangles.TrianglesQt</icon>
|
||||
|
||||
<categories>
|
||||
<category>Finance</category>
|
||||
<category>Network</category>
|
||||
<category>P2P</category>
|
||||
</categories>
|
||||
|
||||
<url type="homepage">https://cryptographic-triangles.org</url>
|
||||
<url type="bugtracker">https://github.com/SamiAhmed7777/triangles_v5/issues</url>
|
||||
<url type="vcs-browser">https://github.com/SamiAhmed7777/triangles_v5</url>
|
||||
|
||||
<provides>
|
||||
<binary>triangles-qt</binary>
|
||||
<binary>trianglesd</binary>
|
||||
</provides>
|
||||
|
||||
<releases>
|
||||
<release version="5.3.7" date="2026-03-24">
|
||||
<description>
|
||||
<p>Version 5.3.7 release.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="5.3.6" date="2026-03-23">
|
||||
<description>
|
||||
<p>IBD sync optimizations, Linux build fixes, and modern compiler support.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="5.2.0" date="2025-01-01">
|
||||
<description>
|
||||
<p>Tor v3 embedded support, OpenSSL 3.x compatibility, and Boost 1.90+ support.</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
|
||||
<content_rating type="oars-1.1" />
|
||||
|
||||
<supports>
|
||||
<control>pointing</control>
|
||||
<control>keyboard</control>
|
||||
</supports>
|
||||
</component>
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: Cryptographic Triangles Team
|
||||
pkgname=triangles-qt-bin
|
||||
pkgver=5.1.5
|
||||
pkgver=5.5.6
|
||||
pkgrel=1
|
||||
pkgdesc="Cryptographic Triangles (TRI) cryptocurrency wallet - Qt GUI"
|
||||
arch=('x86_64')
|
||||
@@ -11,13 +11,13 @@ optdepends=('tor: anonymous networking support')
|
||||
provides=('triangles-qt' 'trianglesd')
|
||||
conflicts=('triangles-qt' 'trianglesd')
|
||||
source=(
|
||||
"triangles-qt-${pkgver}::https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${pkgver}/triangles-qt-linux"
|
||||
"trianglesd-${pkgver}::https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${pkgver}/trianglesd-linux"
|
||||
"triangles-qt-${pkgver}::https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${pkgver}/Cryptographic-Triangles-v${pkgver}-linux-x64-qt"
|
||||
"trianglesd-${pkgver}::https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${pkgver}/Cryptographic-Triangles-v${pkgver}-linux-x64-daemon"
|
||||
"triangles-qt.desktop"
|
||||
)
|
||||
sha256sums=(
|
||||
'19eaadfdf18b899ce8434fe714e690e2db0546597e36037de37a29854fc23aeb'
|
||||
'6f5c19d34a2e1f6cdadee095d9e11b25d18b41a0d1602a163ffca7ec80b3da37'
|
||||
'ed220eb8d0b403f62cdac28988541fd1a27864491e233216f9c00a4c2537b4a3'
|
||||
'4d2ab25d61127d6aff3e6f3069556d04f4b823f8849e97629c12871ad4779517'
|
||||
'SKIP'
|
||||
)
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ $ErrorActionPreference = 'Stop'
|
||||
$packageArgs = @{
|
||||
packageName = 'triangles'
|
||||
unzipLocation = "$(Split-Path -Parent $MyInvocation.MyCommand.Definition)"
|
||||
url64bit = 'https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.1.5/Triangles-v5.1.5-win-x64.zip'
|
||||
checksum64 = '777e475f366164b342e917111bcf3155ec39e0ab4bd97b2ac295885ad30a93c6'
|
||||
url64bit = 'https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-5.3.7-win-x64.zip'
|
||||
checksum64 = '6f002a669a7e92aaf3d8dd7b1ae80f06a086c99a15ca05cf107665009ffc06b7'
|
||||
checksumType64 = 'sha256'
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>triangles</id>
|
||||
<version>5.1.5</version>
|
||||
<version>5.5.6</version>
|
||||
<title>Cryptographic Triangles</title>
|
||||
<authors>Cryptographic Triangles Team</authors>
|
||||
<owners>SamiAhmed7777</owners>
|
||||
@@ -25,6 +25,6 @@ featuring the unique Hash9 algorithm (13-step hash cascade).
|
||||
- Encrypted peer-to-peer messaging
|
||||
- Tor v3 integration for anonymous transactions
|
||||
</description>
|
||||
<releaseNotes>https://github.com/SamiAhmed7777/triangles_v5/releases/tag/v5.1.5</releaseNotes>
|
||||
<releaseNotes>https://github.com/SamiAhmed7777/triangles_v5/releases/tag/v5.3.7</releaseNotes>
|
||||
</metadata>
|
||||
</package>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: triangles
|
||||
Version: 5.1.5-1
|
||||
Version: 5.5.6-1
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
|
||||
@@ -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
|
||||
@@ -3,7 +3,7 @@
|
||||
# Run from the packaging/debian directory
|
||||
set -e
|
||||
|
||||
VERSION="5.1.5"
|
||||
VERSION="5.6.0"
|
||||
PKGDIR="triangles_${VERSION}-1_amd64"
|
||||
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
|
||||
|
||||
@@ -13,17 +13,24 @@ 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..."
|
||||
curl -L -o "$PKGDIR/usr/bin/triangles-qt" "${RELEASE_URL}/triangles-qt-linux"
|
||||
curl -L -o "$PKGDIR/usr/bin/trianglesd" "${RELEASE_URL}/trianglesd-linux"
|
||||
curl -L -o "$PKGDIR/usr/bin/triangles-qt" "${RELEASE_URL}/Cryptographic-Triangles-v${VERSION}-linux-x64-qt"
|
||||
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]
|
||||
|
||||
@@ -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 ""
|
||||
@@ -0,0 +1,40 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
LABEL maintainer="Cryptographic Triangles Team"
|
||||
LABEL description="Cryptographic Triangles (TRI) headless daemon"
|
||||
LABEL version="5.6.0"
|
||||
|
||||
ARG VERSION=5.6.0
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
libssl3 \
|
||||
libevent-2.1-7 \
|
||||
libboost-system1.74.0 \
|
||||
libboost-filesystem1.74.0 \
|
||||
libboost-program-options1.74.0 \
|
||||
libboost-thread1.74.0 \
|
||||
libboost-chrono1.74.0 \
|
||||
libdb5.3++ \
|
||||
libminiupnpc17 \
|
||||
tor \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl -L -o /usr/local/bin/trianglesd \
|
||||
"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
|
||||
|
||||
USER triangles
|
||||
WORKDIR /home/triangles
|
||||
|
||||
RUN mkdir -p /home/triangles/.triangles
|
||||
|
||||
VOLUME /home/triangles/.triangles
|
||||
|
||||
EXPOSE 24112 19112
|
||||
|
||||
ENTRYPOINT ["trianglesd"]
|
||||
CMD ["-daemon=0", "-printtoconsole"]
|
||||
@@ -0,0 +1,17 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
trianglesd:
|
||||
build: .
|
||||
image: cryptographic-triangles/trianglesd:5.6.0
|
||||
container_name: trianglesd
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "24112:24112"
|
||||
- "19112:19112"
|
||||
volumes:
|
||||
- triangles-data:/home/triangles/.triangles
|
||||
command: ["-daemon=0", "-printtoconsole", "-rpcallowip=172.16.0.0/12"]
|
||||
|
||||
volumes:
|
||||
triangles-data:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"only-arches": ["x86_64"]
|
||||
}
|
||||
@@ -19,13 +19,35 @@ modules:
|
||||
build-commands:
|
||||
- install -Dm755 triangles-qt-linux /app/bin/triangles-qt
|
||||
- install -Dm644 triangles-qt.desktop /app/share/applications/org.cryptographic_triangles.TrianglesQt.desktop
|
||||
- install -Dm644 triangles.svg /app/share/icons/hicolor/scalable/apps/org.cryptographic_triangles.TrianglesQt.svg
|
||||
- install -Dm644 triangles-128.png /app/share/icons/hicolor/128x128/apps/org.cryptographic_triangles.TrianglesQt.png
|
||||
- install -Dm644 triangles-256.png /app/share/icons/hicolor/256x256/apps/org.cryptographic_triangles.TrianglesQt.png
|
||||
- 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.1.5/triangles-qt-linux
|
||||
sha256: 19eaadfdf18b899ce8434fe714e690e2db0546597e36037de37a29854fc23aeb
|
||||
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
|
||||
path: triangles-qt.desktop
|
||||
url: https://raw.githubusercontent.com/SamiAhmed7777/triangles_v5/master/packaging/flatpak/triangles-qt.desktop
|
||||
sha256: f56c4be5870fed6d3f0fb74398241ea909bd3b6f3305fe06ef5f58fba25602ca
|
||||
dest-filename: triangles-qt.desktop
|
||||
- type: file
|
||||
url: https://raw.githubusercontent.com/SamiAhmed7777/triangles_v5/master/src/qt/res/src/triangles.svg
|
||||
sha256: c08d0731e209b1941606709d7236526c4334ee52d1cbda2173cad417d6169486
|
||||
dest-filename: triangles.svg
|
||||
- type: file
|
||||
url: https://raw.githubusercontent.com/SamiAhmed7777/triangles_v5/master/src/qt/res/icons/triangles-128.png
|
||||
sha256: 3a9030b2141ba822059e1d32c29f004c5ed9a4d3c8fc1fba6188201cfdf4ccf5
|
||||
dest-filename: triangles-128.png
|
||||
- type: file
|
||||
url: https://raw.githubusercontent.com/SamiAhmed7777/triangles_v5/master/src/qt/res/icons/triangles.png
|
||||
sha256: eebe5b1890c4cf43b8ae3160f81bac93a2a10cd221c99815de9fcd850f225f4e
|
||||
dest-filename: triangles-256.png
|
||||
- type: file
|
||||
url: https://raw.githubusercontent.com/SamiAhmed7777/triangles_v5/master/packaging/appstream/org.cryptographic_triangles.TrianglesQt.metainfo.xml
|
||||
sha256: dd5ecf9f4916cf0ef3d7ceec763dbbbcf7c4bf806be1e404a96dcfc9423c9fad
|
||||
dest-filename: org.cryptographic_triangles.TrianglesQt.metainfo.xml
|
||||
|
||||
- name: trianglesd
|
||||
buildsystem: simple
|
||||
@@ -33,6 +55,6 @@ modules:
|
||||
- install -Dm755 trianglesd-linux /app/bin/trianglesd
|
||||
sources:
|
||||
- type: file
|
||||
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.1.5/trianglesd-linux
|
||||
sha256: 6f5c19d34a2e1f6cdadee095d9e11b25d18b41a0d1602a163ffca7ec80b3da37
|
||||
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
|
||||
|
||||
@@ -2,21 +2,16 @@ class Triangles < Formula
|
||||
desc "Cryptographic Triangles (TRI) cryptocurrency wallet and daemon"
|
||||
homepage "https://cryptographic-triangles.org"
|
||||
license "MIT"
|
||||
version "5.1.5"
|
||||
version "5.5.6"
|
||||
|
||||
on_macos do
|
||||
if Hardware::CPU.intel?
|
||||
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.1.5/Cryptographic-Triangles-v5.1.5-macos-x64.dmg"
|
||||
sha256 "PLACEHOLDER_X64_HASH"
|
||||
else
|
||||
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.1.5/Cryptographic-Triangles-v5.1.5-macos-arm64.dmg"
|
||||
sha256 "PLACEHOLDER_ARM64_HASH"
|
||||
end
|
||||
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-macos-arm64.dmg"
|
||||
sha256 "3a58e795d898656b455fd639c0ea826a4457d390a64d00ace9a1257598d053be"
|
||||
end
|
||||
|
||||
on_linux do
|
||||
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.1.5/trianglesd-linux"
|
||||
sha256 "6f5c19d34a2e1f6cdadee095d9e11b25d18b41a0d1602a163ffca7ec80b3da37"
|
||||
url "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-daemon"
|
||||
sha256 "4d2ab25d61127d6aff3e6f3069556d04f4b823f8849e97629c12871ad4779517"
|
||||
end
|
||||
|
||||
depends_on "openssl@3"
|
||||
@@ -26,7 +21,7 @@ class Triangles < Formula
|
||||
prefix.install "Triangles-Qt.app"
|
||||
bin.write_exec_script prefix/"Triangles-Qt.app/Contents/MacOS/Triangles-Qt"
|
||||
else
|
||||
bin.install "trianglesd-linux" => "trianglesd"
|
||||
bin.install "Cryptographic-Triangles-v5.3.7-linux-x64-daemon" => "trianglesd"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}:
|
||||
|
||||
let
|
||||
version = "5.1.5";
|
||||
version = "5.5.6";
|
||||
|
||||
desktopItem = makeDesktopItem {
|
||||
name = "triangles-qt";
|
||||
@@ -34,13 +34,13 @@ stdenv.mkDerivation {
|
||||
|
||||
srcs = [
|
||||
(fetchurl {
|
||||
url = "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${version}/triangles-qt-linux";
|
||||
sha256 = "19eaadfdf18b899ce8434fe714e690e2db0546597e36037de37a29854fc23aeb";
|
||||
url = "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${version}/Cryptographic-Triangles-v${version}-linux-x64-qt";
|
||||
sha256 = "ed220eb8d0b403f62cdac28988541fd1a27864491e233216f9c00a4c2537b4a3";
|
||||
name = "triangles-qt-linux";
|
||||
})
|
||||
(fetchurl {
|
||||
url = "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${version}/trianglesd-linux";
|
||||
sha256 = "6f5c19d34a2e1f6cdadee095d9e11b25d18b41a0d1602a163ffca7ec80b3da37";
|
||||
url = "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${version}/Cryptographic-Triangles-v${version}-linux-x64-daemon";
|
||||
sha256 = "4d2ab25d61127d6aff3e6f3069556d04f4b823f8849e97629c12871ad4779517";
|
||||
name = "trianglesd-linux";
|
||||
})
|
||||
];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# Install build tools: sudo dnf install rpm-build rpmdevtools
|
||||
set -e
|
||||
|
||||
VERSION="5.1.5"
|
||||
VERSION="5.6.0"
|
||||
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
|
||||
|
||||
echo "Building RPM for Triangles v${VERSION}..."
|
||||
@@ -14,8 +14,8 @@ rpmdev-setuptree
|
||||
|
||||
# Download sources into SOURCES
|
||||
echo "Downloading binaries..."
|
||||
curl -L -o ~/rpmbuild/SOURCES/triangles-qt-linux "${RELEASE_URL}/triangles-qt-linux"
|
||||
curl -L -o ~/rpmbuild/SOURCES/trianglesd-linux "${RELEASE_URL}/trianglesd-linux"
|
||||
curl -L -o ~/rpmbuild/SOURCES/Cryptographic-Triangles-v${VERSION}-linux-x64-qt "${RELEASE_URL}/Cryptographic-Triangles-v${VERSION}-linux-x64-qt"
|
||||
curl -L -o ~/rpmbuild/SOURCES/Cryptographic-Triangles-v${VERSION}-linux-x64-daemon "${RELEASE_URL}/Cryptographic-Triangles-v${VERSION}-linux-x64-daemon"
|
||||
cp triangles-qt.desktop ~/rpmbuild/SOURCES/
|
||||
|
||||
# Copy spec file
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: triangles
|
||||
Version: 5.1.5
|
||||
Version: 5.6.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Cryptographic Triangles (TRI) cryptocurrency wallet
|
||||
License: MIT
|
||||
URL: https://cryptographic-triangles.org
|
||||
Source0: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v%{version}/triangles-qt-linux
|
||||
Source1: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v%{version}/trianglesd-linux
|
||||
Source0: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v%{version}/Cryptographic-Triangles-v%{version}-linux-x64-qt
|
||||
Source1: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v%{version}/Cryptographic-Triangles-v%{version}-linux-x64-daemon
|
||||
Source2: triangles-qt.desktop
|
||||
|
||||
BuildArch: x86_64
|
||||
|
||||
@@ -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.1.5
|
||||
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.1.5/Triangles-v5.1.5-win-x64.zip
|
||||
InstallerSha256: 777e475f366164b342e917111bcf3155ec39e0ab4bd97b2ac295885ad30a93c6
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -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)"
|
||||
@@ -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")"
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 151 KiB |
@@ -1,6 +1,6 @@
|
||||
name: triangles
|
||||
base: core22
|
||||
version: '5.1.5'
|
||||
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.1.5/triangles-qt-linux
|
||||
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-qt
|
||||
source-type: file
|
||||
organize:
|
||||
triangles-qt-linux: bin/triangles-qt
|
||||
Cryptographic-Triangles-v5.6.0-linux-x64-qt: bin/triangles-qt
|
||||
stage-packages:
|
||||
- libqt5widgets5
|
||||
- libqt5gui5
|
||||
@@ -73,13 +73,19 @@ parts:
|
||||
|
||||
trianglesd:
|
||||
plugin: dump
|
||||
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.1.5/trianglesd-linux
|
||||
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-daemon
|
||||
source-type: file
|
||||
organize:
|
||||
trianglesd-linux: bin/trianglesd
|
||||
Cryptographic-Triangles-v5.6.0-linux-x64-daemon: bin/trianglesd
|
||||
|
||||
desktop-entry:
|
||||
plugin: dump
|
||||
source: snap/gui
|
||||
organize:
|
||||
triangles-qt.desktop: share/applications/triangles-qt.desktop
|
||||
|
||||
appstream:
|
||||
plugin: dump
|
||||
source: packaging/appstream
|
||||
organize:
|
||||
org.cryptographic_triangles.TrianglesQt.metainfo.xml: share/metainfo/org.cryptographic_triangles.TrianglesQt.metainfo.xml
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <algorithm>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <map>
|
||||
|
||||
#include "alert.h"
|
||||
@@ -46,16 +45,16 @@ void CUnsignedAlert::SetNull()
|
||||
std::string CUnsignedAlert::ToString() const
|
||||
{
|
||||
std::string strSetCancel;
|
||||
BOOST_FOREACH(int n, setCancel)
|
||||
for (int n : setCancel)
|
||||
strSetCancel += strprintf("%d ", n);
|
||||
std::string strSetSubVer;
|
||||
BOOST_FOREACH(std::string str, setSubVer)
|
||||
for (std::string str : setSubVer)
|
||||
strSetSubVer += "\"" + str + "\" ";
|
||||
return strprintf(
|
||||
"CAlert(\n"
|
||||
" nVersion = %d\n"
|
||||
" nRelayUntil = %"PRId64"\n"
|
||||
" nExpiration = %"PRId64"\n"
|
||||
" nRelayUntil = %" PRId64 "\n"
|
||||
" nExpiration = %" PRId64 "\n"
|
||||
" nID = %d\n"
|
||||
" nCancel = %d\n"
|
||||
" setCancel = %s\n"
|
||||
@@ -228,7 +227,7 @@ bool CAlert::ProcessAlert(bool fThread)
|
||||
}
|
||||
|
||||
// Check if this alert has been cancelled
|
||||
BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts)
|
||||
for (auto& item : mapAlerts)
|
||||
{
|
||||
const CAlert& alert = item.second;
|
||||
if (alert.Cancels(*this))
|
||||
|
||||
@@ -254,4 +254,9 @@ struct zero_after_free_allocator : public std::allocator<T>
|
||||
// This is exactly like std::string, but with a custom allocator.
|
||||
typedef std::basic_string<char, std::char_traits<char>, secure_allocator<char> > SecureString;
|
||||
|
||||
static inline SecureString MakeSecureString(const std::string& value)
|
||||
{
|
||||
return SecureString(value.begin(), value.end());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -260,7 +260,7 @@ public:
|
||||
* The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script.
|
||||
*/
|
||||
class CTrianglesAddress;
|
||||
class CTrianglesAddressVisitor : public boost::static_visitor<bool>
|
||||
class CTrianglesAddressVisitor
|
||||
{
|
||||
private:
|
||||
CTrianglesAddress *addr;
|
||||
@@ -294,7 +294,7 @@ public:
|
||||
|
||||
bool Set(const CTxDestination &dest)
|
||||
{
|
||||
return boost::apply_visitor(CTrianglesAddressVisitor(this), dest);
|
||||
return std::visit(CTrianglesAddressVisitor(this), dest);
|
||||
}
|
||||
|
||||
bool IsValid() const
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
// Copyright (c) 2024 Triangles developers
|
||||
// Distributed under the MIT/X11 software license
|
||||
|
||||
#include "bootstrap.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "version.h"
|
||||
#include "uint256.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
// Forward declarations to avoid pulling in heavy consensus headers
|
||||
extern bool fTestNet;
|
||||
namespace Checkpoints { bool IsKnownCheckpoint(int nHeight, const uint256& hash); }
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
namespace Bootstrap {
|
||||
|
||||
bool NeedsBootstrap(const fs::path& dataDir)
|
||||
{
|
||||
return !fs::exists(dataDir / "blk0001.dat");
|
||||
}
|
||||
|
||||
bool DownloadFile(const std::string& host, const std::string& urlPath,
|
||||
const fs::path& destPath,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError)
|
||||
{
|
||||
try {
|
||||
boost::asio::io_context io_context;
|
||||
tcp::resolver resolver(io_context);
|
||||
|
||||
boost::system::error_code resolve_ec;
|
||||
tcp::resolver::results_type endpoints =
|
||||
resolver.resolve(host, std::to_string(PORT), resolve_ec);
|
||||
if (resolve_ec) {
|
||||
strError = "Cannot resolve host: " + host;
|
||||
return false;
|
||||
}
|
||||
|
||||
tcp::socket socket(io_context);
|
||||
boost::asio::connect(socket, endpoints);
|
||||
|
||||
// Send HTTP GET request
|
||||
std::string request =
|
||||
"GET " + urlPath + " HTTP/1.1\r\n"
|
||||
"Host: " + host + "\r\n"
|
||||
"Connection: close\r\n"
|
||||
"User-Agent: Triangles\r\n"
|
||||
"\r\n";
|
||||
boost::asio::write(socket, boost::asio::buffer(request));
|
||||
|
||||
// Read response headers
|
||||
boost::asio::streambuf response_buf;
|
||||
boost::asio::read_until(socket, response_buf, "\r\n\r\n");
|
||||
|
||||
std::istream response_stream(&response_buf);
|
||||
|
||||
// Parse status line
|
||||
std::string http_version;
|
||||
unsigned int status_code = 0;
|
||||
response_stream >> http_version >> status_code;
|
||||
std::string status_message;
|
||||
std::getline(response_stream, status_message);
|
||||
|
||||
if (status_code != 200) {
|
||||
strError = "HTTP error " + std::to_string(status_code) + " for " + urlPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse headers for Content-Length
|
||||
int64_t content_length = 0;
|
||||
std::string header_line;
|
||||
while (std::getline(response_stream, header_line) && header_line != "\r") {
|
||||
std::string lower_header = header_line;
|
||||
std::transform(lower_header.begin(), lower_header.end(),
|
||||
lower_header.begin(), ::tolower);
|
||||
if (lower_header.find("content-length:") == 0) {
|
||||
content_length = std::stoll(header_line.substr(header_line.find(':') + 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Open output file
|
||||
FILE* file = fopen(destPath.string().c_str(), "wb");
|
||||
if (!file) {
|
||||
strError = "Cannot create file: " + destPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t bytes_written = 0;
|
||||
|
||||
// Write any data remaining in the header buffer (body starts here)
|
||||
if (response_buf.size() > 0) {
|
||||
std::istreambuf_iterator<char> eos;
|
||||
std::string remaining(std::istreambuf_iterator<char>(response_stream), eos);
|
||||
if (!remaining.empty()) {
|
||||
fwrite(remaining.data(), 1, remaining.size(), file);
|
||||
bytes_written += remaining.size();
|
||||
}
|
||||
}
|
||||
|
||||
// Read remaining body in chunks
|
||||
std::vector<char> chunk(65536); // 64 KB
|
||||
boost::system::error_code ec;
|
||||
int64_t last_progress = 0;
|
||||
|
||||
while (true) {
|
||||
size_t n = socket.read_some(boost::asio::buffer(chunk), ec);
|
||||
if (n > 0) {
|
||||
fwrite(chunk.data(), 1, n, file);
|
||||
bytes_written += n;
|
||||
|
||||
// Report progress every 256 KB
|
||||
if (progressFn && (bytes_written - last_progress >= 262144)) {
|
||||
last_progress = bytes_written;
|
||||
progressFn(bytes_written, content_length);
|
||||
}
|
||||
}
|
||||
if (ec == boost::asio::error::eof)
|
||||
break;
|
||||
if (ec) {
|
||||
fclose(file);
|
||||
fs::remove(destPath);
|
||||
strError = "Network error: " + ec.message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
// Verify download size if Content-Length was provided
|
||||
if (content_length > 0 && bytes_written != content_length) {
|
||||
fs::remove(destPath);
|
||||
strError = "Incomplete download: got " + std::to_string(bytes_written)
|
||||
+ " of " + std::to_string(content_length) + " bytes";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (std::exception& e) {
|
||||
strError = std::string("Download failed: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool FetchFileList(const std::string& host,
|
||||
std::vector<std::string>& files,
|
||||
std::string& strError)
|
||||
{
|
||||
// Download filelist.txt to a temp file
|
||||
fs::path tmpPath = fs::temp_directory_path() / "triangles_bootstrap_filelist.txt";
|
||||
|
||||
std::string urlPath = std::string(BASE_PATH) + "filelist.txt";
|
||||
if (!DownloadFile(host, urlPath, tmpPath, nullptr, strError))
|
||||
return false;
|
||||
|
||||
// Read lines
|
||||
std::ifstream in(tmpPath.string().c_str());
|
||||
if (!in.is_open()) {
|
||||
strError = "Cannot read downloaded file list";
|
||||
return false;
|
||||
}
|
||||
|
||||
files.clear();
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
boost::trim(line);
|
||||
if (!line.empty() && line[0] != '#')
|
||||
files.push_back(line);
|
||||
}
|
||||
in.close();
|
||||
fs::remove(tmpPath);
|
||||
|
||||
if (files.empty()) {
|
||||
strError = "File list is empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- tar.gz bootstrap support ---
|
||||
|
||||
namespace {
|
||||
|
||||
// Parse a tar octal field (ASCII octal, null/space terminated)
|
||||
static int64_t ParseTarOctal(const char* field, size_t len)
|
||||
{
|
||||
int64_t result = 0;
|
||||
for (size_t i = 0; i < len && field[i] != '\0' && field[i] != ' '; i++) {
|
||||
if (field[i] < '0' || field[i] > '7') continue;
|
||||
result = (result << 3) | (field[i] - '0');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Extract a tar.gz file to a destination directory
|
||||
static bool ExtractTarGz(const fs::path& tarGzPath,
|
||||
const fs::path& destDir,
|
||||
std::string& strError)
|
||||
{
|
||||
gzFile gz = gzopen(tarGzPath.string().c_str(), "rb");
|
||||
if (!gz) {
|
||||
strError = "Cannot open " + tarGzPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
gzbuffer(gz, 262144); // 256 KB buffer for performance
|
||||
|
||||
char header[512];
|
||||
|
||||
while (true) {
|
||||
int bytesRead = gzread(gz, header, 512);
|
||||
if (bytesRead == 0) break; // EOF
|
||||
if (bytesRead != 512) {
|
||||
strError = "Truncated tar header";
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
|
||||
// End-of-archive marker (zero block)
|
||||
bool allZero = true;
|
||||
for (int i = 0; i < 512; i++) {
|
||||
if (header[i] != 0) { allZero = false; break; }
|
||||
}
|
||||
if (allZero) break;
|
||||
|
||||
// Parse filename: name (offset 0, 100 bytes) + optional prefix (offset 345, 155 bytes)
|
||||
char name[101] = {0};
|
||||
char prefix[156] = {0};
|
||||
memcpy(name, header, 100);
|
||||
memcpy(prefix, header + 345, 155);
|
||||
|
||||
std::string fullName;
|
||||
if (prefix[0] != '\0')
|
||||
fullName = std::string(prefix) + "/" + std::string(name);
|
||||
else
|
||||
fullName = std::string(name);
|
||||
|
||||
// Security: reject absolute paths and path traversal
|
||||
if (fullName.empty() || fullName[0] == '/' || fullName.find("..") != std::string::npos) {
|
||||
strError = "Unsafe path in tar archive: " + fullName;
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
|
||||
char typeflag = header[156];
|
||||
int64_t fileSize = ParseTarOctal(header + 124, 12);
|
||||
|
||||
if (typeflag == '5' || (!fullName.empty() && fullName.back() == '/')) {
|
||||
// Directory entry
|
||||
fs::create_directories(destDir / fullName);
|
||||
} else if (typeflag == '0' || typeflag == '\0') {
|
||||
// Regular file
|
||||
fs::path filePath = destDir / fullName;
|
||||
fs::create_directories(filePath.parent_path());
|
||||
|
||||
FILE* outFile = fopen(filePath.string().c_str(), "wb");
|
||||
if (!outFile) {
|
||||
strError = "Cannot create file: " + filePath.string();
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t remaining = fileSize;
|
||||
char buf[65536];
|
||||
while (remaining > 0) {
|
||||
int toRead = (remaining > (int64_t)sizeof(buf)) ? (int)sizeof(buf) : (int)remaining;
|
||||
int n = gzread(gz, buf, toRead);
|
||||
if (n <= 0) {
|
||||
fclose(outFile);
|
||||
strError = "Truncated tar data for: " + fullName;
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
fwrite(buf, 1, n, outFile);
|
||||
remaining -= n;
|
||||
}
|
||||
fclose(outFile);
|
||||
|
||||
// Skip padding to next 512-byte boundary
|
||||
int64_t pad = (512 - (fileSize % 512)) % 512;
|
||||
if (pad > 0) {
|
||||
char padBuf[512];
|
||||
if (gzread(gz, padBuf, (unsigned)pad) != (int)pad) {
|
||||
strError = "Truncated tar padding for: " + fullName;
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Unknown entry type - skip its data
|
||||
int64_t totalSkip = fileSize + ((512 - (fileSize % 512)) % 512);
|
||||
char skipBuf[512];
|
||||
while (totalSkip > 0) {
|
||||
int toRead = (totalSkip > 512) ? 512 : (int)totalSkip;
|
||||
if (gzread(gz, skipBuf, toRead) != toRead) break;
|
||||
totalSkip -= toRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gzclose(gz);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool ParseManifest(const fs::path& manifestPath,
|
||||
SnapshotManifest& manifest,
|
||||
std::string& strError)
|
||||
{
|
||||
std::ifstream in(manifestPath.string().c_str());
|
||||
if (!in.is_open()) {
|
||||
strError = "Cannot open " + manifestPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
manifest.format = 0;
|
||||
manifest.network.clear();
|
||||
manifest.height = -1;
|
||||
manifest.hash.clear();
|
||||
manifest.dbversion = 0;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
boost::trim(line);
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
size_t eq = line.find('=');
|
||||
if (eq == std::string::npos)
|
||||
continue;
|
||||
|
||||
std::string key = line.substr(0, eq);
|
||||
std::string val = line.substr(eq + 1);
|
||||
boost::trim(key);
|
||||
boost::trim(val);
|
||||
|
||||
if (key == "format")
|
||||
manifest.format = std::atoi(val.c_str());
|
||||
else if (key == "network")
|
||||
manifest.network = val;
|
||||
else if (key == "height")
|
||||
manifest.height = std::atoi(val.c_str());
|
||||
else if (key == "hash")
|
||||
manifest.hash = val;
|
||||
else if (key == "dbversion")
|
||||
manifest.dbversion = std::atoi(val.c_str());
|
||||
}
|
||||
in.close();
|
||||
|
||||
if (manifest.format == 0) {
|
||||
strError = "Manifest missing 'format' field";
|
||||
return false;
|
||||
}
|
||||
if (manifest.network.empty()) {
|
||||
strError = "Manifest missing 'network' field";
|
||||
return false;
|
||||
}
|
||||
if (manifest.height < 0) {
|
||||
strError = "Manifest missing or invalid 'height' field";
|
||||
return false;
|
||||
}
|
||||
if (manifest.hash.empty()) {
|
||||
strError = "Manifest missing 'hash' field";
|
||||
return false;
|
||||
}
|
||||
if (manifest.dbversion == 0) {
|
||||
strError = "Manifest missing 'dbversion' field";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VerifyManifest(const SnapshotManifest& manifest,
|
||||
std::string& strError)
|
||||
{
|
||||
if (manifest.format != 1) {
|
||||
strError = "Unsupported manifest format: " + std::to_string(manifest.format);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string expectedNetwork = fTestNet ? "test" : "main";
|
||||
if (manifest.network != expectedNetwork) {
|
||||
strError = "Network mismatch: manifest says '" + manifest.network
|
||||
+ "', expected '" + expectedNetwork + "'";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (manifest.dbversion != DATABASE_VERSION) {
|
||||
strError = "DB version mismatch: manifest says "
|
||||
+ std::to_string(manifest.dbversion)
|
||||
+ ", binary expects " + std::to_string(DATABASE_VERSION);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 manifestHash(manifest.hash);
|
||||
if (manifestHash == 0) {
|
||||
strError = "Invalid hash in manifest: " + manifest.hash;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Checkpoints::IsKnownCheckpoint(manifest.height, manifestHash)) {
|
||||
strError = "Height " + std::to_string(manifest.height)
|
||||
+ " / hash " + manifest.hash
|
||||
+ " is not a known checkpoint";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DownloadBootstrap(const std::string& host,
|
||||
const fs::path& dataDir,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError)
|
||||
{
|
||||
bool gotBlockFile = false;
|
||||
|
||||
// Try downloading bootstrap.tar.gz first
|
||||
fs::path tmpTarGz = dataDir / "bootstrap.tar.gz.tmp";
|
||||
std::string tarUrl = std::string(BASE_PATH) + "bootstrap.tar.gz";
|
||||
|
||||
bool tarDownloaded = DownloadFile(host, tarUrl, tmpTarGz, progressFn, strError);
|
||||
|
||||
if (tarDownloaded) {
|
||||
bool extractOk = ExtractTarGz(tmpTarGz, dataDir, strError);
|
||||
fs::remove(tmpTarGz);
|
||||
|
||||
if (extractOk && fs::exists(dataDir / "blk0001.dat"))
|
||||
gotBlockFile = true;
|
||||
// If extraction failed, fall through to legacy path
|
||||
}
|
||||
|
||||
if (!gotBlockFile) {
|
||||
// Fallback: try filelist.txt + individual file downloads
|
||||
std::string fallbackError;
|
||||
std::vector<std::string> files;
|
||||
if (!FetchFileList(host, files, fallbackError)) {
|
||||
if (!tarDownloaded)
|
||||
strError = strError + " (fallback also failed: " + fallbackError + ")";
|
||||
else
|
||||
strError = "Extraction failed: " + strError + " (fallback also failed: " + fallbackError + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
fs::path destPath = dataDir / files[i];
|
||||
fs::create_directories(destPath.parent_path());
|
||||
|
||||
std::string urlPath = std::string(BASE_PATH) + files[i];
|
||||
if (!DownloadFile(host, urlPath, destPath, progressFn, strError))
|
||||
return false;
|
||||
}
|
||||
|
||||
gotBlockFile = fs::exists(dataDir / "blk0001.dat");
|
||||
}
|
||||
|
||||
if (!gotBlockFile) {
|
||||
strError = "No blk0001.dat after download";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the archive included a trusted pre-built index (txleveldb/)
|
||||
// with a valid snapshot.manifest. If verified, keep it to skip the
|
||||
// multi-hour FastImportBlockFile() rebuild.
|
||||
fs::path txleveldb = dataDir / "txleveldb";
|
||||
fs::path database = dataDir / "database";
|
||||
fs::path manifestPath = dataDir / "snapshot.manifest";
|
||||
|
||||
bool keepIndex = false;
|
||||
|
||||
if (fs::exists(manifestPath) && fs::exists(txleveldb)) {
|
||||
SnapshotManifest manifest;
|
||||
std::string manifestError;
|
||||
|
||||
if (ParseManifest(manifestPath, manifest, manifestError)) {
|
||||
printf("Bootstrap: snapshot.manifest found (format=%d, network=%s, "
|
||||
"height=%d, dbversion=%d)\n",
|
||||
manifest.format, manifest.network.c_str(),
|
||||
manifest.height, manifest.dbversion);
|
||||
|
||||
if (VerifyManifest(manifest, manifestError)) {
|
||||
printf("Bootstrap: manifest verified - keeping pre-built index "
|
||||
"(height %d, checkpoint match)\n", manifest.height);
|
||||
keepIndex = true;
|
||||
} else {
|
||||
printf("Bootstrap: manifest verification failed: %s\n",
|
||||
manifestError.c_str());
|
||||
}
|
||||
} else {
|
||||
printf("Bootstrap: cannot parse snapshot.manifest: %s\n",
|
||||
manifestError.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!keepIndex) {
|
||||
// No valid manifest or verification failed - delete the index.
|
||||
// FastImportBlockFile() will rebuild from blk0001.dat on next startup.
|
||||
printf("Bootstrap: removing extracted txleveldb/ (will rebuild index from blk0001.dat)\n");
|
||||
if (fs::exists(txleveldb))
|
||||
fs::remove_all(txleveldb);
|
||||
}
|
||||
|
||||
// Always remove BDB database/ dir (wallet environment from another machine)
|
||||
if (fs::exists(database))
|
||||
fs::remove_all(database);
|
||||
|
||||
// Clean up manifest file (not needed after verification)
|
||||
if (fs::exists(manifestPath))
|
||||
fs::remove(manifestPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Bootstrap
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2024 Triangles developers
|
||||
// Distributed under the MIT/X11 software license
|
||||
|
||||
#ifndef TRIANGLES_BOOTSTRAP_H
|
||||
#define TRIANGLES_BOOTSTRAP_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
namespace Bootstrap {
|
||||
|
||||
// Bootstrap server configuration
|
||||
static const char* DEFAULT_HOST = "bootstrap.cryptographic-triangles.org";
|
||||
static const char* FALLBACK_HOST = "194.233.88.206";
|
||||
static const char* BASE_PATH = "/";
|
||||
static const int PORT = 80;
|
||||
|
||||
// Progress callback: (bytesDownloaded, totalBytes)
|
||||
typedef std::function<void(int64_t, int64_t)> ProgressCallback;
|
||||
|
||||
// Check if data dir already has blockchain data
|
||||
bool NeedsBootstrap(const boost::filesystem::path& dataDir);
|
||||
|
||||
// Download a single file via HTTP GET, write to destPath
|
||||
bool DownloadFile(const std::string& host, const std::string& urlPath,
|
||||
const boost::filesystem::path& destPath,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError);
|
||||
|
||||
// Fetch the file manifest (list of relative paths to download)
|
||||
bool FetchFileList(const std::string& host,
|
||||
std::vector<std::string>& files,
|
||||
std::string& strError);
|
||||
|
||||
// Download bootstrap.tar.gz and extract to dataDir.
|
||||
// Falls back to filelist.txt + individual file download if tar.gz unavailable.
|
||||
bool DownloadBootstrap(const std::string& host,
|
||||
const boost::filesystem::path& dataDir,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError);
|
||||
|
||||
// Snapshot manifest (parsed from snapshot.manifest in bootstrap archive)
|
||||
struct SnapshotManifest {
|
||||
int format; // format version, must be 1
|
||||
std::string network; // "main" or "test"
|
||||
int height; // block height of the snapshot tip
|
||||
std::string hash; // block hash at that height (hex, no 0x prefix)
|
||||
int dbversion; // DATABASE_VERSION the txleveldb was built with
|
||||
};
|
||||
|
||||
// Parse a snapshot.manifest file into a SnapshotManifest struct.
|
||||
bool ParseManifest(const boost::filesystem::path& manifestPath,
|
||||
SnapshotManifest& manifest,
|
||||
std::string& strError);
|
||||
|
||||
// Verify a parsed manifest against compiled-in checkpoints and config.
|
||||
bool VerifyManifest(const SnapshotManifest& manifest,
|
||||
std::string& strError);
|
||||
|
||||
} // namespace Bootstrap
|
||||
|
||||
#endif // TRIANGLES_BOOTSTRAP_H
|
||||
@@ -2,9 +2,6 @@
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <boost/assign/list_of.hpp> // for 'map_list_of()'
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "checkpoints.h"
|
||||
|
||||
#include "txdb.h"
|
||||
@@ -22,39 +19,37 @@ namespace Checkpoints
|
||||
// timestamp before)
|
||||
// + Contains no strange transactions
|
||||
//
|
||||
static MapCheckpoints mapCheckpoints =
|
||||
boost::assign::map_list_of
|
||||
( 0, hashGenesisBlockOfficial )
|
||||
( 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467"))
|
||||
( 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059"))
|
||||
( 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e"))
|
||||
( 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51"))
|
||||
( 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6"))
|
||||
( 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b"))
|
||||
( 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db"))
|
||||
( 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007"))
|
||||
( 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249"))
|
||||
( 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6"))
|
||||
( 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47"))
|
||||
(2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0"))
|
||||
;
|
||||
static MapCheckpoints mapCheckpoints = {
|
||||
{ 0, hashGenesisBlockOfficial },
|
||||
{ 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467")},
|
||||
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
|
||||
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
|
||||
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
|
||||
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
|
||||
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
|
||||
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
|
||||
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
|
||||
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
|
||||
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
|
||||
};
|
||||
|
||||
static MapCheckpoints mapCheckpointsTestnet =
|
||||
boost::assign::map_list_of
|
||||
( 0, hashGenesisBlockTestNet )
|
||||
( 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467"))
|
||||
( 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059"))
|
||||
( 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e"))
|
||||
( 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51"))
|
||||
( 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6"))
|
||||
( 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b"))
|
||||
( 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db"))
|
||||
( 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007"))
|
||||
( 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249"))
|
||||
( 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6"))
|
||||
( 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47"))
|
||||
(2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0"))
|
||||
;
|
||||
static MapCheckpoints mapCheckpointsTestnet = {
|
||||
{ 0, hashGenesisBlockTestNet },
|
||||
{ 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467")},
|
||||
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
|
||||
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
|
||||
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
|
||||
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
|
||||
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
|
||||
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
|
||||
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
|
||||
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
|
||||
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
|
||||
};
|
||||
|
||||
bool CheckHardened(int nHeight, const uint256& hash)
|
||||
{
|
||||
@@ -65,6 +60,14 @@ namespace Checkpoints
|
||||
return hash == i->second;
|
||||
}
|
||||
|
||||
bool IsKnownCheckpoint(int nHeight, const uint256& hash)
|
||||
{
|
||||
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
|
||||
MapCheckpoints::const_iterator i = checkpoints.find(nHeight);
|
||||
if (i == checkpoints.end()) return false;
|
||||
return hash == i->second;
|
||||
}
|
||||
|
||||
int GetTotalBlocksEstimate()
|
||||
{
|
||||
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
|
||||
@@ -76,9 +79,9 @@ namespace Checkpoints
|
||||
{
|
||||
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
|
||||
|
||||
BOOST_REVERSE_FOREACH(const MapCheckpoints::value_type& i, checkpoints)
|
||||
for (auto it = checkpoints.rbegin(); it != checkpoints.rend(); ++it)
|
||||
{
|
||||
const uint256& hash = i.second;
|
||||
const uint256& hash = it->second;
|
||||
std::map<uint256, CBlockIndex*>::const_iterator t = mapBlockIndex.find(hash);
|
||||
if (t != mapBlockIndex.end())
|
||||
return t->second;
|
||||
@@ -87,8 +90,8 @@ namespace Checkpoints
|
||||
}
|
||||
|
||||
// triangles: synchronized checkpoint (centrally broadcasted)
|
||||
uint256 hashSyncCheckpoint = uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0");
|
||||
uint256 hashPendingCheckpoint = uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0");
|
||||
uint256 hashSyncCheckpoint = uint256("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
||||
uint256 hashPendingCheckpoint = uint256("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
|
||||
CSyncCheckpoint checkpointMessage;
|
||||
CSyncCheckpoint checkpointMessagePending;
|
||||
uint256 hashInvalidCheckpoint = 0;
|
||||
@@ -199,7 +202,7 @@ namespace Checkpoints
|
||||
// relay the checkpoint
|
||||
if (!checkpointMessage.IsNull())
|
||||
{
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
checkpointMessage.RelayTo(pnode);
|
||||
}
|
||||
return true;
|
||||
@@ -218,30 +221,10 @@ namespace Checkpoints
|
||||
}
|
||||
|
||||
// Check against synchronized checkpoint
|
||||
// Disabled: master key removed in V5, no new sync checkpoints possible.
|
||||
// Always returns true to prevent stale DB-persisted checkpoints from blocking IBD.
|
||||
bool CheckSync(const uint256& hashBlock, const CBlockIndex* pindexPrev)
|
||||
{
|
||||
if (fTestNet) return true; // Testnet has no checkpoints
|
||||
int nHeight = pindexPrev->nHeight + 1;
|
||||
|
||||
LOCK(cs_hashSyncCheckpoint);
|
||||
// sync-checkpoint should always be accepted block
|
||||
assert(mapBlockIndex.count(hashSyncCheckpoint));
|
||||
const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint];
|
||||
|
||||
if (nHeight > pindexSync->nHeight)
|
||||
{
|
||||
// trace back to same height as sync-checkpoint
|
||||
const CBlockIndex* pindex = pindexPrev;
|
||||
while (pindex->nHeight > pindexSync->nHeight)
|
||||
if (!(pindex = pindex->pprev))
|
||||
return error("CheckSync: pprev null - block index structure failure");
|
||||
if (pindex->nHeight < pindexSync->nHeight || pindex->GetBlockHash() != hashSyncCheckpoint)
|
||||
return false; // only descendant of sync-checkpoint can pass check
|
||||
}
|
||||
if (nHeight == pindexSync->nHeight && hashBlock != hashSyncCheckpoint)
|
||||
return false; // same height with sync-checkpoint
|
||||
if (nHeight < pindexSync->nHeight && !mapBlockIndex.count(hashBlock))
|
||||
return false; // lower height than sync-checkpoint
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -284,9 +267,9 @@ namespace Checkpoints
|
||||
printf("ResetSyncCheckpoint: pending for sync-checkpoint %s\n", hashPendingCheckpoint.ToString().c_str());
|
||||
}
|
||||
|
||||
BOOST_REVERSE_FOREACH(const MapCheckpoints::value_type& i, mapCheckpoints)
|
||||
for (auto it = mapCheckpoints.rbegin(); it != mapCheckpoints.rend(); ++it)
|
||||
{
|
||||
const uint256& hash = i.second;
|
||||
const uint256& hash = it->second;
|
||||
if (mapBlockIndex.count(hash) && mapBlockIndex[hash]->IsInMainChain())
|
||||
{
|
||||
if (!WriteSyncCheckpoint(hash))
|
||||
@@ -351,7 +334,7 @@ namespace Checkpoints
|
||||
// Relay checkpoint
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
checkpoint.RelayTo(pnode);
|
||||
}
|
||||
return true;
|
||||
@@ -361,8 +344,8 @@ namespace Checkpoints
|
||||
bool IsMatureSyncCheckpoint()
|
||||
{
|
||||
LOCK(cs_hashSyncCheckpoint);
|
||||
// sync-checkpoint should always be accepted block
|
||||
assert(mapBlockIndex.count(hashSyncCheckpoint));
|
||||
if (!mapBlockIndex.count(hashSyncCheckpoint))
|
||||
return true; // no valid sync checkpoint, treat as mature
|
||||
const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint];
|
||||
return (nBestHeight >= pindexSync->nHeight + nCoinbaseMaturity ||
|
||||
pindexSync->GetBlockTime() + nStakeMinAge < GetAdjustedTime());
|
||||
|
||||
@@ -39,6 +39,9 @@ namespace Checkpoints
|
||||
// Returns true if block passes checkpoint checks
|
||||
bool CheckHardened(int nHeight, const uint256& hash);
|
||||
|
||||
// Returns true only if (nHeight, hash) is an exact entry in mapCheckpoints
|
||||
bool IsKnownCheckpoint(int nHeight, const uint256& hash);
|
||||
|
||||
// Return conservative estimate of total number of blocks, 0 if unknown
|
||||
int GetTotalBlocksEstimate();
|
||||
|
||||
|
||||
@@ -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 1
|
||||
#define CLIENT_VERSION_REVISION 9
|
||||
#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.
|
||||
|
||||
@@ -79,7 +79,7 @@ bool CDBEnv::Open(fs::path pathEnv_)
|
||||
if (GetBoolArg("-privdb", true))
|
||||
nEnvFlags |= DB_PRIVATE;
|
||||
|
||||
int nDbCache = GetArg("-dbcache", 128);
|
||||
int nDbCache = GetArg("-dbcache", 2048);
|
||||
dbenv.set_lg_dir(pathLogDir.string().c_str());
|
||||
dbenv.set_cachesize(nDbCache / 1024, (nDbCache % 1024)*1048576, 1);
|
||||
dbenv.set_lg_bsize(1048576);
|
||||
@@ -479,7 +479,7 @@ void CDBEnv::Flush(bool fShutdown)
|
||||
else
|
||||
mi++;
|
||||
}
|
||||
printf("DBFlush(%s)%s ended %15"PRId64"ms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " db not started", GetTimeMillis() - nStart);
|
||||
printf("DBFlush(%s)%s ended %15" PRId64 "ms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " db not started", GetTimeMillis() - nStart);
|
||||
if (fShutdown)
|
||||
{
|
||||
char** listp;
|
||||
|
||||
@@ -6,11 +6,15 @@
|
||||
#include "walletdb.h"
|
||||
#include "trianglesrpc.h"
|
||||
#include "net.h"
|
||||
#include "netbase.h"
|
||||
#include "init.h"
|
||||
#include "util.h"
|
||||
#include "ui_interface.h"
|
||||
#include "checkpoints.h"
|
||||
#include "smessage.h"
|
||||
#include "openssl_compat.h"
|
||||
#include "bootstrap.h"
|
||||
#include "tor/tor_embedded.h"
|
||||
#include "tor/onion_v3.h"
|
||||
#include "tor/tor_process.h"
|
||||
#ifdef ENABLE_ZMQ
|
||||
@@ -48,6 +52,21 @@ enum Checkpoints::CPMode CheckpointsMode;
|
||||
static CCriticalSection cs_DeferredStartup;
|
||||
static bool fDeferredStartupRunning = false;
|
||||
|
||||
static void StartupPerfLog(const char* phase, int64_t elapsedMs)
|
||||
{
|
||||
printf("STARTUP-PERF: %s %" PRId64 "ms\n", phase, elapsedMs);
|
||||
}
|
||||
|
||||
static void StartupPerfLog(const char* phase, int64_t elapsedMs, const std::string& detail)
|
||||
{
|
||||
if (detail.empty())
|
||||
{
|
||||
StartupPerfLog(phase, elapsedMs);
|
||||
return;
|
||||
}
|
||||
printf("STARTUP-PERF: %s %" PRId64 "ms %s\n", phase, elapsedMs, detail.c_str());
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Shutdown
|
||||
@@ -92,17 +111,20 @@ void ThreadDeferredStartup(void* parg)
|
||||
{
|
||||
int64_t nStart = GetTimeMillis();
|
||||
SecureMsgStart(fNoSmsg, GetBoolArg("-smsgscanchain"));
|
||||
printf(" securemsg %15"PRId64"ms\n", GetTimeMillis() - nStart);
|
||||
printf(" securemsg %15" PRId64 "ms\n", GetTimeMillis() - nStart);
|
||||
StartupPerfLog("deferred.securemsg", GetTimeMillis() - nStart);
|
||||
}
|
||||
|
||||
if (!fShutdown && pwalletMain)
|
||||
{
|
||||
int64_t nStart = GetTimeMillis();
|
||||
pwalletMain->ReacceptWalletTransactions();
|
||||
printf(" reaccept %15"PRId64"ms\n", GetTimeMillis() - nStart);
|
||||
printf(" reaccept %15" PRId64 "ms\n", GetTimeMillis() - nStart);
|
||||
StartupPerfLog("deferred.reaccept_wallet_transactions", GetTimeMillis() - nStart);
|
||||
}
|
||||
|
||||
printf("Deferred startup tasks finished %"PRId64"ms\n", GetTimeMillis() - nTotalStart);
|
||||
printf("Deferred startup tasks finished %" PRId64 "ms\n", GetTimeMillis() - nTotalStart);
|
||||
StartupPerfLog("deferred.total", GetTimeMillis() - nTotalStart);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
@@ -155,8 +177,14 @@ void Shutdown(void* parg)
|
||||
}
|
||||
|
||||
SecureMsgShutdown();
|
||||
|
||||
// Stop network threads FIRST so nothing references Tor objects
|
||||
nTransactionsUpdated++;
|
||||
StopNode();
|
||||
|
||||
// NOW safe to destroy Tor state - all threads have stopped
|
||||
ShutdownTorV3();
|
||||
StopTorProcess();
|
||||
StopEmbeddedTor();
|
||||
|
||||
#ifdef ENABLE_ZMQ
|
||||
if (pzmqNotifier)
|
||||
@@ -173,10 +201,8 @@ void Shutdown(void* parg)
|
||||
pNotificationQueue = NULL;
|
||||
}
|
||||
|
||||
nTransactionsUpdated++;
|
||||
// CTxDB().Close();
|
||||
bitdb.Flush(false);
|
||||
StopNode();
|
||||
bitdb.Flush(true);
|
||||
fs::remove(GetPidFile());
|
||||
UnregisterWallet(pwalletMain);
|
||||
@@ -332,6 +358,10 @@ 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 (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" +
|
||||
//" -dns " + _("Allow DNS lookups for -addnode, -seednode and -connect") + "\n" +
|
||||
" -port=<port> " + _("Listen for connections on <port> (default: 24112 or testnet: 24111)") + "\n" +
|
||||
" -maxconnections=<n> " + _("Maintain at most <n> connections to peers (default: 125)") + "\n" +
|
||||
@@ -349,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" +
|
||||
@@ -391,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" +
|
||||
@@ -443,6 +477,7 @@ bool InitSanityCheck(void)
|
||||
*/
|
||||
bool AppInit2()
|
||||
{
|
||||
const int64_t nAppInitStart = GetTimeMillis();
|
||||
// ********************************************************* Step 1: setup
|
||||
#ifdef _MSC_VER
|
||||
// Turn off Microsoft heap dump noise
|
||||
@@ -544,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");
|
||||
@@ -599,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))
|
||||
@@ -651,7 +695,7 @@ bool AppInit2()
|
||||
ShrinkDebugFile();
|
||||
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
|
||||
printf("Triangles version %s (%s)\n", FormatFullVersion().c_str(), CLIENT_DATE.c_str());
|
||||
printf("Using OpenSSL version %s\n", SSLeay_version(SSLEAY_VERSION));
|
||||
printf("Using OpenSSL version %s\n", TrianglesOpenSSLVersionString());
|
||||
if (!fLogTimestamps)
|
||||
printf("Startup time: %s\n", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str());
|
||||
printf("Default data directory %s\n", GetDefaultDataDir().string().c_str());
|
||||
@@ -666,6 +710,7 @@ bool AppInit2()
|
||||
// ********************************************************* Step 5: verify database integrity
|
||||
|
||||
uiInterface.InitMessage(_("Verifying database integrity..."));
|
||||
nStart = GetTimeMillis();
|
||||
|
||||
if (!bitdb.Open(GetDataDir()))
|
||||
{
|
||||
@@ -682,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);
|
||||
@@ -696,20 +748,22 @@ bool AppInit2()
|
||||
if (r == CDBEnv::RECOVER_FAIL)
|
||||
return InitError(_("wallet.dat corrupt, salvage failed"));
|
||||
}
|
||||
StartupPerfLog("verify_db", GetTimeMillis() - nStart, strprintf("wallet=%s", strWalletFileName.c_str()));
|
||||
|
||||
// ********************************************************* Step 6: network initialization
|
||||
nStart = GetTimeMillis();
|
||||
|
||||
//int nSocksVersion = GetArg("-socks", 5);
|
||||
//
|
||||
//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;
|
||||
BOOST_FOREACH(std::string snet, mapMultiArgs["-onlynet"]) {
|
||||
for (std::string snet : mapMultiArgs["-onlynet"]) {
|
||||
enum Network net = ParseNetwork(snet);
|
||||
if (net == NET_UNROUTABLE)
|
||||
return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet.c_str()));
|
||||
@@ -724,7 +778,7 @@ bool AppInit2()
|
||||
|
||||
// Tor proxy: always configured for .onion connectivity
|
||||
CService addrOnion;
|
||||
unsigned short const onion_port = 19099;
|
||||
unsigned short const onion_port = static_cast<unsigned short>(GetArg("-torsocks", 19099));
|
||||
|
||||
if (mapArgs.count("-tor") && mapArgs["-tor"] != "0") {
|
||||
addrOnion = CService(mapArgs["-tor"], onion_port);
|
||||
@@ -761,11 +815,11 @@ bool AppInit2()
|
||||
|
||||
|
||||
// Release the old Tor initialization mutex (no longer blocking on embedded Tor)
|
||||
set_initialized();
|
||||
triangles_tor_set_initialized();
|
||||
|
||||
if (mapArgs.count("-externalip"))
|
||||
{
|
||||
BOOST_FOREACH(string strAddr, mapMultiArgs["-externalip"]) {
|
||||
for (string strAddr : mapMultiArgs["-externalip"]) {
|
||||
CService addrLocal(strAddr, GetListenPort(), fNameLookup);
|
||||
if (!addrLocal.IsValid())
|
||||
return InitError(strprintf(_("Cannot resolve -externalip address: '%s'"), strAddr.c_str()));
|
||||
@@ -789,8 +843,64 @@ bool AppInit2()
|
||||
InitError(_("Unable to sign checkpoint, wrong checkpointkey?\n"));
|
||||
}
|
||||
|
||||
BOOST_FOREACH(string strDest, mapMultiArgs["-seednode"])
|
||||
for (string strDest : mapMultiArgs["-seednode"])
|
||||
AddOneShot(strDest);
|
||||
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
|
||||
{
|
||||
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();
|
||||
std::string host = Bootstrap::DEFAULT_HOST;
|
||||
std::string strError;
|
||||
|
||||
uiInterface.InitMessage(_("Downloading blockchain snapshot..."));
|
||||
printf("Bootstrap: contacting %s...\n", host.c_str());
|
||||
|
||||
auto progressFn = [](int64_t bytesDownloaded, int64_t totalBytes) {
|
||||
if (totalBytes > 0) {
|
||||
printf("\rBootstrap: %lld / %lld MB (%lld%%)",
|
||||
(long long)(bytesDownloaded / (1024*1024)),
|
||||
(long long)(totalBytes / (1024*1024)),
|
||||
(long long)((bytesDownloaded * 100) / totalBytes));
|
||||
fflush(stdout);
|
||||
}
|
||||
};
|
||||
|
||||
bool success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
|
||||
if (!success) {
|
||||
host = Bootstrap::FALLBACK_HOST;
|
||||
printf("\nBootstrap: primary host failed, trying fallback %s...\n", host.c_str());
|
||||
success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
printf("\nBootstrap: failed: %s\n", strError.c_str());
|
||||
printf("Bootstrap: skipping, will sync from network.\n");
|
||||
} else {
|
||||
printf("\nBootstrap: done.\n");
|
||||
}
|
||||
StartupPerfLog("bootstrap_download", GetTimeMillis() - nBootstrapStart,
|
||||
strprintf("host=%s success=%d", host.c_str(), success));
|
||||
}
|
||||
} // end bootstrap scope
|
||||
#endif
|
||||
|
||||
// ********************************************************* Step 7: load blockchain
|
||||
|
||||
@@ -816,6 +926,18 @@ bool AppInit2()
|
||||
if (!LoadBlockIndex())
|
||||
return InitError(_("Error loading blkindex.dat"));
|
||||
|
||||
// If the block index is empty but blk0001.dat exists (bootstrap download),
|
||||
// fast-import: build the index directly from the block file without re-writing
|
||||
// data. Batches LevelDB commits every 200K blocks for speed.
|
||||
if (nBestHeight == 0 && boost::filesystem::exists(GetDataDir() / "blk0001.dat")
|
||||
&& mapBlockIndex.size() <= 1)
|
||||
{
|
||||
uiInterface.InitMessage(_("Importing bootstrap blocks..."));
|
||||
printf("Block index empty but blk0001.dat exists - running fast import...\n");
|
||||
int64_t nFastImportStart = GetTimeMillis();
|
||||
FastImportBlockFile();
|
||||
StartupPerfLog("bootstrap_fast_import", GetTimeMillis() - nFastImportStart, strprintf("bestheight=%d", nBestHeight));
|
||||
}
|
||||
|
||||
// as LoadBlockIndex can take several minutes, it's possible the user
|
||||
// requested to kill triangles-qt during the last operation. If so, exit.
|
||||
@@ -825,7 +947,24 @@ bool AppInit2()
|
||||
printf("Shutdown requested. Exiting.\n");
|
||||
return false;
|
||||
}
|
||||
printf(" block index %15"PRId64"ms\n", GetTimeMillis() - nStart);
|
||||
printf(" block index %15" PRId64 "ms\n", GetTimeMillis() - nStart);
|
||||
StartupPerfLog("block_index", GetTimeMillis() - nStart, strprintf("bestheight=%d indexsize=%" PRIszu, nBestHeight, mapBlockIndex.size()));
|
||||
|
||||
// Diagnostic: check for blocks in mapBlockIndex above pindexBest
|
||||
{
|
||||
int nMaxIndexHeight = 0;
|
||||
int nAboveBest = 0;
|
||||
for (std::map<uint256, CBlockIndex*>::iterator it = mapBlockIndex.begin();
|
||||
it != mapBlockIndex.end(); ++it)
|
||||
{
|
||||
if (it->second->nHeight > nMaxIndexHeight)
|
||||
nMaxIndexHeight = it->second->nHeight;
|
||||
if (it->second->nHeight > nBestHeight)
|
||||
nAboveBest++;
|
||||
}
|
||||
printf("SYNC-DIAG: mapBlockIndex=%d entries, maxHeight=%d, bestHeight=%d, aboveBest=%d\n",
|
||||
(int)mapBlockIndex.size(), nMaxIndexHeight, nBestHeight, nAboveBest);
|
||||
}
|
||||
|
||||
if (GetBoolArg("-printblockindex") || GetBoolArg("-printblocktree"))
|
||||
{
|
||||
@@ -919,7 +1058,8 @@ bool AppInit2()
|
||||
}
|
||||
|
||||
printf("%s", strErrors.str().c_str());
|
||||
printf(" wallet %15"PRId64"ms\n", GetTimeMillis() - nStart);
|
||||
printf(" wallet %15" PRId64 "ms\n", GetTimeMillis() - nStart);
|
||||
StartupPerfLog("wallet_load", GetTimeMillis() - nStart, strprintf("firstrun=%d", fFirstRun));
|
||||
|
||||
RegisterWallet(pwalletMain);
|
||||
|
||||
@@ -928,10 +1068,12 @@ bool AppInit2()
|
||||
pindexRescan = pindexGenesisBlock;
|
||||
else
|
||||
{
|
||||
int64_t nWalletLocatorStart = GetTimeMillis();
|
||||
CWalletDB walletdb(strWalletFileName);
|
||||
CBlockLocator locator;
|
||||
if (walletdb.ReadBestBlock(locator))
|
||||
pindexRescan = locator.GetBlockIndex();
|
||||
StartupPerfLog("wallet_bestblock_locator", GetTimeMillis() - nWalletLocatorStart);
|
||||
}
|
||||
if (pindexBest != pindexRescan && pindexBest && pindexRescan && pindexBest->nHeight > pindexRescan->nHeight)
|
||||
{
|
||||
@@ -939,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;
|
||||
@@ -963,7 +1105,13 @@ bool AppInit2()
|
||||
if (!fScannedWithIndex)
|
||||
pwalletMain->ScanForWalletTransactions(pindexRescan, true);
|
||||
|
||||
printf(" rescan %15"PRId64"ms\n", GetTimeMillis() - nStart);
|
||||
printf(" rescan %15" PRId64 "ms\n", GetTimeMillis() - nStart);
|
||||
StartupPerfLog("wallet_rescan", GetTimeMillis() - nStart,
|
||||
strprintf("from=%d to=%d indexed=%d", pindexRescan->nHeight, pindexBest->nHeight, fScannedWithIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
StartupPerfLog("wallet_rescan", 0, "skipped");
|
||||
}
|
||||
|
||||
// ********************************************************* Step 8.5: start Tor and initialize V3 identity
|
||||
@@ -971,31 +1119,87 @@ bool AppInit2()
|
||||
uiInterface.InitMessage(_("Starting Tor..."));
|
||||
printf("Starting Tor process...\n");
|
||||
|
||||
// Start the Tor process (finds/launches tor binary, provides SOCKS proxy)
|
||||
std::string torDataPath = (GetDataDir() / "tor_data").string();
|
||||
bool torStarted = StartTorProcess(torDataPath);
|
||||
// Restore hidden service secret key from wallet backup if the key
|
||||
// file is missing on disk. This preserves the .onion identity even
|
||||
// if the tor_data directory was deleted.
|
||||
if (pwalletMain && !GetBoolArg("-notor", false)) {
|
||||
std::string restoreDataPath = GetArg("-tordatadir", (GetDataDir() / "tor_data").string());
|
||||
fs::path secretKeyPath = fs::path(restoreDataPath) / "hidden_service" / "hs_ed25519_secret_key";
|
||||
|
||||
if (!fs::exists(secretKeyPath)) {
|
||||
CWalletDB walletdb(pwalletMain->strWalletFile);
|
||||
std::vector<unsigned char> backedUpKey;
|
||||
|
||||
if (walletdb.ReadSetting("tor_v3_hs_secret_key_backup", backedUpKey) &&
|
||||
backedUpKey.size() == 96) {
|
||||
fs::create_directories(secretKeyPath.parent_path());
|
||||
|
||||
std::ofstream keyFile(secretKeyPath.string().c_str(), std::ios::binary);
|
||||
if (keyFile.is_open()) {
|
||||
keyFile.write(reinterpret_cast<const char*>(backedUpKey.data()),
|
||||
backedUpKey.size());
|
||||
keyFile.close();
|
||||
printf("Restored Tor hidden service secret key from wallet backup\n");
|
||||
} else {
|
||||
printf("WARNING: Failed to write restored hs_ed25519_secret_key to %s\n",
|
||||
secretKeyPath.string().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
OPENSSL_cleanse(backedUpKey.data(), backedUpKey.size());
|
||||
}
|
||||
}
|
||||
|
||||
int64_t nTorStart = GetTimeMillis();
|
||||
bool torStarted = StartEmbeddedTor();
|
||||
StartupPerfLog("tor_start", GetTimeMillis() - nTorStart, strprintf("started=%d", torStarted));
|
||||
std::string torDataPath = CTorEmbedded::GetInstance()->GetDataDir();
|
||||
if (torDataPath.empty())
|
||||
torDataPath = (GetDataDir() / "tor_data").string();
|
||||
|
||||
if (torStarted) {
|
||||
printf("Tor process running, SOCKS proxy at %s\n",
|
||||
CTorProcess::GetInstance()->GetSocksProxy().c_str());
|
||||
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)
|
||||
uiInterface.InitMessage(_("Initializing Tor V3 identity..."));
|
||||
printf("Initializing Tor V3 onion identity...\n");
|
||||
|
||||
int64_t nTorIdentityStart = GetTimeMillis();
|
||||
LoadTorV3Config();
|
||||
TorV3Config& torConfig = GetTorV3Config();
|
||||
torConfig.enableTor = true;
|
||||
torConfig.enableHiddenService = true;
|
||||
torConfig.hiddenServicePort = GetListenPort();
|
||||
torConfig.enableTor = torStarted;
|
||||
torConfig.enableHiddenService = torStarted && CTorEmbedded::GetInstance()->IsHiddenServiceEnabled();
|
||||
torConfig.hiddenServicePort = CTorEmbedded::GetInstance()->GetHiddenServicePort();
|
||||
torConfig.torDataDirectory = torDataPath;
|
||||
std::string onionAddr;
|
||||
|
||||
if (InitTorV3()) {
|
||||
string onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
|
||||
if (torConfig.enableTor && torConfig.enableHiddenService && InitTorV3()) {
|
||||
onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
|
||||
if (!onionAddr.empty()) {
|
||||
// Write onion/hostname for compatibility with existing code paths
|
||||
fs::path onionDir = GetDataDir() / "onion";
|
||||
@@ -1007,14 +1211,19 @@ bool AppInit2()
|
||||
}
|
||||
|
||||
// Register onion address as local address for peer discovery
|
||||
AddLocal(CService(onionAddr, GetListenPort(), fNameLookup), LOCAL_MANUAL);
|
||||
AddLocal(CService(onionAddr, torConfig.hiddenServicePort, fNameLookup), LOCAL_MANUAL);
|
||||
printf("Tor V3 identity: %s\n", onionAddr.c_str());
|
||||
} else {
|
||||
printf("WARNING: Tor V3 initialized but no onion address available\n");
|
||||
}
|
||||
} else if (torStarted && !torConfig.enableHiddenService) {
|
||||
printf("Tor hidden service disabled by configuration\n");
|
||||
} else if (!torStarted) {
|
||||
printf("Skipping Tor V3 identity because the Tor backend is unavailable\n");
|
||||
} else {
|
||||
printf("WARNING: Failed to initialize Tor V3 identity\n");
|
||||
}
|
||||
StartupPerfLog("tor_v3_identity", GetTimeMillis() - nTorIdentityStart);
|
||||
|
||||
// Also check if Tor gave us a hidden service hostname
|
||||
if (torStarted) {
|
||||
@@ -1027,12 +1236,21 @@ bool AppInit2()
|
||||
while (!torOnion.empty() && (torOnion.back() == '\n' || torOnion.back() == '\r' || torOnion.back() == ' '))
|
||||
torOnion.pop_back();
|
||||
if (!torOnion.empty()) {
|
||||
AddLocal(CService(torOnion, GetListenPort(), fNameLookup), LOCAL_MANUAL);
|
||||
if (torOnion != onionAddr) {
|
||||
AddLocal(CService(torOnion, torConfig.hiddenServicePort, fNameLookup), LOCAL_MANUAL);
|
||||
}
|
||||
printf("Tor hidden service (from Tor process): %s\n", torOnion.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StartupPerfLog("tor_setup_total", GetTimeMillis() - nTorStart);
|
||||
|
||||
// Launch background thread for Tor health monitoring and seeder maintenance
|
||||
if (torStarted) {
|
||||
if (!NewThread(ThreadTorMaintenance, NULL))
|
||||
printf("Warning: ThreadTorMaintenance could not be started\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************* Step 9: import blocks
|
||||
@@ -1041,11 +1259,13 @@ bool AppInit2()
|
||||
{
|
||||
uiInterface.InitMessage(_("Importing blockchain data file."));
|
||||
|
||||
BOOST_FOREACH(string strFile, mapMultiArgs["-loadblock"])
|
||||
for (string strFile : mapMultiArgs["-loadblock"])
|
||||
{
|
||||
int64_t nLoadBlockStart = GetTimeMillis();
|
||||
FILE *file = fopen(strFile.c_str(), "rb");
|
||||
if (file)
|
||||
LoadExternalBlockFile(file);
|
||||
StartupPerfLog("loadblock_import", GetTimeMillis() - nLoadBlockStart, strprintf("file=%s", strFile.c_str()));
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
@@ -1054,12 +1274,14 @@ bool AppInit2()
|
||||
if (fs::exists(pathBootstrap)) {
|
||||
uiInterface.InitMessage(_("Importing bootstrap blockchain data file."));
|
||||
|
||||
int64_t nBootstrapImportStart = GetTimeMillis();
|
||||
FILE *file = fopen(pathBootstrap.string().c_str(), "rb");
|
||||
if (file) {
|
||||
fs::path pathBootstrapOld = GetDataDir() / "bootstrap.dat.old";
|
||||
LoadExternalBlockFile(file);
|
||||
RenameOver(pathBootstrap, pathBootstrapOld);
|
||||
}
|
||||
StartupPerfLog("bootstrap_dat_import", GetTimeMillis() - nBootstrapImportStart, strprintf("file=%s", pathBootstrap.string().c_str()));
|
||||
}
|
||||
|
||||
// ********************************************************* Step 10: load peers
|
||||
@@ -1074,11 +1296,13 @@ bool AppInit2()
|
||||
printf("Invalid or missing peers.dat; recreating\n");
|
||||
}
|
||||
|
||||
printf("Loaded %i addresses from peers.dat %"PRId64"ms\n",
|
||||
printf("Loaded %i addresses from peers.dat %" PRId64 "ms\n",
|
||||
addrman.size(), GetTimeMillis() - nStart);
|
||||
StartupPerfLog("peers_load", GetTimeMillis() - nStart, strprintf("count=%d", addrman.size()));
|
||||
|
||||
|
||||
// ********************************************************* Step 11: start node
|
||||
nStart = GetTimeMillis();
|
||||
|
||||
if (!CheckDiskSpace())
|
||||
return false;
|
||||
@@ -1086,11 +1310,11 @@ bool AppInit2()
|
||||
RandAddSeedPerfmon();
|
||||
|
||||
//// debug print
|
||||
printf("mapBlockIndex.size() = %"PRIszu"\n", mapBlockIndex.size());
|
||||
printf("mapBlockIndex.size() = %" PRIszu "\n", mapBlockIndex.size());
|
||||
printf("nBestHeight = %d\n", nBestHeight);
|
||||
printf("setKeyPool.size() = %"PRIszu"\n", pwalletMain->setKeyPool.size());
|
||||
printf("mapWallet.size() = %"PRIszu"\n", pwalletMain->mapWallet.size());
|
||||
printf("mapAddressBook.size() = %"PRIszu"\n", pwalletMain->mapAddressBook.size());
|
||||
printf("setKeyPool.size() = %" PRIszu "\n", pwalletMain->setKeyPool.size());
|
||||
printf("mapWallet.size() = %" PRIszu "\n", pwalletMain->mapWallet.size());
|
||||
printf("mapAddressBook.size() = %" PRIszu "\n", pwalletMain->mapAddressBook.size());
|
||||
|
||||
if (!NewThread(StartNode, NULL))
|
||||
InitError(_("Error: could not start node"));
|
||||
@@ -1107,6 +1331,7 @@ bool AppInit2()
|
||||
printf("Warning: deferred startup thread could not be started, running inline\n");
|
||||
ThreadDeferredStartup(NULL);
|
||||
}
|
||||
StartupPerfLog("start_services", GetTimeMillis() - nStart);
|
||||
|
||||
// ********************************************************* Step 11.5: ZMQ notifications
|
||||
#ifdef ENABLE_ZMQ
|
||||
@@ -1129,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))
|
||||
{
|
||||
@@ -1145,6 +1365,7 @@ bool AppInit2()
|
||||
|
||||
uiInterface.InitMessage(_("Done loading"));
|
||||
printf("Done loading\n");
|
||||
StartupPerfLog("appinit_total", GetTimeMillis() - nAppInitStart);
|
||||
|
||||
if (!strErrors.str().empty())
|
||||
return InitError(strErrors.str());
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#define TRIANGLES_INIT_H
|
||||
|
||||
#include "wallet.h"
|
||||
#include <tor/anonymize.h>
|
||||
#include "tor_embed_hooks.h"
|
||||
|
||||
extern CWallet* pwalletMain;
|
||||
extern std::string strWalletFileName;
|
||||
|
||||
@@ -260,7 +260,7 @@ void ThreadIRCSeed2(void* parg)
|
||||
if (!fNoListen && GetLocal(addrLocal, &addrIPv4) && nNameRetry<3)
|
||||
strMyName = EncodeAddress(GetLocalAddress(&addrConnect));
|
||||
if (strMyName == "")
|
||||
strMyName = strprintf("x%"PRIu64"", GetRand(1000000000));
|
||||
strMyName = strprintf("x%" PRIu64 "", GetRand(1000000000));
|
||||
|
||||
Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str());
|
||||
Send(hSocket, strprintf("USER %s 8 * : %s\r", strMyName.c_str(), strMyName.c_str()).c_str());
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
#include <stdexcept>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace json_spirit
|
||||
{
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <boost/assign/list_of.hpp>
|
||||
|
||||
#include "kernel.h"
|
||||
#include "txdb.h"
|
||||
|
||||
@@ -19,10 +17,9 @@ extern unsigned int nTargetSpacing;
|
||||
typedef std::map<int, unsigned int> MapModifierCheckpoints;
|
||||
|
||||
// Hard checkpoints of stake modifiers to ensure they are deterministic
|
||||
static std::map<int, unsigned int> mapStakeModifierCheckpoints =
|
||||
boost::assign::map_list_of
|
||||
( 0, 0x000000000e00670b )
|
||||
;
|
||||
static std::map<int, unsigned int> mapStakeModifierCheckpoints = {
|
||||
{ 0, 0x000000000e00670b },
|
||||
};
|
||||
|
||||
// Get time weight
|
||||
int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd)
|
||||
@@ -80,7 +77,7 @@ static bool SelectBlockFromCandidates(vector<pair<int64_t, uint256> >& vSortedBy
|
||||
bool fSelected = false;
|
||||
uint256 hashBest = 0;
|
||||
*pindexSelected = (const CBlockIndex*) 0;
|
||||
BOOST_FOREACH(const PAIRTYPE(int64_t, uint256)& item, vSortedByTimestamp)
|
||||
for (const auto& item : vSortedByTimestamp)
|
||||
{
|
||||
if (!mapBlockIndex.count(item.second))
|
||||
return error("SelectBlockFromCandidates: failed to find block index for candidate block %s", item.second.ToString().c_str());
|
||||
@@ -199,7 +196,7 @@ bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64_t& nStakeMod
|
||||
strSelectionMap.replace(pindex->nHeight - nHeightFirstCandidate, 1, "=");
|
||||
pindex = pindex->pprev;
|
||||
}
|
||||
BOOST_FOREACH(const PAIRTYPE(uint256, const CBlockIndex*)& item, mapSelectedBlocks)
|
||||
for (const auto& item : mapSelectedBlocks)
|
||||
{
|
||||
// 'S' indicates selected proof-of-stake blocks
|
||||
// 'W' indicates selected proof-of-work blocks
|
||||
|
||||
@@ -202,7 +202,7 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
|
||||
return false;
|
||||
|
||||
fUseCrypto = true;
|
||||
BOOST_FOREACH(KeyMap::value_type& mKey, mapKeys)
|
||||
for (KeyMap::value_type& mKey : mapKeys)
|
||||
{
|
||||
CKey key;
|
||||
if (!key.SetSecret(mKey.second.first, mKey.second.second))
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
LZ4 Library
|
||||
Copyright (c) 2011-2014, Yann Collet
|
||||
All rights reserved.
|
||||
This repository uses 2 different licenses :
|
||||
- all files in the `lib` directory use a BSD 2-Clause license
|
||||
- all other files use a GPL-2.0-or-later license, unless explicitly stated otherwise
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
Relevant license is reminded at the top of each source file,
|
||||
and with presence of COPYING or LICENSE file in associated directories.
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
This model is selected to emphasize that
|
||||
files in the `lib` directory are designed to be included into 3rd party applications,
|
||||
while all other files, in `programs`, `tests` or `examples`,
|
||||
are intended to be used "as is", as part of their intended scenarios,
|
||||
with no intention to support 3rd party integration use cases.
|
||||
|
||||
@@ -29,15 +29,18 @@ 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;
|
||||
static const unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50;
|
||||
static const unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100;
|
||||
static const unsigned int MAX_ORPHAN_BLOCKS = 750;
|
||||
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;
|
||||
@@ -54,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;
|
||||
@@ -111,6 +117,7 @@ CBlockIndex* FindBlockByHeight(int nHeight);
|
||||
bool ProcessMessages(CNode* pfrom);
|
||||
bool SendMessages(CNode* pto, bool fSendTrickle);
|
||||
bool LoadExternalBlockFile(FILE* fileIn);
|
||||
bool FastImportBlockFile();
|
||||
|
||||
bool CheckProofOfWork(uint256 hash, unsigned int nBits);
|
||||
unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake);
|
||||
@@ -424,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.
|
||||
@@ -489,7 +540,7 @@ public:
|
||||
nBlockTime = GetAdjustedTime();
|
||||
if ((int64_t)nLockTime < ((int64_t)nLockTime < LOCKTIME_THRESHOLD ? (int64_t)nBlockHeight : nBlockTime))
|
||||
return true;
|
||||
BOOST_FOREACH(const CTxIn& txin, vin)
|
||||
for (const CTxIn& txin : vin)
|
||||
if (!txin.IsFinal())
|
||||
return false;
|
||||
return true;
|
||||
@@ -567,7 +618,7 @@ public:
|
||||
int64_t GetValueOut() const
|
||||
{
|
||||
int64_t nValueOut = 0;
|
||||
BOOST_FOREACH(const CTxOut& txout, vout)
|
||||
for (const CTxOut& txout : vout)
|
||||
{
|
||||
nValueOut += txout.nValue;
|
||||
if (!MoneyRange(txout.nValue) || !MoneyRange(nValueOut))
|
||||
@@ -640,7 +691,7 @@ public:
|
||||
{
|
||||
std::string str;
|
||||
str += IsCoinBase()? "Coinbase" : (IsCoinStake()? "Coinstake" : "CTransaction");
|
||||
str += strprintf("(hash=%s, nTime=%d, ver=%d, vin.size=%"PRIszu", vout.size=%"PRIszu", nLockTime=%d)\n",
|
||||
str += strprintf("(hash=%s, nTime=%d, ver=%d, vin.size=%" PRIszu ", vout.size=%" PRIszu ", nLockTime=%d)\n",
|
||||
GetHash().ToString().substr(0,10).c_str(),
|
||||
nTime,
|
||||
nVersion,
|
||||
@@ -665,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;
|
||||
@@ -764,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
|
||||
{
|
||||
@@ -955,7 +1003,7 @@ public:
|
||||
int64_t GetMaxTransactionTime() const
|
||||
{
|
||||
int64_t maxTransactionTime = 0;
|
||||
BOOST_FOREACH(const CTransaction& tx, vtx)
|
||||
for (const CTransaction& tx : vtx)
|
||||
maxTransactionTime = std::max(maxTransactionTime, (int64_t)tx.nTime);
|
||||
return maxTransactionTime;
|
||||
}
|
||||
@@ -963,7 +1011,7 @@ public:
|
||||
uint256 BuildMerkleTree() const
|
||||
{
|
||||
vMerkleTree.clear();
|
||||
BOOST_FOREACH(const CTransaction& tx, vtx)
|
||||
for (const CTransaction& tx : vtx)
|
||||
vMerkleTree.push_back(tx.GetHash());
|
||||
int j = 0;
|
||||
for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2)
|
||||
@@ -999,7 +1047,7 @@ public:
|
||||
{
|
||||
if (nIndex == -1)
|
||||
return 0;
|
||||
BOOST_FOREACH(const uint256& otherside, vMerkleBranch)
|
||||
for (const uint256& otherside : vMerkleBranch)
|
||||
{
|
||||
if (nIndex & 1)
|
||||
hash = Hash(BEGIN(otherside), END(otherside), BEGIN(hash), END(hash));
|
||||
@@ -1067,7 +1115,7 @@ public:
|
||||
|
||||
void print() const
|
||||
{
|
||||
printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%"PRIszu", vchBlockSig=%s)\n",
|
||||
printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%" PRIszu ", vchBlockSig=%s)\n",
|
||||
GetHash().ToString().c_str(),
|
||||
nVersion,
|
||||
hashPrevBlock.ToString().c_str(),
|
||||
@@ -1328,7 +1376,7 @@ public:
|
||||
|
||||
std::string ToString() const
|
||||
{
|
||||
return strprintf("CBlockIndex(nprev=%p, pnext=%p, nFile=%u, nBlockPos=%-6d nHeight=%d, nMint=%s, nMoneySupply=%s, nFlags=(%s)(%d)(%s), nStakeModifier=%016"PRIx64", nStakeModifierChecksum=%08x, hashProofOfStake=%s, prevoutStake=(%s), nStakeTime=%d merkle=%s, hashBlock=%s)",
|
||||
return strprintf("CBlockIndex(nprev=%p, pnext=%p, nFile=%u, nBlockPos=%-6d nHeight=%d, nMint=%s, nMoneySupply=%s, nFlags=(%s)(%d)(%s), nStakeModifier=%016" PRIx64 ", nStakeModifierChecksum=%08x, hashProofOfStake=%s, prevoutStake=(%s), nStakeTime=%d merkle=%s, hashBlock=%s)",
|
||||
pprev, pnext, nFile, nBlockPos, nHeight,
|
||||
FormatMoney(nMint).c_str(), FormatMoney(nMoneySupply).c_str(),
|
||||
GeneratedStakeModifier() ? "MOD" : "-", GetStakeEntropyBit(), IsProofOfStake()? "PoS" : "PoW",
|
||||
@@ -1357,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;
|
||||
@@ -1404,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
|
||||
@@ -1518,7 +1574,7 @@ public:
|
||||
// Retrace how far back it was in the sender's branch
|
||||
int nDistance = 0;
|
||||
int nStep = 1;
|
||||
BOOST_FOREACH(const uint256& hash, vHave)
|
||||
for (const uint256& hash : vHave)
|
||||
{
|
||||
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
|
||||
if (mi != mapBlockIndex.end())
|
||||
@@ -1537,7 +1593,7 @@ public:
|
||||
CBlockIndex* GetBlockIndex()
|
||||
{
|
||||
// Find the first block the caller has in the main chain
|
||||
BOOST_FOREACH(const uint256& hash, vHave)
|
||||
for (const uint256& hash : vHave)
|
||||
{
|
||||
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
|
||||
if (mi != mapBlockIndex.end())
|
||||
@@ -1553,7 +1609,7 @@ public:
|
||||
uint256 GetBlockHash()
|
||||
{
|
||||
// Find the first block the caller has in the main chain
|
||||
BOOST_FOREACH(const uint256& hash, vHave)
|
||||
for (const uint256& hash : vHave)
|
||||
{
|
||||
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
|
||||
if (mi != mapBlockIndex.end())
|
||||
|
||||
@@ -74,7 +74,7 @@ HARDENING+=-D_FORTIFY_SOURCE=2
|
||||
|
||||
DEBUGFLAGS=-g
|
||||
|
||||
xCXXFLAGS=-O2 -pthread -Wall -Wextra -Wno-ignored-qualifiers -Wformat -Wformat-security -Wno-unused-parameter \
|
||||
xCXXFLAGS=-O2 -std=c++17 -pthread -Wall -Wextra -Wno-ignored-qualifiers -Wformat -Wformat-security -Wno-unused-parameter \
|
||||
-Wno-deprecated-declarations -Wno-reserved-user-defined-literal \
|
||||
-Wa,-mbig-obj \
|
||||
$(DEBUGFLAGS) $(DEFS) $(HARDENING) $(CXXFLAGS)
|
||||
@@ -111,6 +111,7 @@ OBJS= \
|
||||
obj/miner.o \
|
||||
obj/main.o \
|
||||
obj/net.o \
|
||||
obj/bootstrap.o \
|
||||
obj/net_bootstrap.o \
|
||||
obj/protocol.o \
|
||||
obj/trianglesrpc.o \
|
||||
@@ -135,8 +136,11 @@ OBJS= \
|
||||
obj/scrypt-x86.o \
|
||||
obj/scrypt-x86_64.o \
|
||||
obj/smessage.o \
|
||||
obj/lz4.o \
|
||||
obj/onion_v3.o \
|
||||
obj/tor_process.o
|
||||
obj/tor_process.o \
|
||||
obj/tor_embed_hooks.o \
|
||||
obj/tor_embedded.o
|
||||
|
||||
all: trianglesd.exe
|
||||
|
||||
@@ -208,6 +212,34 @@ obj/tor_process.o: tor/tor_process.cpp
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/lz4.o: lz4/lz4.c
|
||||
$(CXX) -c $(xCXXFLAGS) -fpermissive -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/tor_embed_hooks.o: tor_embed_hooks.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/tor_embedded.o: tor/tor_embedded.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/bootstrap.o: bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/net_bootstrap.o: net_bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
|
||||
@@ -6,7 +6,7 @@ USE_UPNP:=0
|
||||
USE_IPV6:=1
|
||||
|
||||
LINK:=$(CXX)
|
||||
ARCH:=$(system lscpu | head -n 1 | awk '{print $2}')
|
||||
ARCH:=$(shell lscpu | head -n 1 | awk '{print $$2}')
|
||||
|
||||
DEFS=-DBOOST_SPIRIT_THREADSAFE
|
||||
|
||||
@@ -41,7 +41,6 @@ LIBS += \
|
||||
-l boost_chrono$(BOOST_LIB_SUFFIX) \
|
||||
-l db_cxx$(BDB_LIB_SUFFIX) \
|
||||
-l ssl \
|
||||
-l event \
|
||||
-l crypto
|
||||
|
||||
ifndef USE_UPNP
|
||||
@@ -107,7 +106,7 @@ endif
|
||||
|
||||
# CXXFLAGS can be specified on the make command line, so we use xCXXFLAGS that only
|
||||
# adds some defaults in front. Unfortunately, CXXFLAGS=... $(CXXFLAGS) does not work.
|
||||
xCXXFLAGS=-O2 $(EXT_OPTIONS) -pthread -Wall -Wextra -Wno-ignored-qualifiers -Wformat -Wformat-security -Wno-unused-parameter \
|
||||
xCXXFLAGS=-O2 -std=c++17 $(EXT_OPTIONS) -pthread -Wall -Wextra -Wno-ignored-qualifiers -Wformat -Wformat-security -Wno-unused-parameter \
|
||||
$(DEBUGFLAGS) $(DEFS) $(HARDENING) $(CXXFLAGS)
|
||||
|
||||
# LDFLAGS can be specified on the make command line, so we use xLDFLAGS that only
|
||||
@@ -146,6 +145,7 @@ OBJS= \
|
||||
obj/miner.o \
|
||||
obj/main.o \
|
||||
obj/net.o \
|
||||
obj/bootstrap.o \
|
||||
obj/net_bootstrap.o \
|
||||
obj/protocol.o \
|
||||
obj/trianglesrpc.o \
|
||||
@@ -170,8 +170,26 @@ OBJS= \
|
||||
obj/scrypt-x86.o \
|
||||
obj/scrypt-x86_64.o \
|
||||
obj/smessage.o \
|
||||
obj/lz4.o \
|
||||
obj/onion_v3.o \
|
||||
obj/tor_process.o
|
||||
obj/tor_process.o \
|
||||
obj/tor_embed_hooks.o \
|
||||
obj/tor_embedded.o
|
||||
|
||||
# Embedded Tor support (optional)
|
||||
# Build with: make -f makefile.unix USE_TOR_EMBEDDED=1 TOR_SOURCE_ROOT=/path/to/tor-src
|
||||
# Requires libtor.a built from official Tor source (see CODEX-TOR-GUIDE.md)
|
||||
TOR_SOURCE_ROOT ?= tor/tor-src
|
||||
TOR_INCLUDE_PATH ?= $(TOR_SOURCE_ROOT)/src/feature/api
|
||||
TOR_LIB_PATH ?= $(TOR_SOURCE_ROOT)
|
||||
TOR_EMBEDDED_LIBS ?= -ltor
|
||||
ifdef USE_TOR_EMBEDDED
|
||||
DEFS += -DENABLE_TOR_EMBEDDED
|
||||
DEFS += $(addprefix -I,$(TOR_INCLUDE_PATH))
|
||||
LIBS += $(addprefix -L,$(TOR_LIB_PATH))
|
||||
LIBS += -Wl,--start-group $(TOR_EMBEDDED_LIBS) -Wl,--end-group
|
||||
LIBS += -llzma -lzstd -lm -lpthread
|
||||
endif
|
||||
|
||||
# ZMQ support (optional)
|
||||
# Build with: make -f makefile.unix USE_ZMQ=1
|
||||
@@ -183,6 +201,12 @@ endif
|
||||
|
||||
all: trianglesd
|
||||
|
||||
obj:
|
||||
@mkdir -p obj
|
||||
|
||||
obj-test:
|
||||
@mkdir -p obj-test
|
||||
|
||||
test check: test_triangles FORCE
|
||||
./test_triangles
|
||||
|
||||
@@ -212,28 +236,28 @@ obj/scrypt-x86_64.o: scrypt-x86_64.S
|
||||
obj/scrypt-arm.o: scrypt-arm.S
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -o $@ $<
|
||||
|
||||
obj/%.o: %.cpp
|
||||
obj/%.o: %.cpp | obj
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/%.o: %.c
|
||||
obj/%.o: %.c | obj
|
||||
$(CXX) -c $(xCXXFLAGS) -fpermissive -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/%.o: tor/%.c
|
||||
$(CC) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
obj/lz4.o: lz4/lz4.c
|
||||
$(CXX) -c $(xCXXFLAGS) -fpermissive -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//'\
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/anonymize.o: tor/anonymize.cpp
|
||||
obj/tor_embed_hooks.o: tor_embed_hooks.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
@@ -254,6 +278,20 @@ obj/tor_process.o: tor/tor_process.cpp
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/tor_embedded.o: tor/tor_embedded.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/bootstrap.o: bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/net_bootstrap.o: net_bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
@@ -261,12 +299,14 @@ obj/net_bootstrap.o: net_bootstrap.cpp
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
trianglesd: $(OBJS:obj/%=obj/%) obj/anonymize.o
|
||||
trianglesd: $(OBJS:obj/%=obj/%)
|
||||
$(LINK) $(xCXXFLAGS) -o $@ $^ $(xLDFLAGS) $(LIBS)
|
||||
|
||||
TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp))
|
||||
# miner_tests.cpp references CreateNewBlock() which was never ported from Bitcoin
|
||||
TESTOBJS := $(filter-out obj-test/miner_tests.o, \
|
||||
$(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)))
|
||||
|
||||
obj-test/%.o: test/%.cpp
|
||||
obj-test/%.o: test/%.cpp | obj-test
|
||||
$(CXX) -c $(TESTDEFS) $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
|
||||
@@ -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
|
||||
@@ -72,7 +39,7 @@ public:
|
||||
{
|
||||
printf("COrphan(hash=%s, dPriority=%.1f, dFeePerKb=%.1f)\n",
|
||||
ptx->GetHash().ToString().substr(0,10).c_str(), dPriority, dFeePerKb);
|
||||
BOOST_FOREACH(uint256 hash, setDependsOn)
|
||||
for (uint256 hash : setDependsOn)
|
||||
printf(" setDependsOn %s\n", hash.ToString().substr(0,10).c_str());
|
||||
}
|
||||
};
|
||||
@@ -83,7 +50,7 @@ uint64_t nLastBlockSize = 0;
|
||||
int64_t nLastCoinStakeSearchInterval = 0;
|
||||
|
||||
// We want to sort transactions by priority and fee, so:
|
||||
typedef boost::tuple<double, double, CTransaction*> TxPriority;
|
||||
typedef std::tuple<double, double, CTransaction*> TxPriority;
|
||||
class TxPriorityCompare
|
||||
{
|
||||
bool byFee;
|
||||
@@ -93,15 +60,15 @@ public:
|
||||
{
|
||||
if (byFee)
|
||||
{
|
||||
if (a.get<1>() == b.get<1>())
|
||||
return a.get<0>() < b.get<0>();
|
||||
return a.get<1>() < b.get<1>();
|
||||
if (std::get<1>(a) == std::get<1>(b))
|
||||
return std::get<0>(a) < std::get<0>(b);
|
||||
return std::get<1>(a) < std::get<1>(b);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a.get<0>() == b.get<0>())
|
||||
return a.get<1>() < b.get<1>();
|
||||
return a.get<0>() < b.get<0>();
|
||||
if (std::get<0>(a) == std::get<0>(b))
|
||||
return std::get<1>(a) < std::get<1>(b);
|
||||
return std::get<0>(a) < std::get<0>(b);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -110,7 +77,7 @@ public:
|
||||
CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
{
|
||||
// Create new block
|
||||
auto_ptr<CBlock> pblock(new CBlock());
|
||||
unique_ptr<CBlock> pblock(new CBlock());
|
||||
if (!pblock.get())
|
||||
return NULL;
|
||||
|
||||
@@ -188,7 +155,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
double dPriority = 0;
|
||||
int64_t nTotalIn = 0;
|
||||
bool fMissingInputs = false;
|
||||
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
||||
for (const CTxIn& txin : tx.vin)
|
||||
{
|
||||
// Read prev transaction
|
||||
CTransaction txPrev;
|
||||
@@ -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;
|
||||
@@ -259,9 +225,9 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
while (!vecPriority.empty())
|
||||
{
|
||||
// Take highest priority transaction off the priority queue:
|
||||
double dPriority = vecPriority.front().get<0>();
|
||||
double dFeePerKb = vecPriority.front().get<1>();
|
||||
CTransaction& tx = *(vecPriority.front().get<2>());
|
||||
double dPriority = std::get<0>(vecPriority.front());
|
||||
double dFeePerKb = std::get<1>(vecPriority.front());
|
||||
CTransaction& tx = *(std::get<2>(vecPriority.front()));
|
||||
|
||||
std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
||||
vecPriority.pop_back();
|
||||
@@ -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);
|
||||
@@ -335,7 +299,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
uint256 hash = tx.GetHash();
|
||||
if (mapDependers.count(hash))
|
||||
{
|
||||
BOOST_FOREACH(COrphan* porphan, mapDependers[hash])
|
||||
for (COrphan* porphan : mapDependers[hash])
|
||||
{
|
||||
if (!porphan->setDependsOn.empty())
|
||||
{
|
||||
@@ -354,7 +318,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
nLastBlockSize = nBlockSize;
|
||||
|
||||
if (fDebug && GetBoolArg("-printpriority"))
|
||||
printf("CreateNewBlock(): total size %"PRIu64"\n", nBlockSize);
|
||||
printf("CreateNewBlock(): total size %" PRIu64 "\n", nBlockSize);
|
||||
|
||||
if (!fProofOfStake)
|
||||
pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(nFees);
|
||||
@@ -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;
|
||||
@@ -551,20 +411,33 @@ void StakeMiner(CWallet *pwallet)
|
||||
if (fTryToSync)
|
||||
{
|
||||
fTryToSync = false;
|
||||
if (vNodes.size() < 3 || nBestHeight < GetNumBlocksOfPeers())
|
||||
if (vNodes.size() < 1 || nBestHeight < GetNumBlocksOfPeers())
|
||||
{
|
||||
MilliSleep(60000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Update cached stake weight for UI display (avoids heavy work on UI thread)
|
||||
//
|
||||
{
|
||||
uint64_t nMinWeight = 0, nMaxWeight = 0, nWeight = 0;
|
||||
pwallet->GetStakeWeight(*pwallet, nMinWeight, nMaxWeight, nWeight);
|
||||
pwallet->nCachedStakeWeight = nWeight;
|
||||
pwallet->nCachedStakeWeightTime = GetTime();
|
||||
}
|
||||
|
||||
//
|
||||
// Create new block
|
||||
//
|
||||
int64_t nFees;
|
||||
auto_ptr<CBlock> pblock(CreateNewBlock(pwallet, true, &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,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
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -213,7 +218,7 @@ bool RecvLine(SOCKET hSocket, string& strLine)
|
||||
void static AdvertizeLocal()
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
if (pnode->fSuccessfullyConnected)
|
||||
{
|
||||
@@ -458,7 +463,7 @@ CNode* FindNode(const CNetAddr& ip)
|
||||
{
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
if ((CNetAddr)pnode->addr == ip)
|
||||
return (pnode);
|
||||
}
|
||||
@@ -468,7 +473,7 @@ CNode* FindNode(const CNetAddr& ip)
|
||||
CNode* FindNode(std::string addrName)
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
if (pnode->addrName == addrName)
|
||||
return (pnode);
|
||||
return NULL;
|
||||
@@ -478,7 +483,7 @@ CNode* FindNode(const CService& addr)
|
||||
{
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
if ((CService)pnode->addr == addr)
|
||||
return (pnode);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -823,7 +836,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
LOCK(cs_vNodes);
|
||||
// Disconnect unused nodes
|
||||
vector<CNode*> vNodesCopy = vNodes;
|
||||
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
{
|
||||
if (pnode->fDisconnect ||
|
||||
(pnode->GetRefCount() <= 0 && pnode->vRecvMsg.empty() && pnode->nSendSize == 0 && pnode->ssSend.empty()))
|
||||
@@ -847,7 +860,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
|
||||
// Delete disconnected nodes
|
||||
list<CNode*> vNodesDisconnectedCopy = vNodesDisconnected;
|
||||
BOOST_FOREACH(CNode* pnode, vNodesDisconnectedCopy)
|
||||
for (CNode* pnode : vNodesDisconnectedCopy)
|
||||
{
|
||||
// wait until threads are done using it
|
||||
if (pnode->GetRefCount() <= 0)
|
||||
@@ -878,10 +891,19 @@ void ThreadSocketHandler2(void* parg)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vNodes.size() != nPrevNodeCount)
|
||||
{
|
||||
nPrevNodeCount = vNodes.size();
|
||||
uiInterface.NotifyNumConnectionsChanged(vNodes.size());
|
||||
// Read vNodes.size() under the lock to avoid data race
|
||||
unsigned int nNodeCount;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
nNodeCount = vNodes.size();
|
||||
}
|
||||
if (nNodeCount != nPrevNodeCount)
|
||||
{
|
||||
nPrevNodeCount = nNodeCount;
|
||||
if (!fShutdown)
|
||||
uiInterface.NotifyNumConnectionsChanged(nNodeCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -890,7 +912,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
//
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = IsInitialBlockDownload() ? 10000 : 50000; // faster polling during IBD
|
||||
timeout.tv_usec = IsInitialBlockDownload() ? 1000 : 50000; // 1ms during IBD, 50ms normal
|
||||
|
||||
fd_set fdsetRecv;
|
||||
fd_set fdsetSend;
|
||||
@@ -901,14 +923,14 @@ void ThreadSocketHandler2(void* parg)
|
||||
SOCKET hSocketMax = 0;
|
||||
bool have_fds = false;
|
||||
|
||||
BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) {
|
||||
for (SOCKET hListenSocket : vhListenSocket) {
|
||||
FD_SET(hListenSocket, &fdsetRecv);
|
||||
hSocketMax = max(hSocketMax, hListenSocket);
|
||||
have_fds = true;
|
||||
}
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
if (pnode->hSocket == INVALID_SOCKET)
|
||||
continue;
|
||||
@@ -948,7 +970,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
//
|
||||
// Accept new connections
|
||||
//
|
||||
BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket)
|
||||
for (SOCKET hListenSocket : vhListenSocket)
|
||||
if (hListenSocket != INVALID_SOCKET && FD_ISSET(hListenSocket, &fdsetRecv))
|
||||
{
|
||||
#ifdef USE_IPV6
|
||||
@@ -967,7 +989,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
if (pnode->fInbound)
|
||||
nInbound++;
|
||||
}
|
||||
@@ -1007,13 +1029,13 @@ void ThreadSocketHandler2(void* parg)
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
vNodesCopy = vNodes;
|
||||
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
pnode->AddRef();
|
||||
}
|
||||
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
{
|
||||
if (fShutdown)
|
||||
return;
|
||||
break;
|
||||
|
||||
//
|
||||
// Receive
|
||||
@@ -1039,7 +1061,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
|
||||
pnode->CloseSocketDisconnect();
|
||||
pnode->nLastRecv = GetTime();
|
||||
pnode->nRecvBytes += nBytes;
|
||||
pnode->nRecvBytes += nBytes;
|
||||
}
|
||||
else if (nBytes == 0)
|
||||
{
|
||||
@@ -1100,10 +1122,12 @@ void ThreadSocketHandler2(void* parg)
|
||||
}
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
pnode->Release();
|
||||
}
|
||||
|
||||
if (fShutdown)
|
||||
return;
|
||||
MilliSleep(10);
|
||||
}
|
||||
}
|
||||
@@ -1328,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");
|
||||
}
|
||||
|
||||
|
||||
@@ -1375,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()
|
||||
@@ -1419,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))
|
||||
{
|
||||
BOOST_FOREACH(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)
|
||||
@@ -1515,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]);
|
||||
}
|
||||
@@ -1541,7 +1737,7 @@ void ThreadOpenConnections2(void* parg)
|
||||
for (int64_t nLoop = 0;; nLoop++)
|
||||
{
|
||||
ProcessOneShot();
|
||||
BOOST_FOREACH(string strAddr, mapMultiArgs["-connect"])
|
||||
for (string strAddr : mapMultiArgs["-connect"])
|
||||
{
|
||||
CAddress addr;
|
||||
OpenNetworkConnection(addr, NULL, strAddr.c_str());
|
||||
@@ -1575,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;
|
||||
BOOST_FOREACH(CNode* pnode, vNodes) {
|
||||
if (!pnode->fInbound) { fNoOutbound = false; break; }
|
||||
}
|
||||
if (fNoOutbound && (GetTime() - nStart > 30) && !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 30s, 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
|
||||
@@ -1611,7 +1785,7 @@ void ThreadOpenConnections2(void* parg)
|
||||
set<vector<unsigned char> > setConnected;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes) {
|
||||
for (CNode* pnode : vNodes) {
|
||||
if (!pnode->fInbound) {
|
||||
setConnected.insert(pnode->addr.GetGroup());
|
||||
nOutbound++;
|
||||
@@ -1642,7 +1816,7 @@ void ThreadOpenConnections2(void* parg)
|
||||
continue;
|
||||
|
||||
// only consider very recently tried nodes after 30 failed attempts
|
||||
if (nANow - addr.nLastTry < 600 && nTries < 30)
|
||||
if (nANow - addr.nLastTry < 120 && nTries < 30)
|
||||
continue;
|
||||
|
||||
// do not allow non-default ports, unless after 50 invalid addresses selected already
|
||||
@@ -1688,7 +1862,7 @@ void ThreadOpenAddedConnections2(void* parg)
|
||||
|
||||
if (HaveNameProxy()) {
|
||||
while(!fShutdown) {
|
||||
BOOST_FOREACH(string& strAddNode, mapMultiArgs["-addnode"]) {
|
||||
for (string& strAddNode : mapMultiArgs["-addnode"]) {
|
||||
CAddress addr;
|
||||
CSemaphoreGrant grant(*semOutbound);
|
||||
OpenNetworkConnection(addr, &grant, strAddNode.c_str());
|
||||
@@ -1702,7 +1876,7 @@ void ThreadOpenAddedConnections2(void* parg)
|
||||
}
|
||||
|
||||
vector<vector<CService> > vservAddressesToAdd(0);
|
||||
BOOST_FOREACH(string& strAddNode, mapMultiArgs["-addnode"])
|
||||
for (string& strAddNode : mapMultiArgs["-addnode"])
|
||||
{
|
||||
vector<CService> vservNode(0);
|
||||
if(Lookup(strAddNode.c_str(), vservNode, GetDefaultPort(), fNameLookup, 0))
|
||||
@@ -1710,7 +1884,7 @@ void ThreadOpenAddedConnections2(void* parg)
|
||||
vservAddressesToAdd.push_back(vservNode);
|
||||
{
|
||||
LOCK(cs_setservAddNodeAddresses);
|
||||
BOOST_FOREACH(CService& serv, vservNode)
|
||||
for (CService& serv : vservNode)
|
||||
setservAddNodeAddresses.insert(serv);
|
||||
}
|
||||
}
|
||||
@@ -1722,9 +1896,9 @@ void ThreadOpenAddedConnections2(void* parg)
|
||||
// (keeping in mind that addnode entries can have many IPs if fNameLookup)
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
for (vector<vector<CService> >::iterator it = vservConnectAddresses.begin(); it != vservConnectAddresses.end(); it++)
|
||||
BOOST_FOREACH(CService& addrNode, *(it))
|
||||
for (CService& addrNode : *(it))
|
||||
if (pnode->addr == addrNode)
|
||||
{
|
||||
it = vservConnectAddresses.erase(it);
|
||||
@@ -1732,7 +1906,7 @@ void ThreadOpenAddedConnections2(void* parg)
|
||||
break;
|
||||
}
|
||||
}
|
||||
BOOST_FOREACH(vector<CService>& vserv, vservConnectAddresses)
|
||||
for (vector<CService>& vserv : vservConnectAddresses)
|
||||
{
|
||||
CSemaphoreGrant grant(*semOutbound);
|
||||
OpenNetworkConnection(CAddress(*(vserv.begin())), &grant);
|
||||
@@ -1822,7 +1996,7 @@ void ThreadMessageHandler2(void* parg)
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
vNodesCopy = vNodes;
|
||||
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
pnode->AddRef();
|
||||
}
|
||||
|
||||
@@ -1830,8 +2004,11 @@ void ThreadMessageHandler2(void* parg)
|
||||
CNode* pnodeTrickle = NULL;
|
||||
if (!vNodesCopy.empty())
|
||||
pnodeTrickle = vNodesCopy[GetRand(vNodesCopy.size())];
|
||||
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
{
|
||||
if (fShutdown)
|
||||
break;
|
||||
|
||||
// Receive messages
|
||||
{
|
||||
TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
|
||||
@@ -1839,8 +2016,6 @@ void ThreadMessageHandler2(void* parg)
|
||||
if (!ProcessMessages(pnode))
|
||||
pnode->CloseSocketDisconnect();
|
||||
}
|
||||
if (fShutdown)
|
||||
return;
|
||||
|
||||
// Send messages
|
||||
{
|
||||
@@ -1848,13 +2023,11 @@ void ThreadMessageHandler2(void* parg)
|
||||
if (lockSend)
|
||||
SendMessages(pnode, pnode == pnodeTrickle);
|
||||
}
|
||||
if (fShutdown)
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
pnode->Release();
|
||||
}
|
||||
|
||||
@@ -1872,7 +2045,7 @@ void ThreadMessageHandler2(void* parg)
|
||||
// Reduce vnThreadsRunning so StopNode has permission to exit while
|
||||
// we're sleeping, but we must always check fShutdown after doing this.
|
||||
vnThreadsRunning[THREAD_MESSAGEHANDLER]--;
|
||||
MilliSleep(IsInitialBlockDownload() ? 10 : 100);
|
||||
MilliSleep(IsInitialBlockDownload() ? 1 : 100);
|
||||
if (fRequestShutdown)
|
||||
StartShutdown();
|
||||
vnThreadsRunning[THREAD_MESSAGEHANDLER]++;
|
||||
@@ -2027,7 +2200,7 @@ static void run_tor() {
|
||||
// which starts an external Tor binary with SOCKS5 proxy and v3 hidden service.
|
||||
// The old embedded Tor v2 code was removed (incompatible with OpenSSL 3.x).
|
||||
printf("Tor v3 mode: using managed Tor process via SOCKS5 proxy.\n");
|
||||
set_initialized();
|
||||
triangles_tor_set_initialized();
|
||||
}
|
||||
|
||||
|
||||
@@ -2102,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))
|
||||
@@ -2162,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");
|
||||
@@ -2180,6 +2358,18 @@ bool StopNode()
|
||||
}
|
||||
MilliSleep(50);
|
||||
DumpAddresses();
|
||||
|
||||
// Force-disconnect and clean up all remaining nodes now that threads have stopped.
|
||||
// Close sockets first so any lingering I/O fails immediately.
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
pnode->CloseSocketDisconnect();
|
||||
pnode->Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2191,11 +2381,12 @@ public:
|
||||
}
|
||||
~CNetCleanup()
|
||||
{
|
||||
// Close sockets
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
// Close sockets - acquire lock in case other threads are still winding down
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
if (pnode->hSocket != INVALID_SOCKET)
|
||||
closesocket(pnode->hSocket);
|
||||
BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket)
|
||||
for (SOCKET hListenSocket : vhListenSocket)
|
||||
if (hListenSocket != INVALID_SOCKET)
|
||||
if (closesocket(hListenSocket) == SOCKET_ERROR)
|
||||
printf("closesocket(hListenSocket) failed with error %d\n", WSAGetLastError());
|
||||
@@ -2235,4 +2426,3 @@ void RelayTransaction(const CTransaction& tx, const uint256& hash, const CDataSt
|
||||
|
||||
RelayInventory(inv);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include <deque>
|
||||
#include <boost/array.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#ifndef WIN32
|
||||
@@ -27,8 +26,8 @@ extern int nBestHeight;
|
||||
|
||||
|
||||
|
||||
inline unsigned int ReceiveFloodSize() { return (unsigned int)-1; }
|
||||
inline unsigned int SendBufferSize() { return (unsigned int)-1; }
|
||||
inline unsigned int ReceiveFloodSize() { return 100 * 1024 * 1024; } // 100 MB
|
||||
inline unsigned int SendBufferSize() { return 32 * 1024 * 1024; } // 32 MB
|
||||
|
||||
void AddOneShot(std::string strDest);
|
||||
bool RecvLine(SOCKET hSocket, std::string& strLine);
|
||||
@@ -104,7 +103,7 @@ enum threadId
|
||||
THREAD_MESSAGEHANDLER,
|
||||
THREAD_RPCLISTENER,
|
||||
THREAD_UPNP,
|
||||
THREAD_DNSSEED,
|
||||
THREAD_HTTPSEED,
|
||||
THREAD_ADDEDCONNECTIONS,
|
||||
THREAD_DUMPADDRESS,
|
||||
THREAD_RPCHANDLER,
|
||||
@@ -325,8 +324,10 @@ public:
|
||||
|
||||
// Be shy and don't send version until we hear
|
||||
if (hSocket != INVALID_SOCKET && !fInbound)
|
||||
{
|
||||
printf("CNode(): pfrom-addr %s\n", addrName.c_str());
|
||||
PushVersion();
|
||||
}
|
||||
}
|
||||
|
||||
~CNode()
|
||||
@@ -354,7 +355,7 @@ public:
|
||||
unsigned int GetTotalRecvSize()
|
||||
{
|
||||
unsigned int total = 0;
|
||||
BOOST_FOREACH(const CNetMessage &msg, vRecvMsg)
|
||||
for (const CNetMessage &msg : vRecvMsg)
|
||||
total += msg.vRecv.size() + 24;
|
||||
return total;
|
||||
}
|
||||
@@ -366,7 +367,7 @@ public:
|
||||
void SetRecvVersion(int nVersionIn)
|
||||
{
|
||||
nRecvVersion = nVersionIn;
|
||||
BOOST_FOREACH(CNetMessage &msg, vRecvMsg)
|
||||
for (CNetMessage &msg : vRecvMsg)
|
||||
msg.SetVersion(nVersionIn);
|
||||
}
|
||||
|
||||
@@ -421,7 +422,7 @@ public:
|
||||
// the key is the earliest time the request can be sent
|
||||
int64_t& nRequestTime = mapAlreadyAskedFor[inv];
|
||||
if (fDebugNet)
|
||||
printf("askfor %s %"PRId64" (%s)\n", inv.ToString().c_str(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str());
|
||||
printf("askfor %s %" PRId64 " (%s)\n", inv.ToString().c_str(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str());
|
||||
|
||||
// Make sure not to reuse time indexes to keep things in the same order
|
||||
int64_t nNow = (GetTime() - 1) * 1000000;
|
||||
@@ -740,7 +741,7 @@ inline void RelayInventory(const CInv& inv)
|
||||
// Put on lists to offer to the other nodes
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
for (CNode* pnode : vNodes)
|
||||
pnode->PushInventory(inv);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef TRIANGLES_OPENSSL_COMPAT_H
|
||||
#define TRIANGLES_OPENSSL_COMPAT_H
|
||||
|
||||
#include <openssl/opensslv.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
static inline const char* TrianglesOpenSSLVersionString()
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||
return OpenSSL_version(OPENSSL_VERSION);
|
||||
#else
|
||||
return SSLeay_version(SSLEAY_VERSION);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // TRIANGLES_OPENSSL_COMPAT_H
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "aboutdialog.h"
|
||||
#include "ui_aboutdialog.h"
|
||||
|
||||
#include <QDesktopWidget>
|
||||
|
||||
#include "clientmodel.h"
|
||||
#include "dialog_move_handler.h"
|
||||
#include "version.h"
|
||||
|
||||
@@ -27,7 +27,10 @@ AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) :
|
||||
tab(tab)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Window);
|
||||
if (mode == ForEditing && parent)
|
||||
setWindowFlags(Qt::Widget);
|
||||
else
|
||||
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Window);
|
||||
ui->wAddressBookHeader->installEventFilter(new DialogMoveHandler(this));
|
||||
|
||||
#ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
cachedAddressTable.clear();
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
BOOST_FOREACH(const PAIRTYPE(CTxDestination, std::string)& item, wallet->mapAddressBook)
|
||||
for (const auto& item : wallet->mapAddressBook)
|
||||
{
|
||||
const CTrianglesAddress& address = item.first;
|
||||
const std::string& strName = item.second;
|
||||
|
||||
@@ -99,11 +99,9 @@ void AskPassphraseDialog::accept()
|
||||
oldpass.reserve(MAX_PASSPHRASE_SIZE);
|
||||
newpass1.reserve(MAX_PASSPHRASE_SIZE);
|
||||
newpass2.reserve(MAX_PASSPHRASE_SIZE);
|
||||
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
|
||||
// Alternately, find a way to make this input mlock()'d to begin with.
|
||||
oldpass.assign(ui->passEdit1->text().toStdString().c_str());
|
||||
newpass1.assign(ui->passEdit2->text().toStdString().c_str());
|
||||
newpass2.assign(ui->passEdit3->text().toStdString().c_str());
|
||||
oldpass = MakeSecureString(ui->passEdit1->text().toStdString());
|
||||
newpass1 = MakeSecureString(ui->passEdit2->text().toStdString());
|
||||
newpass2 = MakeSecureString(ui->passEdit3->text().toStdString());
|
||||
|
||||
switch(mode)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ static const int64_t nClientStartupTime = GetTime();
|
||||
|
||||
ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) :
|
||||
QObject(parent), optionsModel(optionsModel),
|
||||
cachedNumBlocks(0), cachedNumBlocksOfPeers(0), pollTimer(0)
|
||||
cachedNumBlocks(0), cachedNumBlocksOfPeers(0), cachedNumConnections(0), pollTimer(0)
|
||||
{
|
||||
numBlocksAtStartup = -1;
|
||||
|
||||
@@ -34,7 +34,14 @@ ClientModel::~ClientModel()
|
||||
|
||||
int ClientModel::getNumConnections() const
|
||||
{
|
||||
return vNodes.size();
|
||||
// Use TRY_LOCK to avoid blocking the UI thread when the network
|
||||
// thread holds cs_vNodes (e.g. during DNS resolution or connections).
|
||||
// Return the cached value if the lock is busy.
|
||||
TRY_LOCK(cs_vNodes, lockNodes);
|
||||
if (lockNodes) {
|
||||
cachedNumConnections = vNodes.size();
|
||||
}
|
||||
return cachedNumConnections;
|
||||
}
|
||||
|
||||
int ClientModel::getNumBlocks() const
|
||||
@@ -63,12 +70,20 @@ void ClientModel::updateTimer()
|
||||
int newNumBlocks = getNumBlocks();
|
||||
int newNumBlocksOfPeers = getNumBlocksOfPeers();
|
||||
|
||||
// Always emit during IBD so the speed/ETA display stays live
|
||||
if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers
|
||||
|| newNumBlocks < newNumBlocksOfPeers)
|
||||
// Always emit when values change or during IBD.
|
||||
// Also emit every ~30 seconds even when idle so setNumBlocks() can
|
||||
// re-evaluate sync status (e.g. when a new block arrives after a long gap).
|
||||
static int64_t nLastEmit = 0;
|
||||
int64_t nNow = GetTime();
|
||||
bool fChanged = (cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers);
|
||||
bool fCatchingUp = (newNumBlocks < newNumBlocksOfPeers);
|
||||
bool fPeriodicRefresh = (nNow - nLastEmit >= 30);
|
||||
|
||||
if(fChanged || fCatchingUp || fPeriodicRefresh)
|
||||
{
|
||||
cachedNumBlocks = newNumBlocks;
|
||||
cachedNumBlocksOfPeers = newNumBlocksOfPeers;
|
||||
nLastEmit = nNow;
|
||||
|
||||
emit numBlocksChanged(newNumBlocks, newNumBlocksOfPeers);
|
||||
}
|
||||
@@ -178,13 +193,16 @@ static void NotifyBlocksChanged(ClientModel *clientmodel)
|
||||
|
||||
static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections)
|
||||
{
|
||||
// Too noisy: OutputDebugStringF("NotifyNumConnectionsChanged %i\n", newNumConnections);
|
||||
// Don't queue UI updates during shutdown - ClientModel may be destroyed
|
||||
// before Qt processes the queued invocation, causing use-after-free.
|
||||
if (fShutdown) return;
|
||||
QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection,
|
||||
Q_ARG(int, newNumConnections));
|
||||
}
|
||||
|
||||
static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, ChangeType status)
|
||||
{
|
||||
if (fShutdown) return;
|
||||
OutputDebugStringF("NotifyAlertChanged %s status=%i\n", hash.GetHex().c_str(), status);
|
||||
QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection,
|
||||
Q_ARG(QString, QString::fromStdString(hash.GetHex())),
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
|
||||
int cachedNumBlocks;
|
||||
int cachedNumBlocksOfPeers;
|
||||
mutable int cachedNumConnections;
|
||||
|
||||
int numBlocksAtStartup;
|
||||
|
||||
|
||||
@@ -506,7 +506,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
coinControl->ListSelected(vCoinControl);
|
||||
model->getOutputs(vCoinControl, vOutputs);
|
||||
|
||||
BOOST_FOREACH(const COutput& out, vOutputs)
|
||||
for (const COutput& out : vOutputs)
|
||||
{
|
||||
// Quantity
|
||||
nQuantity++;
|
||||
@@ -522,7 +522,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, address))
|
||||
{
|
||||
CPubKey pubkey;
|
||||
CKeyID *keyid = boost::get< CKeyID >(&address);
|
||||
CKeyID *keyid = std::get_if< CKeyID >(&address);
|
||||
if (keyid && model->getPubKey(*keyid, pubkey))
|
||||
nBytesInputs += (pubkey.IsCompressed() ? 148 : 180);
|
||||
else
|
||||
@@ -647,7 +647,7 @@ void CoinControlDialog::updateView()
|
||||
map<QString, vector<COutput> > mapCoins;
|
||||
model->listCoins(mapCoins);
|
||||
|
||||
BOOST_FOREACH(PAIRTYPE(QString, vector<COutput>) coins, mapCoins)
|
||||
for (auto coins : mapCoins)
|
||||
{
|
||||
QTreeWidgetItem *itemWalletAddress = new QTreeWidgetItem();
|
||||
QString sWalletAddress = coins.first;
|
||||
@@ -679,7 +679,7 @@ void CoinControlDialog::updateView()
|
||||
double dPrioritySum = 0;
|
||||
int nChildren = 0;
|
||||
int nInputSum = 0;
|
||||
BOOST_FOREACH(const COutput& out, coins.second)
|
||||
for (const COutput& out : coins.second)
|
||||
{
|
||||
int nInputSize = 148; // 180 if uncompressed public key
|
||||
nSum += out.tx->vout[out.i].nValue;
|
||||
@@ -703,7 +703,7 @@ void CoinControlDialog::updateView()
|
||||
itemOutput->setText(COLUMN_ADDRESS, sAddress);
|
||||
|
||||
CPubKey pubkey;
|
||||
CKeyID *keyid = boost::get< CKeyID >(&outputAddress);
|
||||
CKeyID *keyid = std::get_if< CKeyID >(&outputAddress);
|
||||
if (keyid && model->getPubKey(*keyid, pubkey) && !pubkey.IsCompressed())
|
||||
nInputSize = 180;
|
||||
}
|
||||
|
||||
@@ -1085,6 +1085,63 @@ QPushButton:!enabled {
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="filterLayout">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="showLabel">
|
||||
<property name="text">
|
||||
<string>Show:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showRequestsCheckBox">
|
||||
<property name="text">
|
||||
<string>Requests</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showRepliesCheckBox">
|
||||
<property name="text">
|
||||
<string>Replies</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showErrorsCheckBox">
|
||||
<property name="text">
|
||||
<string>Errors</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacerFilters">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define GUICONSTANTS_H
|
||||
|
||||
/* Milliseconds between model updates */
|
||||
static const int MODEL_UPDATE_DELAY = 500;
|
||||
static const int MODEL_UPDATE_DELAY = 2500;
|
||||
|
||||
/* AskPassphraseDialog -- Maximum passphrase length */
|
||||
static const int MAX_PASSPHRASE_SIZE = 1024;
|
||||
|
||||
@@ -405,6 +405,96 @@ bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#elif defined(Q_OS_MAC) || defined(MAC_OSX) || defined(__APPLE__)
|
||||
|
||||
boost::filesystem::path static GetLaunchAgentsDir()
|
||||
{
|
||||
const QString homeDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||
if (homeDir.isEmpty())
|
||||
return boost::filesystem::path();
|
||||
return boost::filesystem::path(homeDir.toStdString()) / "Library" / "LaunchAgents";
|
||||
}
|
||||
|
||||
boost::filesystem::path static GetAutostartFilePath()
|
||||
{
|
||||
return GetLaunchAgentsDir() / "org.triangles.triangles-qt.plist";
|
||||
}
|
||||
|
||||
static std::string PlistEscape(const std::string& value)
|
||||
{
|
||||
std::string escaped;
|
||||
escaped.reserve(value.size());
|
||||
for (std::string::const_iterator it = value.begin(); it != value.end(); ++it)
|
||||
{
|
||||
switch (*it)
|
||||
{
|
||||
case '&': escaped += "&"; break;
|
||||
case '<': escaped += "<"; break;
|
||||
case '>': escaped += ">"; break;
|
||||
case '"': escaped += """; break;
|
||||
case '\'': escaped += "'"; break;
|
||||
default: escaped += *it; break;
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
bool GetStartOnSystemStartup()
|
||||
{
|
||||
boost::filesystem::ifstream optionFile(GetAutostartFilePath());
|
||||
if (!optionFile.good())
|
||||
return false;
|
||||
|
||||
std::string contents;
|
||||
std::string line;
|
||||
while (getline(optionFile, line))
|
||||
contents += line;
|
||||
optionFile.close();
|
||||
|
||||
return contents.find("<key>RunAtLoad</key>") != std::string::npos &&
|
||||
contents.find("<true/>") != std::string::npos &&
|
||||
contents.find("<string>-min</string>") != std::string::npos;
|
||||
}
|
||||
|
||||
bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
{
|
||||
if (!fAutoStart)
|
||||
return !boost::filesystem::exists(GetAutostartFilePath()) || boost::filesystem::remove(GetAutostartFilePath());
|
||||
|
||||
const QString exePath = QApplication::applicationFilePath();
|
||||
if (exePath.isEmpty())
|
||||
return false;
|
||||
|
||||
const QString workingDir = QFileInfo(exePath).absolutePath();
|
||||
boost::filesystem::create_directories(GetLaunchAgentsDir());
|
||||
|
||||
boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
|
||||
if (!optionFile.good())
|
||||
return false;
|
||||
|
||||
optionFile
|
||||
<< "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
<< "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
|
||||
<< "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
|
||||
<< "<plist version=\"1.0\">\n"
|
||||
<< "<dict>\n"
|
||||
<< " <key>Label</key>\n"
|
||||
<< " <string>org.triangles.triangles-qt</string>\n"
|
||||
<< " <key>ProgramArguments</key>\n"
|
||||
<< " <array>\n"
|
||||
<< " <string>" << PlistEscape(exePath.toStdString()) << "</string>\n"
|
||||
<< " <string>-min</string>\n"
|
||||
<< " </array>\n"
|
||||
<< " <key>RunAtLoad</key>\n"
|
||||
<< " <true/>\n"
|
||||
<< " <key>WorkingDirectory</key>\n"
|
||||
<< " <string>" << PlistEscape(workingDir.toStdString()) << "</string>\n"
|
||||
<< "</dict>\n"
|
||||
<< "</plist>\n";
|
||||
optionFile.close();
|
||||
|
||||
return optionFile.good();
|
||||
}
|
||||
#else
|
||||
|
||||
// TODO: OSX startup stuff; see:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "introdialog.h"
|
||||
#include "util.h"
|
||||
#include "bootstrap.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
@@ -9,9 +10,14 @@
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QCheckBox>
|
||||
#include <QApplication>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
IntroDialog::IntroDialog(QWidget *parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
@@ -186,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) {
|
||||
@@ -201,5 +225,230 @@ bool IntroDialog::pickDataDirectory()
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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());
|
||||
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");
|
||||
msgBox.setText(
|
||||
"Would you like to download the latest blockchain snapshot?\n\n"
|
||||
"This will download the blockchain data from the Triangles network "
|
||||
"and replace any existing chain data in your data directory.\n\n"
|
||||
"Click Yes to download, or No to sync from the network.");
|
||||
msgBox.setIcon(QMessageBox::Question);
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::Yes);
|
||||
QCheckBox *dontAskBox = new QCheckBox("Don't show this again");
|
||||
msgBox.setCheckBox(dontAskBox);
|
||||
|
||||
int ret = msgBox.exec();
|
||||
|
||||
if (dontAskBox->isChecked())
|
||||
settings.setValue("bootstrapDontAsk", true);
|
||||
|
||||
userWantsBootstrap = (ret == QMessageBox::Yes);
|
||||
}
|
||||
|
||||
if (userWantsBootstrap)
|
||||
{
|
||||
std::string host = Bootstrap::DEFAULT_HOST;
|
||||
std::string strError;
|
||||
|
||||
QProgressDialog progress("Downloading blockchain snapshot...", "Cancel",
|
||||
0, 100, 0);
|
||||
progress.setWindowTitle("Triangles - Bootstrap");
|
||||
progress.setWindowModality(Qt::ApplicationModal);
|
||||
progress.setMinimumDuration(0);
|
||||
progress.setValue(0);
|
||||
|
||||
auto progressFn = [&progress](int64_t bytesDownloaded, int64_t totalBytes) {
|
||||
if (totalBytes > 0) {
|
||||
int pct = (int)((bytesDownloaded * 100) / totalBytes);
|
||||
progress.setValue(pct);
|
||||
progress.setLabelText(
|
||||
QString("Downloading blockchain snapshot... %1 MB / %2 MB")
|
||||
.arg(bytesDownloaded / (1024*1024))
|
||||
.arg(totalBytes / (1024*1024)));
|
||||
} else {
|
||||
progress.setLabelText(
|
||||
QString("Downloading blockchain snapshot... %1 MB")
|
||||
.arg(bytesDownloaded / (1024*1024)));
|
||||
}
|
||||
QApplication::processEvents();
|
||||
};
|
||||
|
||||
bool success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
|
||||
if (!success) {
|
||||
host = Bootstrap::FALLBACK_HOST;
|
||||
progress.setValue(0);
|
||||
success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
QMessageBox::warning(0, "Triangles",
|
||||
QString("Could not download blockchain snapshot:\n%1\n\n"
|
||||
"The wallet will sync from the network instead.")
|
||||
.arg(QString::fromStdString(strError)));
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <QMenu>
|
||||
#include <QFont>
|
||||
#include <QColor>
|
||||
#include <QTextDocument>
|
||||
|
||||
Q_DECLARE_METATYPE(std::vector<unsigned char>);
|
||||
|
||||
@@ -27,6 +28,24 @@ QList<QString> ambiguous; /**< Specifies Ambiguous addresses */
|
||||
const QString MessageModel::Sent = "Sent";
|
||||
const QString MessageModel::Received = "Received";
|
||||
|
||||
namespace {
|
||||
|
||||
static QString FormatShortMessage(const QString& message)
|
||||
{
|
||||
static const int kMaxPreviewChars = 80;
|
||||
|
||||
QTextDocument doc;
|
||||
doc.setHtml(message);
|
||||
|
||||
QString preview = doc.toPlainText().simplified();
|
||||
if (preview.length() <= kMaxPreviewChars)
|
||||
return preview;
|
||||
|
||||
return preview.left(kMaxPreviewChars - 3) + "...";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct MessageTableEntryLessThan
|
||||
{
|
||||
bool operator()(const MessageTableEntry &a, const MessageTableEntry &b) const {return a.received_datetime < b.received_datetime;};
|
||||
@@ -485,7 +504,7 @@ QVariant MessageModel::data(const QModelIndex &index, int role) const
|
||||
case FilterAddressRole: return (rec->type == MessageTableEntry::Sent ? rec->to_address + rec->from_address : rec->from_address + rec->to_address);
|
||||
case LabelRole: return rec->label;
|
||||
case MessageRole: return rec->message;
|
||||
case ShortMessageRole: return rec->message; // TODO: Short message
|
||||
case ShortMessageRole: return FormatShortMessage(rec->message);
|
||||
case HTMLRole: return rec->received_datetime.toString() + "<br>" + (rec->label.isEmpty() ? rec->from_address : rec->label) + "<br>" + rec->message;
|
||||
case Ambiguous:
|
||||
int it;
|
||||
|
||||
@@ -31,7 +31,7 @@ protected:
|
||||
|
||||
void MessageViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
QStyleOptionViewItemV4 optionV4 = option;
|
||||
QStyleOptionViewItem optionV4 = option;
|
||||
initStyleOption(&optionV4, index);
|
||||
|
||||
QStyle *style = optionV4.widget? optionV4.widget->style() : QApplication::style();
|
||||
@@ -62,7 +62,7 @@ void MessageViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
|
||||
|
||||
QSize MessageViewDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const
|
||||
{
|
||||
QStyleOptionViewItemV4 options = option;
|
||||
QStyleOptionViewItem options = option;
|
||||
initStyleOption(&options, index);
|
||||
|
||||
QTextDocument doc;
|
||||
@@ -159,7 +159,7 @@ void MessagePage::setModel(MessageModel *model)
|
||||
// Set column widths
|
||||
ui->tableView->horizontalHeader()->resizeSection(MessageModel::Type, 100);
|
||||
ui->tableView->horizontalHeader()->resizeSection(MessageModel::Label, 100);
|
||||
ui->tableView->horizontalHeader()->setResizeMode(MessageModel::Label, QHeaderView::Stretch);
|
||||
ui->tableView->horizontalHeader()->setSectionResizeMode(MessageModel::Label, QHeaderView::Stretch);
|
||||
ui->tableView->horizontalHeader()->resizeSection(MessageModel::FromAddress, 320);
|
||||
ui->tableView->horizontalHeader()->resizeSection(MessageModel::ToAddress, 320);
|
||||
ui->tableView->horizontalHeader()->resizeSection(MessageModel::SentDateTime, 170);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -92,6 +92,7 @@ OverviewPage::OverviewPage(QWidget *parent) :
|
||||
currentStake(0),
|
||||
currentUnconfirmedBalance(-1),
|
||||
currentImmatureBalance(-1),
|
||||
walletTransactionSyncing(false),
|
||||
txdelegate(new TxViewDelegate()),
|
||||
filter(0)
|
||||
{
|
||||
@@ -126,6 +127,8 @@ OverviewPage::~OverviewPage()
|
||||
|
||||
void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance)
|
||||
{
|
||||
if (!model || !model->getOptionsModel())
|
||||
return;
|
||||
int unit = model->getOptionsModel()->getDisplayUnit();
|
||||
currentBalance = balance;
|
||||
currentStake = stake;
|
||||
@@ -164,8 +167,10 @@ void OverviewPage::setModel(WalletModel *model)
|
||||
// Keep up to date with wallet
|
||||
setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance(), model->getImmatureBalance());
|
||||
connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64, qint64)));
|
||||
connect(model, SIGNAL(transactionSyncStateChanged(bool)), this, SLOT(setTransactionSyncState(bool)));
|
||||
|
||||
connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
|
||||
setTransactionSyncState(model->isTransactionSyncing());
|
||||
}
|
||||
|
||||
// update the display unit, to not use the default ("TRI")
|
||||
@@ -186,6 +191,23 @@ void OverviewPage::updateDisplayUnit()
|
||||
}
|
||||
}
|
||||
|
||||
void OverviewPage::setTransactionSyncState(bool syncing)
|
||||
{
|
||||
walletTransactionSyncing = syncing;
|
||||
if (!filter)
|
||||
return;
|
||||
|
||||
filter->setDynamicSortFilter(!syncing);
|
||||
ui->listTransactions->setUpdatesEnabled(!syncing);
|
||||
|
||||
if (!syncing)
|
||||
{
|
||||
filter->invalidate();
|
||||
filter->sort(TransactionTableModel::Status, Qt::DescendingOrder);
|
||||
ui->listTransactions->viewport()->update();
|
||||
}
|
||||
}
|
||||
|
||||
void OverviewPage::showOutOfSyncWarning(bool fShow)
|
||||
{
|
||||
ui->labelWalletStatus->setVisible(fShow);
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
|
||||
public slots:
|
||||
void setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance);
|
||||
void setTransactionSyncState(bool syncing);
|
||||
|
||||
signals:
|
||||
void transactionClicked(const QModelIndex &index);
|
||||
@@ -42,6 +43,7 @@ private:
|
||||
qint64 currentStake;
|
||||
qint64 currentUnconfirmedBalance;
|
||||
qint64 currentImmatureBalance;
|
||||
bool walletTransactionSyncing;
|
||||
|
||||
TxViewDelegate *txdelegate;
|
||||
TransactionFilterProxy *filter;
|
||||
|
||||
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 20 KiB |
@@ -5,6 +5,7 @@
|
||||
#include "trianglesrpc.h"
|
||||
#include "guiutil.h"
|
||||
#include "dialog_move_handler.h"
|
||||
#include "openssl_compat.h"
|
||||
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
@@ -16,9 +17,6 @@
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
// TODO: make it possible to filter out categories (esp debug messages when implemented)
|
||||
// TODO: receive errors and debug messages through ClientModel
|
||||
|
||||
const int CONSOLE_SCROLLBACK = 50;
|
||||
const int CONSOLE_HISTORY = 50;
|
||||
|
||||
@@ -189,6 +187,7 @@ void RPCExecutor::request(const QString &command)
|
||||
RPCConsole::RPCConsole(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::RPCConsole),
|
||||
clientModel(0),
|
||||
historyPtr(0)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
@@ -207,7 +206,7 @@ RPCConsole::RPCConsole(QWidget *parent) :
|
||||
connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
|
||||
|
||||
// set OpenSSL version label
|
||||
ui->openSSLVersion->setText(SSLeay_version(SSLEAY_VERSION));
|
||||
ui->openSSLVersion->setText(TrianglesOpenSSLVersionString());
|
||||
|
||||
startExecutor();
|
||||
|
||||
@@ -258,12 +257,16 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
|
||||
|
||||
void RPCConsole::setClientModel(ClientModel *model)
|
||||
{
|
||||
if (clientModel)
|
||||
disconnect(clientModel, 0, this, 0);
|
||||
|
||||
this->clientModel = model;
|
||||
if(model)
|
||||
{
|
||||
// Subscribe to information, replies, messages, errors
|
||||
connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
|
||||
connect(model, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int)));
|
||||
connect(model, SIGNAL(error(QString,QString,bool)), this, SLOT(showClientError(QString,QString,bool)));
|
||||
|
||||
// Provide initial values
|
||||
ui->clientVersion->setText(model->formatFullVersion());
|
||||
@@ -284,14 +287,16 @@ static QString categoryClass(int category)
|
||||
{
|
||||
case RPCConsole::CMD_REQUEST: return "cmd-request"; break;
|
||||
case RPCConsole::CMD_REPLY: return "cmd-reply"; break;
|
||||
case RPCConsole::CMD_ERROR: return "cmd-error"; break;
|
||||
case RPCConsole::CMD_ERROR:
|
||||
case RPCConsole::MC_ERROR: return "cmd-error"; break;
|
||||
default: return "misc";
|
||||
}
|
||||
}
|
||||
|
||||
void RPCConsole::clear()
|
||||
{
|
||||
ui->messagesWidget->clear();
|
||||
consoleEntries.clear();
|
||||
refreshMessages();
|
||||
ui->lineEdit->clear();
|
||||
ui->lineEdit->setFocus();
|
||||
|
||||
@@ -322,18 +327,21 @@ void RPCConsole::clear()
|
||||
|
||||
void RPCConsole::message(int category, const QString &message, bool html)
|
||||
{
|
||||
QTime time = QTime::currentTime();
|
||||
QString timeString = time.toString();
|
||||
QString out;
|
||||
out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
|
||||
out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(category) + "\"></td>";
|
||||
out += "<td class=\"message " + categoryClass(category) + "\" valign=\"middle\">";
|
||||
if(html)
|
||||
out += message;
|
||||
else
|
||||
out += GUIUtil::HtmlEscape(message, true);
|
||||
out += "</td></tr></table>";
|
||||
ui->messagesWidget->append(out);
|
||||
ConsoleEntry entry;
|
||||
entry.category = category;
|
||||
entry.text = message;
|
||||
entry.time = QTime::currentTime().toString();
|
||||
entry.html = html;
|
||||
|
||||
consoleEntries.append(entry);
|
||||
while (consoleEntries.size() > CONSOLE_SCROLLBACK)
|
||||
consoleEntries.removeFirst();
|
||||
|
||||
if (categoryVisible(category))
|
||||
{
|
||||
ui->messagesWidget->append(formatEntry(entry));
|
||||
scrollToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void RPCConsole::setNumConnections(int count)
|
||||
@@ -438,3 +446,72 @@ void RPCConsole::on_showCLOptionsButton_clicked()
|
||||
GUIUtil::HelpMessageBox help;
|
||||
help.exec();
|
||||
}
|
||||
|
||||
bool RPCConsole::categoryVisible(int category) const
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case CMD_REQUEST:
|
||||
return ui->showRequestsCheckBox->isChecked();
|
||||
case CMD_ERROR:
|
||||
case MC_ERROR:
|
||||
return ui->showErrorsCheckBox->isChecked();
|
||||
case CMD_REPLY:
|
||||
case MC_DEBUG:
|
||||
default:
|
||||
return ui->showRepliesCheckBox->isChecked();
|
||||
}
|
||||
}
|
||||
|
||||
QString RPCConsole::formatEntry(const ConsoleEntry &entry) const
|
||||
{
|
||||
QString out;
|
||||
out += "<table><tr><td class=\"time\" width=\"65\">" + entry.time + "</td>";
|
||||
out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(entry.category) + "\"></td>";
|
||||
out += "<td class=\"message " + categoryClass(entry.category) + "\" valign=\"middle\">";
|
||||
if (entry.html)
|
||||
out += entry.text;
|
||||
else
|
||||
out += GUIUtil::HtmlEscape(entry.text, true);
|
||||
out += "</td></tr></table>";
|
||||
return out;
|
||||
}
|
||||
|
||||
void RPCConsole::refreshMessages()
|
||||
{
|
||||
ui->messagesWidget->clear();
|
||||
for (QList<ConsoleEntry>::const_iterator it = consoleEntries.begin(); it != consoleEntries.end(); ++it)
|
||||
{
|
||||
if (categoryVisible(it->category))
|
||||
ui->messagesWidget->append(formatEntry(*it));
|
||||
}
|
||||
scrollToEnd();
|
||||
}
|
||||
|
||||
void RPCConsole::on_showRequestsCheckBox_toggled(bool checked)
|
||||
{
|
||||
Q_UNUSED(checked);
|
||||
refreshMessages();
|
||||
}
|
||||
|
||||
void RPCConsole::on_showRepliesCheckBox_toggled(bool checked)
|
||||
{
|
||||
Q_UNUSED(checked);
|
||||
refreshMessages();
|
||||
}
|
||||
|
||||
void RPCConsole::on_showErrorsCheckBox_toggled(bool checked)
|
||||
{
|
||||
Q_UNUSED(checked);
|
||||
refreshMessages();
|
||||
}
|
||||
|
||||
void RPCConsole::showClientError(const QString &title, const QString &message, bool modal)
|
||||
{
|
||||
Q_UNUSED(modal);
|
||||
|
||||
if (title.isEmpty())
|
||||
this->message(MC_ERROR, message);
|
||||
else
|
||||
this->message(MC_ERROR, title + ": " + message);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ private slots:
|
||||
void on_openDebugLogfileButton_clicked();
|
||||
/** display messagebox with program parameters (same as triangles-qt --help) */
|
||||
void on_showCLOptionsButton_clicked();
|
||||
void on_showRequestsCheckBox_toggled(bool checked);
|
||||
void on_showRepliesCheckBox_toggled(bool checked);
|
||||
void on_showErrorsCheckBox_toggled(bool checked);
|
||||
void showClientError(const QString &title, const QString &message, bool modal);
|
||||
|
||||
public slots:
|
||||
void clear();
|
||||
@@ -55,11 +59,23 @@ signals:
|
||||
void cmdRequest(const QString &command);
|
||||
|
||||
private:
|
||||
struct ConsoleEntry
|
||||
{
|
||||
int category;
|
||||
QString text;
|
||||
QString time;
|
||||
bool html;
|
||||
};
|
||||
|
||||
Ui::RPCConsole *ui;
|
||||
ClientModel *clientModel;
|
||||
QStringList history;
|
||||
QList<ConsoleEntry> consoleEntries;
|
||||
int historyPtr;
|
||||
|
||||
bool categoryVisible(int category) const;
|
||||
QString formatEntry(const ConsoleEntry &entry) const;
|
||||
void refreshMessages();
|
||||
void startExecutor();
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) :
|
||||
model(0)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Window);
|
||||
if (parent)
|
||||
setWindowFlags(Qt::Widget);
|
||||
else
|
||||
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Window);
|
||||
|
||||
#ifdef Q_OS_MAC // Icons on push buttons are very uncommon on Mac
|
||||
ui->addButton->setIcon(QIcon());
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QDesktopWidget>
|
||||
|
||||
SignMessagePage::SignMessagePage(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::SignMessagePage),
|
||||
|
||||
@@ -77,7 +77,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
|
||||
if (nNet > 0)
|
||||
{
|
||||
// Credit
|
||||
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
|
||||
for (const CTxOut& txout : wtx.vout)
|
||||
{
|
||||
if (wallet->IsMine(txout))
|
||||
{
|
||||
@@ -125,7 +125,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
|
||||
// Coinbase
|
||||
//
|
||||
int64_t nUnmatured = 0;
|
||||
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
|
||||
for (const CTxOut& txout : wtx.vout)
|
||||
nUnmatured += wallet->GetCredit(txout);
|
||||
strHTML += "<b>" + tr("Credit") + ":</b> ";
|
||||
if (wtx.IsInMainChain())
|
||||
@@ -144,11 +144,11 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
|
||||
else
|
||||
{
|
||||
bool fAllFromMe = true;
|
||||
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
|
||||
for (const CTxIn& txin : wtx.vin)
|
||||
fAllFromMe = fAllFromMe && wallet->IsMine(txin);
|
||||
|
||||
bool fAllToMe = true;
|
||||
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
|
||||
for (const CTxOut& txout : wtx.vout)
|
||||
fAllToMe = fAllToMe && wallet->IsMine(txout);
|
||||
|
||||
if (fAllFromMe)
|
||||
@@ -156,7 +156,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
|
||||
//
|
||||
// Debit
|
||||
//
|
||||
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
|
||||
for (const CTxOut& txout : wtx.vout)
|
||||
{
|
||||
if (wallet->IsMine(txout))
|
||||
continue;
|
||||
@@ -196,10 +196,10 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
|
||||
//
|
||||
// Mixed debit transaction
|
||||
//
|
||||
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
|
||||
for (const CTxIn& txin : wtx.vin)
|
||||
if (wallet->IsMine(txin))
|
||||
strHTML += "<b>" + tr("Debit") + ":</b> " + TrianglesUnits::formatWithUnit(TrianglesUnits::TRI, -wallet->GetDebit(txin)) + "<br>";
|
||||
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
|
||||
for (const CTxOut& txout : wtx.vout)
|
||||
if (wallet->IsMine(txout))
|
||||
strHTML += "<b>" + tr("Credit") + ":</b> " + TrianglesUnits::formatWithUnit(TrianglesUnits::TRI, wallet->GetCredit(txout)) + "<br>";
|
||||
}
|
||||
@@ -234,10 +234,10 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
|
||||
if (fDebug)
|
||||
{
|
||||
strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
|
||||
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
|
||||
for (const CTxIn& txin : wtx.vin)
|
||||
if(wallet->IsMine(txin))
|
||||
strHTML += "<b>" + tr("Debit") + ":</b> " + TrianglesUnits::formatWithUnit(TrianglesUnits::TRI, -wallet->GetDebit(txin)) + "<br>";
|
||||
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
|
||||
for (const CTxOut& txout : wtx.vout)
|
||||
if(wallet->IsMine(txout))
|
||||
strHTML += "<b>" + tr("Credit") + ":</b> " + TrianglesUnits::formatWithUnit(TrianglesUnits::TRI, wallet->GetCredit(txout)) + "<br>";
|
||||
|
||||
@@ -251,7 +251,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
|
||||
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
|
||||
for (const CTxIn& txin : wtx.vin)
|
||||
{
|
||||
COutPoint prevout = txin.prevout;
|
||||
|
||||
|
||||