Compare commits
93 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 |
@@ -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.3.4"
|
||||
|
||||
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
|
||||
|
||||
@@ -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,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,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 -spec win32-g++ CONFIG+=release
|
||||
# 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 -spec win32-g++ CONFIG+=release
|
||||
$(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 -spec win32-g++ CONFIG+=release
|
||||
@$(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:
|
||||
|
||||
@@ -51,14 +51,13 @@ nOrderPos = -1; // TODO: calculate elsewhere
|
||||
**Status:** Deferred - no functional issue.
|
||||
**Fix:** Move calculation to WalletDB when transaction is added.
|
||||
|
||||
### src/rpcwallet.cpp - SecureString Operator
|
||||
```cpp
|
||||
// Lines 1474, 1513, 1569: "TODO: get rid of this .c_str()"
|
||||
```
|
||||
**Issue:** SecureString missing operator=(std::string).
|
||||
**Impact:** Forced to use .c_str() which exposes password temporarily.
|
||||
**Status:** Deferred - would require SecureString class modification.
|
||||
**Fix:** Add `SecureString& operator=(const std::string&)` method.
|
||||
### 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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,10 +10,18 @@
|
||||
|
||||
#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;
|
||||
@@ -311,6 +319,112 @@ static bool ExtractTarGz(const fs::path& tarGzPath,
|
||||
|
||||
} // 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,
|
||||
@@ -362,16 +476,55 @@ bool DownloadBootstrap(const std::string& host,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove any extracted txleveldb/ and database/ - they were built on
|
||||
// a different machine and won't work here. FastImportBlockFile() will
|
||||
// rebuild the index directly from blk0001.dat on next startup.
|
||||
// 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";
|
||||
if (fs::exists(txleveldb))
|
||||
fs::remove_all(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,24 @@ namespace Bootstrap {
|
||||
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
|
||||
|
||||
@@ -60,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);
|
||||
|
||||
@@ -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 3
|
||||
#define CLIENT_VERSION_REVISION 4
|
||||
#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.
|
||||
|
||||
@@ -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,6 +6,7 @@
|
||||
#include "walletdb.h"
|
||||
#include "trianglesrpc.h"
|
||||
#include "net.h"
|
||||
#include "netbase.h"
|
||||
#include "init.h"
|
||||
#include "util.h"
|
||||
#include "ui_interface.h"
|
||||
@@ -51,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
|
||||
@@ -95,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)
|
||||
{
|
||||
@@ -158,6 +177,12 @@ 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();
|
||||
StopEmbeddedTor();
|
||||
|
||||
@@ -176,10 +201,8 @@ void Shutdown(void* parg)
|
||||
pNotificationQueue = NULL;
|
||||
}
|
||||
|
||||
nTransactionsUpdated++;
|
||||
// CTxDB().Close();
|
||||
bitdb.Flush(false);
|
||||
StopNode();
|
||||
bitdb.Flush(true);
|
||||
fs::remove(GetPidFile());
|
||||
UnregisterWallet(pwalletMain);
|
||||
@@ -335,8 +358,9 @@ std::string HelpMessage()
|
||||
//" -proxy=<ip:port> " + _("Connect through socks proxy") + "\n" +
|
||||
//" -socks=<n> " + _("Select the version of socks proxy to use (4-5, default: 5)") + "\n" +
|
||||
" -tor=<ip:port> " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n"
|
||||
" -notor " + _("Disable Tor startup and .onion connectivity") + "\n" +
|
||||
" -notor " + _("Disable Tor (WARNING: wallet will not start - Tor is required)") + "\n" +
|
||||
" -torsocks=<port> " + _("Set embedded or managed Tor SOCKS proxy port (default: 19099)") + "\n" +
|
||||
" -torhiddenservice " + _("Enable the managed Tor hidden service (default: 1)") + "\n" +
|
||||
" -torhsport=<port> " + _("Set embedded or managed Tor hidden service port (default: wallet listen port)") + "\n" +
|
||||
//" -dns " + _("Allow DNS lookups for -addnode, -seednode and -connect") + "\n" +
|
||||
" -port=<port> " + _("Listen for connections on <port> (default: 24112 or testnet: 24111)") + "\n" +
|
||||
@@ -355,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" +
|
||||
@@ -397,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" +
|
||||
@@ -449,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
|
||||
@@ -550,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");
|
||||
@@ -605,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))
|
||||
@@ -672,6 +710,7 @@ bool AppInit2()
|
||||
// ********************************************************* Step 5: verify database integrity
|
||||
|
||||
uiInterface.InitMessage(_("Verifying database integrity..."));
|
||||
nStart = GetTimeMillis();
|
||||
|
||||
if (!bitdb.Open(GetDataDir()))
|
||||
{
|
||||
@@ -688,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);
|
||||
@@ -702,17 +748,19 @@ 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;
|
||||
for (std::string snet : mapMultiArgs["-onlynet"]) {
|
||||
@@ -730,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);
|
||||
@@ -797,11 +845,27 @@ bool AppInit2()
|
||||
|
||||
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
|
||||
if (GetBoolArg("-bootstrap", false))
|
||||
{
|
||||
bool wantsBootstrap = GetBoolArg("-bootstrap", false);
|
||||
bool noBootstrap = GetBoolArg("-nobootstrap", false);
|
||||
fs::path dataPath = GetDataDir();
|
||||
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataPath);
|
||||
|
||||
if (needsBootstrap && !noBootstrap) {
|
||||
printf("Bootstrap: no blockchain data found — downloading automatically.\n");
|
||||
printf("Bootstrap: (use -nobootstrap to skip)\n");
|
||||
wantsBootstrap = true;
|
||||
}
|
||||
|
||||
if (wantsBootstrap)
|
||||
{
|
||||
int64_t nBootstrapStart = GetTimeMillis();
|
||||
fs::path dataPath = GetDataDir();
|
||||
std::string host = Bootstrap::DEFAULT_HOST;
|
||||
std::string strError;
|
||||
@@ -832,7 +896,10 @@ bool AppInit2()
|
||||
} 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
|
||||
@@ -867,7 +934,9 @@ bool AppInit2()
|
||||
{
|
||||
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
|
||||
@@ -878,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"))
|
||||
{
|
||||
@@ -972,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);
|
||||
|
||||
@@ -981,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)
|
||||
{
|
||||
@@ -992,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;
|
||||
@@ -1016,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
|
||||
@@ -1024,7 +1119,40 @@ bool AppInit2()
|
||||
uiInterface.InitMessage(_("Starting Tor..."));
|
||||
printf("Starting Tor process...\n");
|
||||
|
||||
// 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();
|
||||
@@ -1032,24 +1160,46 @@ bool AppInit2()
|
||||
if (torStarted) {
|
||||
printf("Tor process running, SOCKS proxy at %s\n",
|
||||
CTorEmbedded::GetInstance()->GetSocksProxy().c_str());
|
||||
|
||||
// TOR-NATIVE MODE: Force all traffic through embedded Tor
|
||||
int socksPort = CTorEmbedded::GetInstance()->GetSocksPort();
|
||||
CService torProxyAddr("127.0.0.1", socksPort);
|
||||
|
||||
SetProxy(NET_IPV4, torProxyAddr, 5);
|
||||
SetProxy(NET_IPV6, torProxyAddr, 5);
|
||||
SetProxy(NET_TOR, torProxyAddr, 5);
|
||||
SetNameProxy(torProxyAddr, 5);
|
||||
|
||||
// Disable clearnet reachability - ONION ONLY
|
||||
SetReachable(NET_IPV4, false);
|
||||
SetReachable(NET_IPV6, false);
|
||||
SetReachable(NET_TOR, true);
|
||||
|
||||
printf("TOR-NATIVE MODE: All network traffic forced through Tor\n");
|
||||
printf(" Clearnet disabled - .onion addresses only\n");
|
||||
|
||||
#ifdef USE_UPNP
|
||||
fUseUPnP = false;
|
||||
#endif
|
||||
} else {
|
||||
printf("WARNING: Tor not available. .onion peers will not be reachable.\n");
|
||||
printf(" Clearnet connections will still work normally.\n");
|
||||
return InitError(_("Tor failed to start. Triangles requires Tor to operate."));
|
||||
}
|
||||
|
||||
// Initialize Tor V3 identity (Ed25519 keys, onion address)
|
||||
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";
|
||||
@@ -1061,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) {
|
||||
@@ -1081,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
|
||||
@@ -1097,9 +1261,11 @@ bool AppInit2()
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1108,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
|
||||
@@ -1128,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;
|
||||
@@ -1140,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"));
|
||||
@@ -1161,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
|
||||
@@ -1183,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))
|
||||
{
|
||||
@@ -1199,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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -29,16 +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;
|
||||
@@ -55,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;
|
||||
@@ -426,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.
|
||||
@@ -642,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,
|
||||
@@ -667,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;
|
||||
@@ -766,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
|
||||
{
|
||||
@@ -1069,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(),
|
||||
@@ -1330,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",
|
||||
@@ -1359,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;
|
||||
@@ -1406,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
|
||||
|
||||
@@ -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
|
||||
@@ -202,6 +201,12 @@ endif
|
||||
|
||||
all: trianglesd
|
||||
|
||||
obj:
|
||||
@mkdir -p obj
|
||||
|
||||
obj-test:
|
||||
@mkdir -p obj-test
|
||||
|
||||
test check: test_triangles FORCE
|
||||
./test_triangles
|
||||
|
||||
@@ -231,14 +236,14 @@ 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/ *\\$$//' \
|
||||
@@ -297,9 +302,11 @@ obj/net_bootstrap.o: net_bootstrap.cpp
|
||||
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
|
||||
@@ -247,7 +214,6 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
}
|
||||
|
||||
// Collect transactions into block
|
||||
map<uint256, CTxIndex> mapTestPool;
|
||||
uint64_t nBlockSize = 1000;
|
||||
uint64_t nBlockTx = 0;
|
||||
int nBlockSigOps = 100;
|
||||
@@ -299,10 +265,10 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
|
||||
// Connecting shouldn't fail due to dependency on other memory pool transactions
|
||||
// because we're already processing them in order of dependency
|
||||
map<uint256, CTxIndex> mapTestPoolTmp(mapTestPool);
|
||||
MapPrevTx mapInputs;
|
||||
MapPrevTx mapEmpty;
|
||||
bool fInvalid;
|
||||
if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid))
|
||||
if (!tx.FetchInputs(txdb, mapEmpty, false, true, mapInputs, fInvalid))
|
||||
continue;
|
||||
|
||||
int64_t nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
|
||||
@@ -313,10 +279,8 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
|
||||
continue;
|
||||
|
||||
if (!tx.ConnectInputs(txdb, mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true))
|
||||
if (!tx.ConnectInputs(txdb, mapInputs, pindexPrev, false, true))
|
||||
continue;
|
||||
mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(1,1,1), tx.vout.size());
|
||||
swap(mapTestPool, mapTestPoolTmp);
|
||||
|
||||
// Added
|
||||
pblock->vtx.push_back(tx);
|
||||
@@ -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;
|
||||
@@ -574,7 +434,10 @@ void StakeMiner(CWallet *pwallet)
|
||||
int64_t nFees;
|
||||
unique_ptr<CBlock> pblock(CreateNewBlock(pwallet, true, &nFees));
|
||||
if (!pblock.get())
|
||||
return;
|
||||
{
|
||||
MilliSleep(5000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to sign the block
|
||||
if (pblock->SignBlock(*pwallet, nFees))
|
||||
|
||||
@@ -12,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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1013,7 +1035,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
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)
|
||||
{
|
||||
@@ -1104,6 +1126,8 @@ void ThreadSocketHandler2(void* parg)
|
||||
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))
|
||||
{
|
||||
for (CNetAddr& ip : vaddr)
|
||||
{
|
||||
CAddress addr(CService(ip, GetDefaultPort()));
|
||||
if (fShutdown)
|
||||
return;
|
||||
|
||||
// Trim whitespace and carriage returns
|
||||
while (!line.empty() && (line.back() == '\r' || line.back() == ' ' || line.back() == '\t'))
|
||||
line.pop_back();
|
||||
while (!line.empty() && (line.front() == ' ' || line.front() == '\t'))
|
||||
line.erase(line.begin());
|
||||
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
// Parse address:port
|
||||
std::string addrStr = line;
|
||||
int port = GetDefaultPort();
|
||||
|
||||
// For .onion addresses, the last colon before port is after ".onion"
|
||||
size_t onionPos = addrStr.find(".onion:");
|
||||
if (onionPos != std::string::npos) {
|
||||
port = atoi(addrStr.substr(onionPos + 7).c_str());
|
||||
addrStr = addrStr.substr(0, onionPos + 6); // keep ".onion"
|
||||
} else if (addrStr.find(".onion") == std::string::npos) {
|
||||
// Clearnet address - find last colon for port
|
||||
size_t colonPos = addrStr.rfind(':');
|
||||
if (colonPos != std::string::npos) {
|
||||
port = atoi(addrStr.substr(colonPos + 1).c_str());
|
||||
addrStr = addrStr.substr(0, colonPos);
|
||||
}
|
||||
}
|
||||
|
||||
if (port <= 0 || port > 65535)
|
||||
port = GetDefaultPort();
|
||||
|
||||
CNetAddr parsed;
|
||||
bool resolved = parsed.SetSpecial(addrStr);
|
||||
if (!resolved) {
|
||||
std::vector<CNetAddr> vIP;
|
||||
if (LookupHost(addrStr.c_str(), vIP, 1, false) && !vIP.empty()) {
|
||||
parsed = vIP[0];
|
||||
resolved = true;
|
||||
}
|
||||
}
|
||||
if (resolved) {
|
||||
CAddress addr(CService(parsed, port));
|
||||
addr.nTime = GetTime() - 3*24*60*60; // 3 days ago
|
||||
addrman.Add(addr, CNetAddr(strDNSSeed[seed_idx], true));
|
||||
addrman.Add(addr, CNetAddr("https-seed", true));
|
||||
found++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("%d addresses found from DNS seeds\n", found);
|
||||
printf("%d addresses found from HTTPS seed list (%s)\n", found, seedHost.c_str());
|
||||
|
||||
} catch (std::exception& e) {
|
||||
printf("HTTPS seed fetch failed: %s\n", e.what());
|
||||
if (ssl) { SSL_shutdown(ssl); SSL_free(ssl); }
|
||||
if (ctx) SSL_CTX_free(ctx);
|
||||
if (hSocket != INVALID_SOCKET) closesocket(hSocket);
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadDNSAddressSeed(void* parg)
|
||||
void ThreadHTTPSeedFetch(void* parg)
|
||||
{
|
||||
RenameThread("Triangles-dnsseed");
|
||||
RenameThread("Triangles-httpseed");
|
||||
try
|
||||
{
|
||||
vnThreadsRunning[THREAD_DNSSEED]++;
|
||||
ThreadDNSAddressSeed2(parg);
|
||||
vnThreadsRunning[THREAD_DNSSEED]--;
|
||||
vnThreadsRunning[THREAD_HTTPSEED]++;
|
||||
ThreadHTTPSeedFetch2(parg);
|
||||
vnThreadsRunning[THREAD_HTTPSEED]--;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
vnThreadsRunning[THREAD_DNSSEED]--;
|
||||
PrintException(&e, "ThreadDNSAddressSeed()");
|
||||
vnThreadsRunning[THREAD_HTTPSEED]--;
|
||||
PrintException(&e, "ThreadHTTPSeedFetch()");
|
||||
} catch (...) {
|
||||
vnThreadsRunning[THREAD_DNSSEED]--;
|
||||
PrintException(NULL, "ThreadDNSAddressSeed()");
|
||||
vnThreadsRunning[THREAD_HTTPSEED]--;
|
||||
PrintException(NULL, "ThreadHTTPSeedFetch()");
|
||||
}
|
||||
printf("ThreadDNSAddressSeed exited\n");
|
||||
printf("ThreadHTTPSeedFetch exited\n");
|
||||
}
|
||||
|
||||
void ThreadOpenConnections(void* parg)
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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;
|
||||
for (CNode* pnode : vNodes) {
|
||||
if (!pnode->fInbound) { fNoOutbound = false; break; }
|
||||
}
|
||||
if (fNoOutbound && (GetTime() - nStart > 10) && !fTestNet)
|
||||
{
|
||||
std::vector<CAddress> vAdd;
|
||||
for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++)
|
||||
{
|
||||
struct in_addr ip;
|
||||
memcpy(&ip, &pnSeed[i], sizeof(ip));
|
||||
CAddress addr(CService(ip, GetDefaultPort()));
|
||||
addr.nTime = GetTime() - GetRand(60*60); // seen recently
|
||||
vAdd.push_back(addr);
|
||||
}
|
||||
addrman.Add(vAdd, CNetAddr("127.0.0.1"));
|
||||
printf("No outbound connections after 10s, added %d hardcoded seeds\n", (int)vAdd.size());
|
||||
}
|
||||
}
|
||||
// Hardcoded seed fallback removed - peer discovery is now fully dynamic
|
||||
// via HTTP seed list from seeds.cryptographic-triangles.org
|
||||
|
||||
//
|
||||
// Choose an address to connect to based on most recently seen
|
||||
@@ -1832,6 +2006,9 @@ void ThreadMessageHandler2(void* parg)
|
||||
pnodeTrickle = vNodesCopy[GetRand(vNodesCopy.size())];
|
||||
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,8 +2023,6 @@ void ThreadMessageHandler2(void* parg)
|
||||
if (lockSend)
|
||||
SendMessages(pnode, pnode == pnodeTrickle);
|
||||
}
|
||||
if (fShutdown)
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -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,7 +2381,8 @@ public:
|
||||
}
|
||||
~CNetCleanup()
|
||||
{
|
||||
// Close sockets
|
||||
// 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);
|
||||
|
||||
@@ -103,7 +103,7 @@ enum threadId
|
||||
THREAD_MESSAGEHANDLER,
|
||||
THREAD_RPCLISTENER,
|
||||
THREAD_UPNP,
|
||||
THREAD_DNSSEED,
|
||||
THREAD_HTTPSEED,
|
||||
THREAD_ADDEDCONNECTIONS,
|
||||
THREAD_DUMPADDRESS,
|
||||
THREAD_RPCHANDLER,
|
||||
|
||||
@@ -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}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -70,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);
|
||||
}
|
||||
@@ -185,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())),
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
IntroDialog::IntroDialog(QWidget *parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
@@ -190,6 +192,24 @@ bool IntroDialog::pickDataDirectory()
|
||||
settings.setValue("strDataDir", dataDir);
|
||||
}
|
||||
|
||||
// Check for pending data directory migration
|
||||
if (settings.value("fPendingDataDirMigration", false).toBool()) {
|
||||
QString oldDir = settings.value("strDataDirPrevious", "").toString();
|
||||
if (!oldDir.isEmpty() && oldDir != dataDir) {
|
||||
if (!migrateDataDirectory(oldDir, dataDir)) {
|
||||
// Migration failed - revert to old directory
|
||||
QMessageBox::warning(0, "Triangles",
|
||||
QString("Data directory migration failed.\nContinuing with the previous directory:\n%1")
|
||||
.arg(oldDir));
|
||||
dataDir = oldDir;
|
||||
settings.setValue("strDataDir", oldDir);
|
||||
}
|
||||
}
|
||||
// Clear migration state regardless
|
||||
settings.remove("strDataDirPrevious");
|
||||
settings.setValue("fPendingDataDirMigration", false);
|
||||
}
|
||||
|
||||
// If the saved path is the default, don't set -datadir (let normal defaults work)
|
||||
QString defaultDir = QString::fromStdString(GetDefaultDataDir().string());
|
||||
if (dataDir != defaultDir) {
|
||||
@@ -205,9 +225,22 @@ bool IntroDialog::pickDataDirectory()
|
||||
return false;
|
||||
}
|
||||
|
||||
// Offer bootstrap download on each startup (unless user checked "don't ask again")
|
||||
// Auto-bootstrap: if no blockchain data exists, download automatically.
|
||||
// If data exists, offer optional re-download (unless user checked "don't ask again").
|
||||
fs::path dataDirPath(dataDir.toStdString());
|
||||
if (!settings.value("bootstrapDontAsk", false).toBool())
|
||||
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataDirPath);
|
||||
bool userWantsBootstrap = false;
|
||||
|
||||
if (needsBootstrap)
|
||||
{
|
||||
// No blockchain data — bootstrap automatically, just inform the user
|
||||
QMessageBox::information(0, "Triangles",
|
||||
"No blockchain data found.\n\n"
|
||||
"Downloading the latest blockchain snapshot automatically.\n"
|
||||
"This will only take a few minutes.");
|
||||
userWantsBootstrap = true;
|
||||
}
|
||||
else if (!settings.value("bootstrapDontAsk", false).toBool())
|
||||
{
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Triangles");
|
||||
@@ -227,7 +260,10 @@ bool IntroDialog::pickDataDirectory()
|
||||
if (dontAskBox->isChecked())
|
||||
settings.setValue("bootstrapDontAsk", true);
|
||||
|
||||
if (ret == QMessageBox::Yes)
|
||||
userWantsBootstrap = (ret == QMessageBox::Yes);
|
||||
}
|
||||
|
||||
if (userWantsBootstrap)
|
||||
{
|
||||
std::string host = Bootstrap::DEFAULT_HOST;
|
||||
std::string strError;
|
||||
@@ -271,7 +307,148 @@ bool IntroDialog::pickDataDirectory()
|
||||
progress.setValue(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void copyDirectoryRecursive(const boost::filesystem::path& src,
|
||||
const boost::filesystem::path& dst)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
fs::create_directories(dst);
|
||||
for (fs::directory_iterator it(src), end; it != end; ++it) {
|
||||
fs::path dstChild = dst / it->path().filename();
|
||||
if (fs::is_directory(it->path())) {
|
||||
copyDirectoryRecursive(it->path(), dstChild);
|
||||
} else {
|
||||
fs::copy_file(it->path(), dstChild, fs::copy_options::overwrite_existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IntroDialog::migrateDataDirectory(const QString& oldPath, const QString& newPath)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
fs::path srcDir(oldPath.toStdString());
|
||||
fs::path dstDir(newPath.toStdString());
|
||||
|
||||
if (!fs::exists(srcDir) || !fs::is_directory(srcDir))
|
||||
return false;
|
||||
|
||||
// Create destination directory
|
||||
try {
|
||||
fs::create_directories(dstDir);
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
printf("Migration: Cannot create destination directory: %s\n", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check free space
|
||||
try {
|
||||
quint64 srcSize = 0;
|
||||
for (fs::recursive_directory_iterator it(srcDir), end; it != end; ++it) {
|
||||
if (fs::is_regular_file(*it))
|
||||
srcSize += fs::file_size(*it);
|
||||
}
|
||||
fs::space_info si = fs::space(dstDir);
|
||||
if (si.available < srcSize + (50 * 1024 * 1024)) { // 50MB headroom
|
||||
printf("Migration: Insufficient disk space. Need %llu, have %llu\n",
|
||||
(unsigned long long)srcSize, (unsigned long long)si.available);
|
||||
return false;
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
printf("Migration: Cannot check disk space: %s\n", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Files/directories to skip during copy
|
||||
static const std::set<std::string> skipFiles = {
|
||||
".lock",
|
||||
"debug.log",
|
||||
"db.log",
|
||||
};
|
||||
|
||||
// Show progress dialog
|
||||
QProgressDialog progress("Moving data directory...", QString(), 0, 0, 0);
|
||||
progress.setWindowTitle("Triangles - Data Migration");
|
||||
progress.setWindowModality(Qt::ApplicationModal);
|
||||
progress.setMinimumDuration(0);
|
||||
progress.setCancelButton(0);
|
||||
progress.show();
|
||||
QApplication::processEvents();
|
||||
|
||||
// Phase 1: Copy wallet.dat FIRST (most critical file)
|
||||
fs::path walletSrc = srcDir / "wallet.dat";
|
||||
fs::path walletDst = dstDir / "wallet.dat";
|
||||
if (fs::exists(walletSrc)) {
|
||||
progress.setLabelText("Copying wallet.dat...");
|
||||
QApplication::processEvents();
|
||||
try {
|
||||
// Copy to temp name first, then rename for atomicity
|
||||
fs::path walletTmp = dstDir / "wallet.dat.migrating";
|
||||
fs::copy_file(walletSrc, walletTmp, fs::copy_options::overwrite_existing);
|
||||
|
||||
// Verify copy by checking file size
|
||||
if (fs::file_size(walletTmp) != fs::file_size(walletSrc)) {
|
||||
fs::remove(walletTmp);
|
||||
printf("Migration: wallet.dat copy size mismatch!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rename into place
|
||||
if (fs::exists(walletDst))
|
||||
fs::remove(walletDst);
|
||||
fs::rename(walletTmp, walletDst);
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
printf("Migration: Failed to copy wallet.dat: %s\n", e.what());
|
||||
return false; // Abort - wallet is critical
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Copy everything else
|
||||
int filesCopied = 0;
|
||||
try {
|
||||
for (fs::directory_iterator it(srcDir), end; it != end; ++it) {
|
||||
std::string filename = it->path().filename().string();
|
||||
|
||||
// Skip special files
|
||||
if (skipFiles.count(filename))
|
||||
continue;
|
||||
|
||||
// Skip wallet.dat (already copied)
|
||||
if (filename == "wallet.dat")
|
||||
continue;
|
||||
|
||||
fs::path dst = dstDir / filename;
|
||||
|
||||
progress.setLabelText(QString("Copying %1...").arg(QString::fromStdString(filename)));
|
||||
QApplication::processEvents();
|
||||
|
||||
if (fs::is_directory(it->path())) {
|
||||
copyDirectoryRecursive(it->path(), dst);
|
||||
} else {
|
||||
fs::copy_file(it->path(), dst, fs::copy_options::overwrite_existing);
|
||||
}
|
||||
filesCopied++;
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
// Non-wallet copy failure: log but don't abort
|
||||
// Chain data can be re-synced; wallet was already safely copied
|
||||
printf("Migration: Warning: failed to copy some files: %s\n", e.what());
|
||||
}
|
||||
|
||||
// Phase 3: Rename old wallet.dat as safety backup (don't delete old dir)
|
||||
try {
|
||||
if (fs::exists(walletSrc)) {
|
||||
fs::rename(walletSrc, srcDir / "wallet.dat.bak-migrated");
|
||||
}
|
||||
} catch (...) {
|
||||
// Not critical
|
||||
}
|
||||
|
||||
progress.close();
|
||||
printf("Migration: Successfully copied %d items from %s to %s\n",
|
||||
filesCopied, srcDir.string().c_str(), dstDir.string().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -166,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")
|
||||
@@ -188,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 |
@@ -17,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;
|
||||
|
||||
@@ -190,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);
|
||||
@@ -259,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());
|
||||
@@ -285,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();
|
||||
|
||||
@@ -323,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)
|
||||
@@ -439,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());
|
||||
|
||||
@@ -52,11 +52,13 @@ class TransactionTablePriv
|
||||
public:
|
||||
TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
|
||||
wallet(wallet),
|
||||
parent(parent)
|
||||
parent(parent),
|
||||
fInitialLoadDone(false)
|
||||
{
|
||||
}
|
||||
CWallet *wallet;
|
||||
TransactionTableModel *parent;
|
||||
bool fInitialLoadDone;
|
||||
|
||||
/* Local cache of wallet.
|
||||
* As it is in the same order as the CWallet, by definition
|
||||
@@ -68,22 +70,19 @@ public:
|
||||
*/
|
||||
void refreshWallet()
|
||||
{
|
||||
OutputDebugStringF("refreshWallet\n");
|
||||
OutputDebugStringF("refreshWallet: fInitialLoadDone=%d mapWallet.size=%u\n",
|
||||
(int)fInitialLoadDone, (unsigned)wallet->mapWallet.size());
|
||||
cachedWallet.clear();
|
||||
{
|
||||
TRY_LOCK(wallet->cs_wallet, lockWallet);
|
||||
if(!lockWallet)
|
||||
{
|
||||
// Lock busy (block processing), retry in 500ms
|
||||
QTimer::singleShot(500, parent, SLOT(refreshWallet()));
|
||||
return;
|
||||
}
|
||||
LOCK(wallet->cs_wallet);
|
||||
for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
|
||||
{
|
||||
if(TransactionRecord::showTransaction(it->second))
|
||||
cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
|
||||
}
|
||||
fInitialLoadDone = true;
|
||||
}
|
||||
OutputDebugStringF("refreshWallet: loaded %d transaction records\n", cachedWallet.size());
|
||||
}
|
||||
|
||||
/* Update our model of the wallet incrementally, to synchronize our model of the wallet
|
||||
@@ -95,7 +94,14 @@ public:
|
||||
{
|
||||
OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status);
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
TRY_LOCK(wallet->cs_wallet, lockWallet);
|
||||
if (!lockWallet)
|
||||
{
|
||||
// Lock busy - schedule a full refresh to pick up missed updates.
|
||||
// This avoids silently dropping CT_NEW notifications.
|
||||
QTimer::singleShot(500, parent, SLOT(refreshWallet()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Find transaction in wallet
|
||||
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
|
||||
@@ -190,8 +196,12 @@ public:
|
||||
// simply re-use the cached status.
|
||||
if(rec->statusUpdateNeeded())
|
||||
{
|
||||
// Never block the GUI thread while the core is holding cs_wallet.
|
||||
// If the lock is busy, keep showing the cached status and refresh
|
||||
// it on a later paint/update cycle.
|
||||
TRY_LOCK(wallet->cs_wallet, lockWallet);
|
||||
if (lockWallet)
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
|
||||
|
||||
if(mi != wallet->mapWallet.end())
|
||||
@@ -210,8 +220,14 @@ public:
|
||||
|
||||
QString describe(TransactionRecord *rec)
|
||||
{
|
||||
// Transaction details are generated on demand from wallet/db state.
|
||||
// If the wallet is busy, return a lightweight placeholder instead of
|
||||
// freezing the UI until the lock becomes available.
|
||||
TRY_LOCK(wallet->cs_wallet, lockWallet);
|
||||
if (!lockWallet)
|
||||
return parent->tr("Transaction details are temporarily unavailable while the wallet is busy.");
|
||||
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
|
||||
if(mi != wallet->mapWallet.end())
|
||||
{
|
||||
@@ -231,7 +247,12 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *paren
|
||||
cachedNumBlocks(0)
|
||||
{
|
||||
columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
|
||||
QTimer::singleShot(0, this, SLOT(refreshWallet()));
|
||||
|
||||
// Load transactions synchronously in the constructor so they're
|
||||
// available before the event loop starts. The deferred QTimer approach
|
||||
// was never firing because queued updateTransaction events from sync
|
||||
// would flood the event queue first.
|
||||
priv->refreshWallet();
|
||||
|
||||
QTimer *timer = new QTimer(this);
|
||||
connect(timer, SIGNAL(timeout()), this, SLOT(updateConfirmations()));
|
||||
@@ -255,8 +276,10 @@ void TransactionTableModel::updateTransaction(const QString &hash, int status)
|
||||
|
||||
void TransactionTableModel::refreshWallet()
|
||||
{
|
||||
beginResetModel();
|
||||
priv->refreshWallet();
|
||||
reset();
|
||||
endResetModel();
|
||||
OutputDebugStringF("TransactionTableModel::refreshWallet: rowCount=%d\n", priv->size());
|
||||
}
|
||||
|
||||
void TransactionTableModel::updateConfirmations()
|
||||
|
||||
@@ -34,10 +34,16 @@ TransactionView::TransactionView(QWidget *parent) :
|
||||
QWidget(parent), model(0), transactionProxyModel(0),
|
||||
ui(new Ui::TransactionsPage),
|
||||
transactionView(0),
|
||||
transactionsSortOrderDown(true)
|
||||
transactionsSortOrderDown(true),
|
||||
walletTransactionSyncing(false),
|
||||
transactionSortColumn(TransactionTableModel::Status),
|
||||
transactionSortOrder(Qt::DescendingOrder)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Window);
|
||||
if (parent)
|
||||
setWindowFlags(Qt::Widget);
|
||||
else
|
||||
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Window);
|
||||
// Build filter row
|
||||
dateWidget = ui->dateWidget;
|
||||
dateWidget->addItem(tr("All"), All);
|
||||
@@ -150,7 +156,7 @@ void TransactionView::setModel(WalletModel *model)
|
||||
transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
transactionView->setSortingEnabled(true);
|
||||
transactionView->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder);
|
||||
transactionView->sortByColumn(transactionSortColumn, transactionSortOrder);
|
||||
transactionView->verticalHeader()->hide();
|
||||
|
||||
transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Status, 23);
|
||||
@@ -162,6 +168,9 @@ void TransactionView::setModel(WalletModel *model)
|
||||
transactionView->horizontalHeader()->setSectionResizeMode(TransactionTableModel::ToAddress, QHeaderView::Stretch);
|
||||
#endif
|
||||
transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Amount, 100);
|
||||
|
||||
connect(model, SIGNAL(transactionSyncStateChanged(bool)), this, SLOT(setTransactionSyncState(bool)));
|
||||
setTransactionSyncState(model->isTransactionSyncing());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +281,30 @@ void TransactionView::exportClicked()
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionView::setTransactionSyncState(bool syncing)
|
||||
{
|
||||
walletTransactionSyncing = syncing;
|
||||
if (!transactionProxyModel || !transactionView)
|
||||
return;
|
||||
|
||||
if (syncing)
|
||||
{
|
||||
transactionSortColumn = transactionView->horizontalHeader()->sortIndicatorSection();
|
||||
transactionSortOrder = transactionView->horizontalHeader()->sortIndicatorOrder();
|
||||
transactionProxyModel->setDynamicSortFilter(false);
|
||||
transactionView->setSortingEnabled(false);
|
||||
transactionView->setUpdatesEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
transactionProxyModel->setDynamicSortFilter(true);
|
||||
transactionView->setUpdatesEnabled(true);
|
||||
transactionView->setSortingEnabled(true);
|
||||
transactionProxyModel->invalidate();
|
||||
transactionView->sortByColumn(transactionSortColumn, transactionSortOrder);
|
||||
transactionView->viewport()->update();
|
||||
}
|
||||
|
||||
void TransactionView::contextualMenu(const QPoint &point)
|
||||
{
|
||||
QModelIndex index = transactionView->indexAt(point);
|
||||
|
||||
@@ -48,9 +48,12 @@ public:
|
||||
private:
|
||||
WalletModel *model;
|
||||
TransactionFilterProxy *transactionProxyModel;
|
||||
Ui::TransactionsPage *ui;
|
||||
Ui::TransactionsPage *ui;
|
||||
QTableView *transactionView;
|
||||
bool transactionsSortOrderDown;
|
||||
bool walletTransactionSyncing;
|
||||
int transactionSortColumn;
|
||||
Qt::SortOrder transactionSortOrder;
|
||||
|
||||
QComboBox *dateWidget;
|
||||
QComboBox *typeWidget;
|
||||
@@ -74,6 +77,7 @@ private slots:
|
||||
void copyLabel();
|
||||
void copyAmount();
|
||||
void copyTxID();
|
||||
void setTransactionSyncState(bool syncing);
|
||||
|
||||
signals:
|
||||
void doubleClicked(const QModelIndex&);
|
||||
|
||||
@@ -106,7 +106,9 @@ TrianglesGUI::TrianglesGUI(bool fIsTestnet, QWidget *parent):
|
||||
trayIcon(0),
|
||||
notificator(0),
|
||||
rpcConsole(0),
|
||||
prevBlocks(0)
|
||||
prevBlocks(0),
|
||||
walletTransactionSyncing(false),
|
||||
walletTransactionSyncPending(0)
|
||||
{
|
||||
|
||||
ui->setupUi(this);
|
||||
@@ -282,6 +284,7 @@ TrianglesGUI::TrianglesGUI(bool fIsTestnet, QWidget *parent):
|
||||
createTrayIcon();
|
||||
|
||||
// Create tabs
|
||||
centralWidget = ui->stackedWidget;
|
||||
overviewPage = new OverviewPage();
|
||||
{
|
||||
transactionsPage = new QWidget(this);
|
||||
@@ -294,15 +297,14 @@ TrianglesGUI::TrianglesGUI(bool fIsTestnet, QWidget *parent):
|
||||
vbox->addWidget(transactionView);
|
||||
frameMain->setLayout(vbox);
|
||||
}
|
||||
addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab);
|
||||
addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab, centralWidget);
|
||||
|
||||
receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab);
|
||||
receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, centralWidget);
|
||||
|
||||
sendCoinsPage = 0;
|
||||
messagePage = 0;
|
||||
signMessagePage = 0;
|
||||
verifyMessagePage = 0;
|
||||
centralWidget = ui->stackedWidget;
|
||||
centralWidget->addWidget(overviewPage);
|
||||
centralWidget->addWidget(transactionsPage);
|
||||
centralWidget->addWidget(addressBookPage);
|
||||
@@ -592,6 +594,9 @@ void TrianglesGUI::setWalletModel(WalletModel *walletModel)
|
||||
|
||||
setEncryptionStatus(walletModel->getEncryptionStatus());
|
||||
connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
|
||||
connect(walletModel, SIGNAL(transactionSyncStateChanged(bool)), this, SLOT(setWalletTransactionSyncState(bool)));
|
||||
connect(walletModel, SIGNAL(transactionSyncProgressChanged(bool,int)), this, SLOT(setWalletTransactionSyncProgress(bool,int)));
|
||||
setWalletTransactionSyncState(walletModel->isTransactionSyncing());
|
||||
|
||||
// Balloon pop-up for new transaction
|
||||
connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
|
||||
@@ -636,7 +641,7 @@ void TrianglesGUI::ensureSendCoinsPage()
|
||||
if (sendCoinsPage)
|
||||
return;
|
||||
|
||||
sendCoinsPage = new SendCoinsDialog(this);
|
||||
sendCoinsPage = new SendCoinsDialog(centralWidget);
|
||||
if (walletModel)
|
||||
sendCoinsPage->setModel(walletModel);
|
||||
centralWidget->addWidget(sendCoinsPage);
|
||||
@@ -647,7 +652,7 @@ void TrianglesGUI::ensureMessagePage()
|
||||
if (messagePage)
|
||||
return;
|
||||
|
||||
messagePage = new MessagePage(this);
|
||||
messagePage = new MessagePage(centralWidget);
|
||||
if (messageModel)
|
||||
messagePage->setModel(messageModel);
|
||||
centralWidget->addWidget(messagePage);
|
||||
@@ -658,7 +663,7 @@ void TrianglesGUI::ensureSignMessagePage()
|
||||
if (signMessagePage)
|
||||
return;
|
||||
|
||||
signMessagePage = new SignMessagePage(this);
|
||||
signMessagePage = new SignMessagePage(centralWidget);
|
||||
if (walletModel)
|
||||
signMessagePage->setModel(walletModel);
|
||||
centralWidget->addWidget(signMessagePage);
|
||||
@@ -669,7 +674,7 @@ void TrianglesGUI::ensureVerifyMessagePage()
|
||||
if (verifyMessagePage)
|
||||
return;
|
||||
|
||||
verifyMessagePage = new VerifyMessagePage(this);
|
||||
verifyMessagePage = new VerifyMessagePage(centralWidget);
|
||||
if (walletModel)
|
||||
verifyMessagePage->setModel(walletModel);
|
||||
centralWidget->addWidget(verifyMessagePage);
|
||||
@@ -766,6 +771,32 @@ void TrianglesGUI::restoreWindowGeometry()
|
||||
move(pos);
|
||||
}
|
||||
|
||||
void TrianglesGUI::refreshSyncStatusDisplay()
|
||||
{
|
||||
if (clientModel)
|
||||
{
|
||||
setNumBlocks(clientModel->getNumBlocks(), clientModel->getNumBlocksOfPeers());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!walletTransactionSyncing)
|
||||
{
|
||||
progressBarLabel->setVisible(false);
|
||||
progressBar->setVisible(false);
|
||||
ui->label_blocks->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
progressBarLabel->setText(walletTransactionSyncPending > 0
|
||||
? tr("Updating wallet history... %n change(s) queued", "", walletTransactionSyncPending)
|
||||
: tr("Updating wallet history..."));
|
||||
progressBarLabel->setVisible(true);
|
||||
progressBar->setRange(0, 0);
|
||||
progressBar->setValue(0);
|
||||
progressBar->setVisible(true);
|
||||
ui->label_blocks->setVisible(false);
|
||||
}
|
||||
|
||||
void TrianglesGUI::optionsClicked()
|
||||
{
|
||||
if(!clientModel || !clientModel->getOptionsModel())
|
||||
@@ -803,19 +834,10 @@ void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks)
|
||||
return;
|
||||
|
||||
int nConnections = clientModel->getNumConnections();
|
||||
|
||||
// Hide progress bar when disconnected, but don't return early -
|
||||
// we still need to update sync state and the out-of-sync warning
|
||||
if (nConnections == 0)
|
||||
{
|
||||
progressBarLabel->setVisible(false);
|
||||
progressBar->setVisible(false);
|
||||
ui->label_blocks->setVisible(false);
|
||||
}
|
||||
|
||||
const bool blockSyncActive = nConnections > 0 && count < nTotalBlocks;
|
||||
QString tooltip;
|
||||
|
||||
if(nConnections > 0 && count < nTotalBlocks)
|
||||
if(blockSyncActive)
|
||||
{
|
||||
// Calculate blocks/sec - only update rate when new blocks arrive
|
||||
static int lastCount = 0;
|
||||
@@ -867,10 +889,25 @@ void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks)
|
||||
|
||||
tooltip = tr("Downloaded %1 of %2 blocks of transaction history (%3% done).").arg(count).arg(nTotalBlocks).arg(nPercentageDone, 0, 'f', 2);
|
||||
}
|
||||
else if (walletTransactionSyncing)
|
||||
{
|
||||
progressBarLabel->setText(walletTransactionSyncPending > 0
|
||||
? tr("Updating wallet history... %n change(s) queued", "", walletTransactionSyncPending)
|
||||
: tr("Updating wallet history..."));
|
||||
progressBarLabel->setVisible(true);
|
||||
progressBar->setRange(0, 0);
|
||||
progressBar->setValue(0);
|
||||
progressBar->setVisible(true);
|
||||
ui->label_blocks->setVisible(false);
|
||||
|
||||
tooltip = tr("Wallet history is catching up to recent transactions and stakes.");
|
||||
tooltip += QString("<br>") + (walletTransactionSyncPending > 0
|
||||
? tr("%n wallet update(s) are queued for the UI.", "", walletTransactionSyncPending)
|
||||
: tr("Finalizing the latest wallet updates."));
|
||||
}
|
||||
else
|
||||
{
|
||||
progressBarLabel->setVisible(false);
|
||||
|
||||
progressBar->setVisible(false);
|
||||
ui->label_blocks->setVisible(false);
|
||||
tooltip = tr("Processed %1 blocks of transaction history.").arg(count);
|
||||
@@ -904,9 +941,10 @@ void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks)
|
||||
}
|
||||
|
||||
// Set icon state: spinning if catching up, tick otherwise.
|
||||
// Use a generous threshold (6 hours) for PoS chains where block intervals
|
||||
// can be long during difficulty adjustment with few stakers.
|
||||
if(secs < 6*60*60 && count >= nTotalBlocks)
|
||||
// For PoS chains with few stakers, blocks can be hours or days apart.
|
||||
// Sync status is based purely on block count - NOT block timestamp.
|
||||
// A stale chain (no recent blocks) is still "synced" if we have all blocks.
|
||||
if(count >= nTotalBlocks)
|
||||
{
|
||||
tooltip = tr("Up to date") + QString(".<br>") + tooltip;
|
||||
labelBlocksIcon->setPixmap(QIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
|
||||
@@ -1038,6 +1076,8 @@ void TrianglesGUI::incomingTransaction(const QModelIndex & parent, int start, in
|
||||
{
|
||||
if(!walletModel || !clientModel)
|
||||
return;
|
||||
if(walletTransactionSyncing)
|
||||
return;
|
||||
TransactionTableModel *ttm = walletModel->getTransactionTableModel();
|
||||
qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent)
|
||||
.data(Qt::EditRole).toULongLong();
|
||||
@@ -1069,6 +1109,21 @@ void TrianglesGUI::incomingTransaction(const QModelIndex & parent, int start, in
|
||||
}
|
||||
}
|
||||
|
||||
void TrianglesGUI::setWalletTransactionSyncState(bool syncing)
|
||||
{
|
||||
walletTransactionSyncing = syncing;
|
||||
if (!syncing)
|
||||
walletTransactionSyncPending = 0;
|
||||
refreshSyncStatusDisplay();
|
||||
}
|
||||
|
||||
void TrianglesGUI::setWalletTransactionSyncProgress(bool syncing, int pendingNotifications)
|
||||
{
|
||||
walletTransactionSyncing = syncing;
|
||||
walletTransactionSyncPending = pendingNotifications;
|
||||
refreshSyncStatusDisplay();
|
||||
}
|
||||
|
||||
void TrianglesGUI::incomingMessage(const QModelIndex & parent, int start, int end)
|
||||
{
|
||||
if(!messageModel)
|
||||
|
||||
@@ -143,6 +143,8 @@ private:
|
||||
QMovie *syncIconMovie;
|
||||
/** Keep track of previous number of blocks, to detect progress */
|
||||
int prevBlocks;
|
||||
bool walletTransactionSyncing;
|
||||
int walletTransactionSyncPending;
|
||||
|
||||
/** Create the main UI actions. */
|
||||
void createActions(bool fIsTestnet);
|
||||
@@ -158,6 +160,7 @@ private:
|
||||
void saveWindowGeometry();
|
||||
/** Restore window size and position */
|
||||
void restoreWindowGeometry();
|
||||
void refreshSyncStatusDisplay();
|
||||
|
||||
public slots:
|
||||
/** Set number of connections shown in the UI */
|
||||
@@ -169,6 +172,8 @@ public slots:
|
||||
@see WalletModel::EncryptionStatus
|
||||
*/
|
||||
void setEncryptionStatus(int status);
|
||||
void setWalletTransactionSyncState(bool syncing);
|
||||
void setWalletTransactionSyncProgress(bool syncing, int pendingNotifications);
|
||||
|
||||
/** Notify the user of an error in the network or transaction handling code. */
|
||||
void error(const QString &title, const QString &message, bool modal);
|
||||
|
||||
@@ -8,9 +8,15 @@
|
||||
#include "wallet.h"
|
||||
#include "walletdb.h" // for BackupWallet
|
||||
#include "base58.h"
|
||||
#include "main.h"
|
||||
|
||||
#include <QSet>
|
||||
#include <QTimer>
|
||||
#include <QMutexLocker>
|
||||
|
||||
static const int MODEL_UPDATE_BATCH_THRESHOLD = 128;
|
||||
static const int MODEL_UPDATE_BATCH_DELAY_MS = 250;
|
||||
static const int MODEL_FULL_REFRESH_MIN_INTERVAL_MS = 1500;
|
||||
|
||||
WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent) :
|
||||
QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0),
|
||||
@@ -18,7 +24,12 @@ WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *p
|
||||
cachedBalance(0), cachedStake(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0),
|
||||
cachedNumTransactions(0),
|
||||
cachedEncryptionStatus(Unencrypted),
|
||||
cachedNumBlocks(0)
|
||||
cachedNumBlocks(0),
|
||||
transactionNotificationFlushQueued(false),
|
||||
fullTransactionRefreshQueued(false),
|
||||
transactionSyncing(false),
|
||||
transactionNotificationTimer(0),
|
||||
lastFullTransactionRefreshTime(0)
|
||||
{
|
||||
addressTableModel = new AddressTableModel(wallet, this);
|
||||
transactionTableModel = new TransactionTableModel(wallet, this);
|
||||
@@ -28,6 +39,10 @@ WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *p
|
||||
connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollBalanceChanged()));
|
||||
pollTimer->start(MODEL_UPDATE_DELAY);
|
||||
|
||||
transactionNotificationTimer = new QTimer(this);
|
||||
transactionNotificationTimer->setSingleShot(true);
|
||||
connect(transactionNotificationTimer, SIGNAL(timeout()), this, SLOT(flushTransactionNotifications()));
|
||||
|
||||
subscribeToCoreSignals();
|
||||
}
|
||||
|
||||
@@ -66,6 +81,11 @@ int WalletModel::getNumTransactions() const
|
||||
return numTransactions;
|
||||
}
|
||||
|
||||
bool WalletModel::isTransactionSyncing() const
|
||||
{
|
||||
return transactionSyncing;
|
||||
}
|
||||
|
||||
void WalletModel::updateStatus()
|
||||
{
|
||||
EncryptionStatus newEncryptionStatus = getEncryptionStatus();
|
||||
@@ -78,24 +98,22 @@ void WalletModel::pollBalanceChanged()
|
||||
{
|
||||
if(nBestHeight != cachedNumBlocks)
|
||||
{
|
||||
// Don't block the UI thread waiting for cs_wallet - skip this
|
||||
// update cycle if the lock is held by the block processing thread
|
||||
TRY_LOCK(wallet->cs_wallet, lockWallet);
|
||||
if(!lockWallet)
|
||||
return;
|
||||
|
||||
// Balance and number of transactions might have changed
|
||||
cachedNumBlocks = nBestHeight;
|
||||
checkBalanceChanged();
|
||||
// Balance and number of transactions might have changed.
|
||||
// Only update cachedNumBlocks AFTER a successful balance check,
|
||||
// otherwise a TRY_LOCK failure loses the update permanently.
|
||||
if(checkBalanceChanged())
|
||||
cachedNumBlocks = nBestHeight;
|
||||
}
|
||||
}
|
||||
|
||||
void WalletModel::checkBalanceChanged()
|
||||
bool WalletModel::checkBalanceChanged()
|
||||
{
|
||||
// Get all balances in a single lock acquisition + single pass
|
||||
// instead of 4 separate lock+iterate cycles
|
||||
qint64 newBalance = 0, newStake = 0, newUnconfirmedBalance = 0, newImmatureBalance = 0;
|
||||
wallet->GetAllBalances(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance);
|
||||
// Get all balances in a single lock acquisition + single pass.
|
||||
// Uses TRY_LOCK internally - if cs_wallet is busy (block processing),
|
||||
// skip this cycle. The timer will retry in 2.5 seconds.
|
||||
int64_t newBalance = 0, newStake = 0, newUnconfirmedBalance = 0, newImmatureBalance = 0;
|
||||
if (!wallet->GetAllBalances(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance))
|
||||
return false;
|
||||
|
||||
if(cachedBalance != newBalance || cachedStake != newStake || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance)
|
||||
{
|
||||
@@ -105,21 +123,152 @@ void WalletModel::checkBalanceChanged()
|
||||
cachedImmatureBalance = newImmatureBalance;
|
||||
emit balanceChanged(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WalletModel::updateTransaction(const QString &hash, int status)
|
||||
{
|
||||
if(transactionTableModel)
|
||||
transactionTableModel->updateTransaction(hash, status);
|
||||
queueTransactionUpdate(hash, status);
|
||||
}
|
||||
|
||||
// Balance and number of transactions might have changed
|
||||
checkBalanceChanged();
|
||||
|
||||
int newNumTransactions = getNumTransactions();
|
||||
if(cachedNumTransactions != newNumTransactions)
|
||||
void WalletModel::queueTransactionUpdate(const QString &hash, int status)
|
||||
{
|
||||
bool shouldScheduleFlush = false;
|
||||
{
|
||||
cachedNumTransactions = newNumTransactions;
|
||||
emit numTransactionsChanged(newNumTransactions);
|
||||
QMutexLocker locker(&transactionNotificationMutex);
|
||||
|
||||
int mergedStatus = status;
|
||||
QMap<QString, int>::iterator it = queuedTransactionNotifications.find(hash);
|
||||
if (it != queuedTransactionNotifications.end())
|
||||
{
|
||||
// Preserve insert/delete semantics when multiple updates arrive
|
||||
// for the same transaction before the UI thread drains the queue.
|
||||
if (it.value() == CT_DELETED || status == CT_DELETED)
|
||||
mergedStatus = CT_DELETED;
|
||||
else if (it.value() == CT_NEW || status == CT_NEW)
|
||||
mergedStatus = CT_NEW;
|
||||
else
|
||||
mergedStatus = CT_UPDATED;
|
||||
it.value() = mergedStatus;
|
||||
}
|
||||
else
|
||||
{
|
||||
queuedTransactionNotifications.insert(hash, mergedStatus);
|
||||
}
|
||||
|
||||
if (IsInitialBlockDownload() || queuedTransactionNotifications.size() >= MODEL_UPDATE_BATCH_THRESHOLD)
|
||||
fullTransactionRefreshQueued = true;
|
||||
|
||||
if (!transactionNotificationFlushQueued)
|
||||
{
|
||||
transactionNotificationFlushQueued = true;
|
||||
shouldScheduleFlush = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldScheduleFlush)
|
||||
QMetaObject::invokeMethod(this, "startTransactionNotificationTimer", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void WalletModel::startTransactionNotificationTimer()
|
||||
{
|
||||
if (transactionNotificationTimer)
|
||||
transactionNotificationTimer->start(MODEL_UPDATE_BATCH_DELAY_MS);
|
||||
}
|
||||
|
||||
void WalletModel::flushTransactionNotifications()
|
||||
{
|
||||
QMap<QString, int> pendingNotifications;
|
||||
bool refreshAll = false;
|
||||
bool shouldRescheduleRefresh = false;
|
||||
{
|
||||
QMutexLocker locker(&transactionNotificationMutex);
|
||||
pendingNotifications.swap(queuedTransactionNotifications);
|
||||
refreshAll = fullTransactionRefreshQueued;
|
||||
fullTransactionRefreshQueued = false;
|
||||
transactionNotificationFlushQueued = false;
|
||||
}
|
||||
|
||||
const bool shouldSync = refreshAll ||
|
||||
IsInitialBlockDownload() ||
|
||||
pendingNotifications.size() >= MODEL_UPDATE_BATCH_THRESHOLD;
|
||||
if (transactionSyncing != shouldSync)
|
||||
{
|
||||
transactionSyncing = shouldSync;
|
||||
emit transactionSyncStateChanged(transactionSyncing);
|
||||
}
|
||||
|
||||
if(transactionTableModel)
|
||||
{
|
||||
if (refreshAll)
|
||||
{
|
||||
const qint64 now = GetTimeMillis();
|
||||
if (transactionSyncing &&
|
||||
lastFullTransactionRefreshTime != 0 &&
|
||||
now - lastFullTransactionRefreshTime < MODEL_FULL_REFRESH_MIN_INTERVAL_MS)
|
||||
{
|
||||
QMutexLocker locker(&transactionNotificationMutex);
|
||||
fullTransactionRefreshQueued = true;
|
||||
if (!transactionNotificationFlushQueued)
|
||||
{
|
||||
transactionNotificationFlushQueued = true;
|
||||
shouldRescheduleRefresh = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
transactionTableModel->refreshWallet();
|
||||
lastFullTransactionRefreshTime = now;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (QMap<QString, int>::const_iterator it = pendingNotifications.begin(); it != pendingNotifications.end(); ++it)
|
||||
transactionTableModel->updateTransaction(it.key(), it.value());
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRescheduleRefresh && transactionNotificationTimer)
|
||||
transactionNotificationTimer->start(MODEL_FULL_REFRESH_MIN_INTERVAL_MS);
|
||||
|
||||
bool stillPending = false;
|
||||
int queuedNotificationCount = 0;
|
||||
{
|
||||
QMutexLocker locker(&transactionNotificationMutex);
|
||||
stillPending = transactionNotificationFlushQueued || !queuedTransactionNotifications.isEmpty();
|
||||
queuedNotificationCount = queuedTransactionNotifications.size();
|
||||
}
|
||||
if (!transactionSyncing && pendingNotifications.size() >= MODEL_UPDATE_BATCH_THRESHOLD)
|
||||
{
|
||||
transactionSyncing = true;
|
||||
emit transactionSyncStateChanged(true);
|
||||
}
|
||||
|
||||
if (transactionSyncing && !IsInitialBlockDownload() && !stillPending)
|
||||
{
|
||||
transactionSyncing = false;
|
||||
emit transactionSyncStateChanged(false);
|
||||
}
|
||||
|
||||
const int pendingNotificationCount = transactionSyncing
|
||||
? pendingNotifications.size() + queuedNotificationCount
|
||||
: queuedNotificationCount;
|
||||
emit transactionSyncProgressChanged(transactionSyncing, pendingNotificationCount);
|
||||
|
||||
// Don't call checkBalanceChanged() here - it does LOCK(cs_wallet) + iterates
|
||||
// all wallet transactions, blocking the UI thread. The pollBalanceChanged()
|
||||
// timer already handles balance updates every 2.5 seconds with TRY_LOCK.
|
||||
|
||||
// Same for getNumTransactions() - use cached count from the transaction model
|
||||
// to avoid another LOCK(cs_wallet) on the UI thread.
|
||||
if(transactionTableModel)
|
||||
{
|
||||
int newNumTransactions = transactionTableModel->rowCount(QModelIndex());
|
||||
if(cachedNumTransactions != newNumTransactions)
|
||||
{
|
||||
cachedNumTransactions = newNumTransactions;
|
||||
emit numTransactionsChanged(newNumTransactions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,9 +536,7 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet,
|
||||
static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status)
|
||||
{
|
||||
OutputDebugStringF("NotifyTransactionChanged %s status=%i\n", hash.GetHex().c_str(), status);
|
||||
QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection,
|
||||
Q_ARG(QString, QString::fromStdString(hash.GetHex())),
|
||||
Q_ARG(int, status));
|
||||
walletmodel->queueTransactionUpdate(QString::fromStdString(hash.GetHex()), status);
|
||||
}
|
||||
|
||||
void WalletModel::subscribeToCoreSignals()
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <QObject>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
|
||||
#include "allocators.h" /* for SecureString */
|
||||
|
||||
@@ -72,6 +74,7 @@ public:
|
||||
qint64 getImmatureBalance() const;
|
||||
int getNumTransactions() const;
|
||||
EncryptionStatus getEncryptionStatus() const;
|
||||
bool isTransactionSyncing() const;
|
||||
|
||||
// Check address for validity
|
||||
bool validateAddress(const QString &address);
|
||||
@@ -152,7 +155,7 @@ private:
|
||||
|
||||
void subscribeToCoreSignals();
|
||||
void unsubscribeFromCoreSignals();
|
||||
void checkBalanceChanged();
|
||||
bool checkBalanceChanged();
|
||||
|
||||
|
||||
public slots:
|
||||
@@ -160,10 +163,16 @@ public slots:
|
||||
void updateStatus();
|
||||
/* New transaction, or transaction changed status */
|
||||
void updateTransaction(const QString &hash, int status);
|
||||
/* Queue a transaction update from a core thread without touching the UI directly */
|
||||
void queueTransactionUpdate(const QString &hash, int status);
|
||||
/* New, updated or removed address book entry */
|
||||
void updateAddressBook(const QString &address, const QString &label, bool isMine, int status);
|
||||
/* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */
|
||||
void pollBalanceChanged();
|
||||
/* Start or restart the deferred transaction notification flush timer */
|
||||
void startTransactionNotificationTimer();
|
||||
/* Flush queued transaction notifications from core threads */
|
||||
void flushTransactionNotifications();
|
||||
|
||||
signals:
|
||||
// Signal that balance in wallet changed
|
||||
@@ -182,6 +191,17 @@ signals:
|
||||
|
||||
// Asynchronous error notification
|
||||
void error(const QString &title, const QString &message, bool modal);
|
||||
void transactionSyncStateChanged(bool syncing);
|
||||
void transactionSyncProgressChanged(bool syncing, int pendingNotifications);
|
||||
|
||||
private:
|
||||
QMutex transactionNotificationMutex;
|
||||
QMap<QString, int> queuedTransactionNotifications;
|
||||
bool transactionNotificationFlushQueued;
|
||||
bool fullTransactionRefreshQueued;
|
||||
bool transactionSyncing;
|
||||
QTimer *transactionNotificationTimer;
|
||||
qint64 lastFullTransactionRefreshTime;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -108,438 +108,10 @@ Value getstakinginfo(const Array& params, bool fHelp)
|
||||
return obj;
|
||||
}
|
||||
|
||||
Value getworkex(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() > 2)
|
||||
throw runtime_error(
|
||||
"getworkex [data, coinbase]\n"
|
||||
"If [data, coinbase] is not specified, returns extended work data.\n"
|
||||
);
|
||||
|
||||
if (vNodes.empty())
|
||||
throw JSONRPCError(-9, "Triangles is not connected!");
|
||||
|
||||
if (IsInitialBlockDownload())
|
||||
throw JSONRPCError(-10, "Triangles is downloading blocks...");
|
||||
|
||||
if (pindexBest->nHeight >= CUTOFF_POW_BLOCK)
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "No more PoW blocks");
|
||||
|
||||
typedef map<uint256, pair<CBlock*, CScript> > mapNewBlock_t;
|
||||
static mapNewBlock_t mapNewBlock;
|
||||
static vector<CBlock*> vNewBlock;
|
||||
static CReserveKey reservekey(pwalletMain);
|
||||
|
||||
if (params.size() == 0)
|
||||
{
|
||||
// Update block
|
||||
static unsigned int nTransactionsUpdatedLast;
|
||||
static CBlockIndex* pindexPrev;
|
||||
static int64_t nStart;
|
||||
static CBlock* pblock;
|
||||
if (pindexPrev != pindexBest ||
|
||||
(nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60))
|
||||
{
|
||||
if (pindexPrev != pindexBest)
|
||||
{
|
||||
// Deallocate old blocks since they're obsolete now
|
||||
mapNewBlock.clear();
|
||||
for (CBlock* pblock : vNewBlock)
|
||||
delete pblock;
|
||||
vNewBlock.clear();
|
||||
}
|
||||
nTransactionsUpdatedLast = nTransactionsUpdated;
|
||||
pindexPrev = pindexBest;
|
||||
nStart = GetTime();
|
||||
|
||||
// Create new block
|
||||
pblock = CreateNewBlock(pwalletMain);
|
||||
if (!pblock)
|
||||
throw JSONRPCError(-7, "Out of memory");
|
||||
vNewBlock.push_back(pblock);
|
||||
}
|
||||
|
||||
// Update nTime
|
||||
pblock->nTime = max(pindexPrev->GetPastTimeLimit()+1, GetAdjustedTime());
|
||||
pblock->nNonce = 0;
|
||||
|
||||
// Update nExtraNonce
|
||||
static unsigned int nExtraNonce = 0;
|
||||
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
|
||||
|
||||
// Save
|
||||
mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, pblock->vtx[0].vin[0].scriptSig);
|
||||
|
||||
// Prebuild hash buffers
|
||||
char pmidstate[32];
|
||||
char pdata[128];
|
||||
char phash1[64];
|
||||
FormatHashBuffers(pblock, pmidstate, pdata, phash1);
|
||||
|
||||
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
|
||||
|
||||
CTransaction coinbaseTx = pblock->vtx[0];
|
||||
std::vector<uint256> merkle = pblock->GetMerkleBranch(0);
|
||||
|
||||
Object result;
|
||||
result.push_back(Pair("data", HexStr(BEGIN(pdata), END(pdata))));
|
||||
result.push_back(Pair("target", HexStr(BEGIN(hashTarget), END(hashTarget))));
|
||||
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << coinbaseTx;
|
||||
result.push_back(Pair("coinbase", HexStr(ssTx.begin(), ssTx.end())));
|
||||
|
||||
Array merkle_arr;
|
||||
|
||||
for (uint256 merkleh : merkle) {
|
||||
merkle_arr.push_back(HexStr(BEGIN(merkleh), END(merkleh)));
|
||||
}
|
||||
|
||||
result.push_back(Pair("merkle", merkle_arr));
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse parameters
|
||||
vector<unsigned char> vchData = ParseHex(params[0].get_str());
|
||||
vector<unsigned char> coinbase;
|
||||
|
||||
if(params.size() == 2)
|
||||
coinbase = ParseHex(params[1].get_str());
|
||||
|
||||
if (vchData.size() != 128)
|
||||
throw JSONRPCError(-8, "Invalid parameter");
|
||||
|
||||
CBlock* pdata = (CBlock*)&vchData[0];
|
||||
|
||||
// Byte reverse
|
||||
for (int i = 0; i < 128/4; i++)
|
||||
((unsigned int*)pdata)[i] = ByteReverse(((unsigned int*)pdata)[i]);
|
||||
|
||||
// Get saved block
|
||||
if (!mapNewBlock.count(pdata->hashMerkleRoot))
|
||||
return false;
|
||||
CBlock* pblock = mapNewBlock[pdata->hashMerkleRoot].first;
|
||||
|
||||
pblock->nTime = pdata->nTime;
|
||||
pblock->nNonce = pdata->nNonce;
|
||||
|
||||
if(coinbase.size() == 0)
|
||||
pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second;
|
||||
else
|
||||
// Deserialize custom coinbase transaction from miner
|
||||
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0];
|
||||
|
||||
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
|
||||
|
||||
return CheckWork(pblock, *pwalletMain, reservekey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Value getwork(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() > 1)
|
||||
throw runtime_error(
|
||||
"getwork [data]\n"
|
||||
"If [data] is not specified, returns formatted hash data to work on:\n"
|
||||
" \"midstate\" : precomputed hash state after hashing the first half of the data (DEPRECATED)\n" // deprecated
|
||||
" \"data\" : block data\n"
|
||||
" \"hash1\" : formatted hash buffer for second hash (DEPRECATED)\n" // deprecated
|
||||
" \"target\" : little endian hash target\n"
|
||||
"If [data] is specified, tries to solve the block and returns true if it was successful.");
|
||||
|
||||
if (vNodes.empty())
|
||||
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Triangles is not connected!");
|
||||
|
||||
if (IsInitialBlockDownload())
|
||||
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Triangles is downloading blocks...");
|
||||
|
||||
if (pindexBest->nHeight >= CUTOFF_POW_BLOCK)
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "No more PoW blocks");
|
||||
|
||||
typedef map<uint256, pair<CBlock*, CScript> > mapNewBlock_t;
|
||||
// NOTE: Thread safety issue - static variables accessed by multiple RPC threads
|
||||
// without mutex protection. Low priority since PoW ended at block 9000 and
|
||||
// getwork is rarely used. Consider adding std::mutex if usage increases.
|
||||
static mapNewBlock_t mapNewBlock;
|
||||
static vector<CBlock*> vNewBlock;
|
||||
static CReserveKey reservekey(pwalletMain);
|
||||
|
||||
if (params.size() == 0)
|
||||
{
|
||||
// Update block
|
||||
static unsigned int nTransactionsUpdatedLast;
|
||||
static CBlockIndex* pindexPrev;
|
||||
static int64_t nStart;
|
||||
static CBlock* pblock;
|
||||
if (pindexPrev != pindexBest ||
|
||||
(nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60))
|
||||
{
|
||||
if (pindexPrev != pindexBest)
|
||||
{
|
||||
// Deallocate old blocks since they're obsolete now
|
||||
mapNewBlock.clear();
|
||||
for (CBlock* pblock : vNewBlock)
|
||||
delete pblock;
|
||||
vNewBlock.clear();
|
||||
}
|
||||
|
||||
// Clear pindexPrev so future getworks make a new block, despite any failures from here on
|
||||
pindexPrev = NULL;
|
||||
|
||||
// Store the pindexBest used before CreateNewBlock, to avoid races
|
||||
nTransactionsUpdatedLast = nTransactionsUpdated;
|
||||
CBlockIndex* pindexPrevNew = pindexBest;
|
||||
nStart = GetTime();
|
||||
|
||||
// Create new block
|
||||
pblock = CreateNewBlock(pwalletMain);
|
||||
if (!pblock)
|
||||
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
|
||||
vNewBlock.push_back(pblock);
|
||||
|
||||
// Need to update only after we know CreateNewBlock succeeded
|
||||
pindexPrev = pindexPrevNew;
|
||||
}
|
||||
|
||||
// Update nTime
|
||||
pblock->UpdateTime(pindexPrev);
|
||||
pblock->nNonce = 0;
|
||||
|
||||
// Update nExtraNonce
|
||||
static unsigned int nExtraNonce = 0;
|
||||
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
|
||||
|
||||
// Save
|
||||
mapNewBlock[pblock->hashMerkleRoot] = make_pair(pblock, pblock->vtx[0].vin[0].scriptSig);
|
||||
|
||||
// Pre-build hash buffers
|
||||
char pmidstate[32];
|
||||
char pdata[128];
|
||||
char phash1[64];
|
||||
FormatHashBuffers(pblock, pmidstate, pdata, phash1);
|
||||
|
||||
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
|
||||
|
||||
Object result;
|
||||
result.push_back(Pair("midstate", HexStr(BEGIN(pmidstate), END(pmidstate)))); // deprecated
|
||||
result.push_back(Pair("data", HexStr(BEGIN(pdata), END(pdata))));
|
||||
result.push_back(Pair("hash1", HexStr(BEGIN(phash1), END(phash1)))); // deprecated
|
||||
result.push_back(Pair("target", HexStr(BEGIN(hashTarget), END(hashTarget))));
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse parameters
|
||||
vector<unsigned char> vchData = ParseHex(params[0].get_str());
|
||||
if (vchData.size() != 128)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
|
||||
CBlock* pdata = (CBlock*)&vchData[0];
|
||||
|
||||
// Byte reverse
|
||||
for (int i = 0; i < 128/4; i++)
|
||||
((unsigned int*)pdata)[i] = ByteReverse(((unsigned int*)pdata)[i]);
|
||||
|
||||
// Get saved block
|
||||
if (!mapNewBlock.count(pdata->hashMerkleRoot))
|
||||
return false;
|
||||
CBlock* pblock = mapNewBlock[pdata->hashMerkleRoot].first;
|
||||
|
||||
pblock->nTime = pdata->nTime;
|
||||
pblock->nNonce = pdata->nNonce;
|
||||
pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second;
|
||||
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
|
||||
|
||||
return CheckWork(pblock, *pwalletMain, reservekey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Value getblocktemplate(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() > 1)
|
||||
throw runtime_error(
|
||||
"getblocktemplate [params]\n"
|
||||
"Returns data needed to construct a block to work on:\n"
|
||||
" \"version\" : block version\n"
|
||||
" \"previousblockhash\" : hash of current highest block\n"
|
||||
" \"transactions\" : contents of non-coinbase transactions that should be included in the next block\n"
|
||||
" \"coinbaseaux\" : data that should be included in coinbase\n"
|
||||
" \"coinbasevalue\" : maximum allowable input to coinbase transaction, including the generation award and transaction fees\n"
|
||||
" \"target\" : hash target\n"
|
||||
" \"mintime\" : minimum timestamp appropriate for next block\n"
|
||||
" \"curtime\" : current timestamp\n"
|
||||
" \"mutable\" : list of ways the block template may be changed\n"
|
||||
" \"noncerange\" : range of valid nonces\n"
|
||||
" \"sigoplimit\" : limit of sigops in blocks\n"
|
||||
" \"sizelimit\" : limit of block size\n"
|
||||
" \"bits\" : compressed target of next block\n"
|
||||
" \"height\" : height of the next block");
|
||||
|
||||
std::string strMode = "template";
|
||||
if (params.size() > 0)
|
||||
{
|
||||
const Object& oparam = params[0].get_obj();
|
||||
const Value& modeval = find_value(oparam, "mode");
|
||||
if (modeval.type() == str_type)
|
||||
strMode = modeval.get_str();
|
||||
else if (modeval.type() == null_type)
|
||||
{
|
||||
/* Do nothing */
|
||||
}
|
||||
else
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
|
||||
}
|
||||
|
||||
if (strMode != "template")
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
|
||||
|
||||
if (vNodes.empty())
|
||||
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Triangles is not connected!");
|
||||
|
||||
if (IsInitialBlockDownload())
|
||||
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Triangles is downloading blocks...");
|
||||
|
||||
if (pindexBest->nHeight >= CUTOFF_POW_BLOCK)
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "No more PoW blocks");
|
||||
|
||||
static CReserveKey reservekey(pwalletMain);
|
||||
|
||||
// Update block
|
||||
static unsigned int nTransactionsUpdatedLast;
|
||||
static CBlockIndex* pindexPrev;
|
||||
static int64_t nStart;
|
||||
static CBlock* pblock;
|
||||
if (pindexPrev != pindexBest ||
|
||||
(nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 5))
|
||||
{
|
||||
// Clear pindexPrev so future calls make a new block, despite any failures from here on
|
||||
pindexPrev = NULL;
|
||||
|
||||
// Store the pindexBest used before CreateNewBlock, to avoid races
|
||||
nTransactionsUpdatedLast = nTransactionsUpdated;
|
||||
CBlockIndex* pindexPrevNew = pindexBest;
|
||||
nStart = GetTime();
|
||||
|
||||
// Create new block
|
||||
if(pblock)
|
||||
{
|
||||
delete pblock;
|
||||
pblock = NULL;
|
||||
}
|
||||
pblock = CreateNewBlock(pwalletMain);
|
||||
if (!pblock)
|
||||
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
|
||||
|
||||
// Need to update only after we know CreateNewBlock succeeded
|
||||
pindexPrev = pindexPrevNew;
|
||||
}
|
||||
|
||||
// Update nTime
|
||||
pblock->UpdateTime(pindexPrev);
|
||||
pblock->nNonce = 0;
|
||||
|
||||
Array transactions;
|
||||
map<uint256, int64_t> setTxIndex;
|
||||
int i = 0;
|
||||
CTxDB txdb("r");
|
||||
for (CTransaction& tx : pblock->vtx)
|
||||
{
|
||||
uint256 txHash = tx.GetHash();
|
||||
setTxIndex[txHash] = i++;
|
||||
|
||||
if (tx.IsCoinBase() || tx.IsCoinStake())
|
||||
continue;
|
||||
|
||||
Object entry;
|
||||
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << tx;
|
||||
entry.push_back(Pair("data", HexStr(ssTx.begin(), ssTx.end())));
|
||||
|
||||
entry.push_back(Pair("hash", txHash.GetHex()));
|
||||
|
||||
MapPrevTx mapInputs;
|
||||
map<uint256, CTxIndex> mapUnused;
|
||||
bool fInvalid = false;
|
||||
if (tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid))
|
||||
{
|
||||
entry.push_back(Pair("fee", (int64_t)(tx.GetValueIn(mapInputs) - tx.GetValueOut())));
|
||||
|
||||
Array deps;
|
||||
for (MapPrevTx::value_type& inp : mapInputs)
|
||||
{
|
||||
if (setTxIndex.count(inp.first))
|
||||
deps.push_back(setTxIndex[inp.first]);
|
||||
}
|
||||
entry.push_back(Pair("depends", deps));
|
||||
|
||||
int64_t nSigOps = tx.GetLegacySigOpCount();
|
||||
nSigOps += tx.GetP2SHSigOpCount(mapInputs);
|
||||
entry.push_back(Pair("sigops", nSigOps));
|
||||
}
|
||||
|
||||
transactions.push_back(entry);
|
||||
}
|
||||
|
||||
Object aux;
|
||||
aux.push_back(Pair("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end())));
|
||||
|
||||
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
|
||||
|
||||
static Array aMutable;
|
||||
if (aMutable.empty())
|
||||
{
|
||||
aMutable.push_back("time");
|
||||
aMutable.push_back("transactions");
|
||||
aMutable.push_back("prevblock");
|
||||
}
|
||||
|
||||
Object result;
|
||||
result.push_back(Pair("version", pblock->nVersion));
|
||||
result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex()));
|
||||
result.push_back(Pair("transactions", transactions));
|
||||
result.push_back(Pair("coinbaseaux", aux));
|
||||
result.push_back(Pair("coinbasevalue", (int64_t)pblock->vtx[0].vout[0].nValue));
|
||||
result.push_back(Pair("target", hashTarget.GetHex()));
|
||||
result.push_back(Pair("mintime", (int64_t)pindexPrev->GetPastTimeLimit()+1));
|
||||
result.push_back(Pair("mutable", aMutable));
|
||||
result.push_back(Pair("noncerange", "00000000ffffffff"));
|
||||
result.push_back(Pair("sigoplimit", (int64_t)MAX_BLOCK_SIGOPS));
|
||||
result.push_back(Pair("sizelimit", (int64_t)MAX_BLOCK_SIZE));
|
||||
result.push_back(Pair("curtime", (int64_t)pblock->nTime));
|
||||
result.push_back(Pair("bits", HexBits(pblock->nBits)));
|
||||
result.push_back(Pair("height", (int64_t)(pindexPrev->nHeight+1)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Value submitblock(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() < 1 || params.size() > 2)
|
||||
throw runtime_error(
|
||||
"submitblock <hex data> [optional-params-obj]\n"
|
||||
"[optional-params-obj] parameter is currently ignored.\n"
|
||||
"Attempts to submit new block to network.");
|
||||
|
||||
vector<unsigned char> blockData(ParseHex(params[0].get_str()));
|
||||
CDataStream ssBlock(blockData, SER_NETWORK, PROTOCOL_VERSION);
|
||||
CBlock block;
|
||||
try {
|
||||
ssBlock >> block;
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");
|
||||
}
|
||||
|
||||
bool fAccepted = ProcessBlock(NULL, &block);
|
||||
if (!fAccepted)
|
||||
return "rejected";
|
||||
|
||||
return Value::null;
|
||||
}
|
||||
// PoW mining RPCs (getwork, getworkex, getblocktemplate, submitblock) removed.
|
||||
// PoW ended at block 9000 (CUTOFF_POW_BLOCK). These dead-code mining pool
|
||||
// interfaces were removed to reduce false-positive antivirus detections,
|
||||
// since AV engines pattern-match nonce-incrementing loops and mining pool
|
||||
// protocols as "cryptominer" signatures.
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "net.h"
|
||||
#include "addrman.h"
|
||||
#include "trianglesrpc.h"
|
||||
#include "alert.h"
|
||||
#include "wallet.h"
|
||||
#include "db.h"
|
||||
#include "walletdb.h"
|
||||
#include "net_bootstrap.h"
|
||||
|
||||
using namespace json_spirit;
|
||||
using namespace std;
|
||||
@@ -17,10 +19,20 @@ Value getnetworkinfo(const Array& params, bool fHelp)
|
||||
if (fHelp || params.size() != 0)
|
||||
throw runtime_error(
|
||||
"getnetworkinfo\n"
|
||||
"Returns an object containing various state info regarding P2P networking.");
|
||||
"Returns an object containing various state info regarding P2P networking,\n"
|
||||
"including peer mix, bootstrap mode, and basic sync health.");
|
||||
|
||||
proxyType proxy;
|
||||
GetProxy(NET_IPV4, proxy);
|
||||
const NetBootstrap::NetworkHealth health = NetBootstrap::GetNetworkHealth();
|
||||
|
||||
Object healthObj;
|
||||
healthObj.push_back(Pair("connectedpeers", health.connectedPeers));
|
||||
healthObj.push_back(Pair("torpeers", health.torPeers));
|
||||
healthObj.push_back(Pair("bootstrapped", health.isBootstrapped));
|
||||
healthObj.push_back(Pair("syncing", health.isSyncing));
|
||||
healthObj.push_back(Pair("lastblocktime", static_cast<boost::int64_t>(health.lastBlockTime)));
|
||||
healthObj.push_back(Pair("networkmode", "tor_native"));
|
||||
|
||||
Object obj;
|
||||
obj.push_back(Pair("version", FormatFullVersion()));
|
||||
@@ -30,6 +42,7 @@ Value getnetworkinfo(const Array& params, bool fHelp)
|
||||
obj.push_back(Pair("ip", addrSeenByPeer.ToStringIP()));
|
||||
obj.push_back(Pair("localservices", strprintf("%016"PRIx64, nLocalServices)));
|
||||
obj.push_back(Pair("testnet", fTestNet));
|
||||
obj.push_back(Pair("networkhealth", healthObj));
|
||||
obj.push_back(Pair("errors", GetWarnings("statusbar")));
|
||||
return obj;
|
||||
}
|
||||
@@ -155,3 +168,28 @@ Value sendalert(const Array& params, bool fHelp)
|
||||
result.push_back(Pair("nCancel", alert.nCancel));
|
||||
return result;
|
||||
}
|
||||
|
||||
Value getseedlist(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 0)
|
||||
throw runtime_error(
|
||||
"getseedlist\n"
|
||||
"Returns known .onion peer addresses from the address manager.\n"
|
||||
"Used by the seed collector to build the dynamic seed list.");
|
||||
|
||||
vector<CAddress> vAddr = addrman.GetAddr();
|
||||
Array ret;
|
||||
|
||||
for (const CAddress& addr : vAddr) {
|
||||
if (!addr.IsTor())
|
||||
continue;
|
||||
|
||||
Object obj;
|
||||
obj.push_back(Pair("address", addr.ToStringIP()));
|
||||
obj.push_back(Pair("port", (int)addr.GetPort()));
|
||||
obj.push_back(Pair("lastseen", (boost::int64_t)addr.nTime));
|
||||
ret.push_back(obj);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -370,20 +370,20 @@ Value signrawtransaction(const Array& params, bool fHelp)
|
||||
{
|
||||
CTransaction tempTx;
|
||||
MapPrevTx mapPrevTx;
|
||||
MapPrevTx mapEmpty;
|
||||
CTxDB txdb("r");
|
||||
map<uint256, CTxIndex> unused;
|
||||
bool fInvalid;
|
||||
|
||||
// FetchInputs aborts on failure, so we go one at a time.
|
||||
tempTx.vin.push_back(mergedTx.vin[i]);
|
||||
tempTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid);
|
||||
tempTx.FetchInputs(txdb, mapEmpty, false, false, mapPrevTx, fInvalid);
|
||||
|
||||
// Copy results into mapPrevOut:
|
||||
for (const CTxIn& txin : tempTx.vin)
|
||||
{
|
||||
const uint256& prevHash = txin.prevout.hash;
|
||||
if (mapPrevTx.count(prevHash) && mapPrevTx[prevHash].second.vout.size()>txin.prevout.n)
|
||||
mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey;
|
||||
MapPrevTx::const_iterator mi = mapPrevTx.find(txin.prevout);
|
||||
if (mi != mapPrevTx.end())
|
||||
mapPrevOut[txin.prevout] = mi->second.scriptPubKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1471,9 +1471,7 @@ Value walletpassphrase(const Array& params, bool fHelp)
|
||||
// Note that the walletpassphrase is stored in params[0] which is not mlock()ed
|
||||
SecureString strWalletPass;
|
||||
strWalletPass.reserve(100);
|
||||
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
|
||||
// Alternately, find a way to make params[0] mlock()'d to begin with.
|
||||
strWalletPass = params[0].get_str().c_str();
|
||||
strWalletPass = MakeSecureString(params[0].get_str());
|
||||
|
||||
if (strWalletPass.length() > 0)
|
||||
{
|
||||
@@ -1510,15 +1508,13 @@ Value walletpassphrasechange(const Array& params, bool fHelp)
|
||||
if (!pwalletMain->IsCrypted())
|
||||
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.");
|
||||
|
||||
// TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string)
|
||||
// Alternately, find a way to make params[0] mlock()'d to begin with.
|
||||
SecureString strOldWalletPass;
|
||||
strOldWalletPass.reserve(100);
|
||||
strOldWalletPass = params[0].get_str().c_str();
|
||||
strOldWalletPass = MakeSecureString(params[0].get_str());
|
||||
|
||||
SecureString strNewWalletPass;
|
||||
strNewWalletPass.reserve(100);
|
||||
strNewWalletPass = params[1].get_str().c_str();
|
||||
strNewWalletPass = MakeSecureString(params[1].get_str());
|
||||
|
||||
if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1)
|
||||
throw runtime_error(
|
||||
@@ -1566,11 +1562,9 @@ Value encryptwallet(const Array& params, bool fHelp)
|
||||
if (pwalletMain->IsCrypted())
|
||||
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");
|
||||
|
||||
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
|
||||
// Alternately, find a way to make params[0] mlock()'d to begin with.
|
||||
SecureString strWalletPass;
|
||||
strWalletPass.reserve(100);
|
||||
strWalletPass = params[0].get_str().c_str();
|
||||
strWalletPass = MakeSecureString(params[0].get_str());
|
||||
|
||||
if (strWalletPass.length() < 1)
|
||||
throw runtime_error(
|
||||
@@ -1917,7 +1911,8 @@ Value clearwallettransactions(const Array& params, bool fHelp)
|
||||
};
|
||||
|
||||
pwalletMain->mapWallet.erase(hash);
|
||||
pwalletMain->NotifyTransactionChanged(pwalletMain, hash, CT_DELETED);
|
||||
try { pwalletMain->NotifyTransactionChanged(pwalletMain, hash, CT_DELETED); }
|
||||
catch (...) { }
|
||||
|
||||
nTransactions++;
|
||||
};
|
||||
|
||||
@@ -29,8 +29,12 @@ Notes:
|
||||
|
||||
#include "smessage.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
@@ -95,6 +99,187 @@ leveldb::DB *smsgDB = NULL;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
const long int SMSG_BUCKET_FILE_SIZE_LIMIT = 0x70000000L;
|
||||
const int64_t SMSG_THREAD_SHUTDOWN_WAIT_MS = 5000;
|
||||
const int64_t SMSG_THREAD_SHUTDOWN_POLL_MS = 50;
|
||||
|
||||
std::atomic<int> nSecureMsgThreadsRunning(0);
|
||||
|
||||
class CSecureMsgThreadGuard
|
||||
{
|
||||
public:
|
||||
CSecureMsgThreadGuard()
|
||||
{
|
||||
++nSecureMsgThreadsRunning;
|
||||
};
|
||||
|
||||
~CSecureMsgThreadGuard()
|
||||
{
|
||||
--nSecureMsgThreadsRunning;
|
||||
};
|
||||
};
|
||||
|
||||
bool SecureMsgAllDigits(const std::string& value)
|
||||
{
|
||||
if (value.empty())
|
||||
return false;
|
||||
|
||||
for (std::string::const_iterator it = value.begin(); it != value.end(); ++it)
|
||||
{
|
||||
if (!std::isdigit((unsigned char) *it))
|
||||
return false;
|
||||
};
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
bool SecureMsgParseBucketFilename(const std::string& fileName, int64_t& bucket, uint32_t& fileIndex, bool& fWalletLocked)
|
||||
{
|
||||
if (!boost::algorithm::ends_with(fileName, ".dat"))
|
||||
return false;
|
||||
|
||||
std::string baseName = fileName.substr(0, fileName.size() - 4);
|
||||
fWalletLocked = false;
|
||||
if (boost::algorithm::ends_with(baseName, "_wl"))
|
||||
{
|
||||
fWalletLocked = true;
|
||||
baseName.erase(baseName.size() - 3);
|
||||
};
|
||||
|
||||
size_t sep = baseName.find_first_of("_");
|
||||
if (sep == std::string::npos
|
||||
|| sep == 0
|
||||
|| sep + 1 >= baseName.size())
|
||||
return false;
|
||||
|
||||
std::string sBucket = baseName.substr(0, sep);
|
||||
std::string sIndex = baseName.substr(sep + 1);
|
||||
|
||||
if (!SecureMsgAllDigits(sBucket)
|
||||
|| !SecureMsgAllDigits(sIndex))
|
||||
return false;
|
||||
|
||||
try {
|
||||
bucket = std::stoll(sBucket);
|
||||
unsigned long nIndex = std::stoul(sIndex);
|
||||
if (nIndex < 1
|
||||
|| nIndex > (unsigned long) std::numeric_limits<uint32_t>::max())
|
||||
return false;
|
||||
|
||||
fileIndex = (uint32_t) nIndex;
|
||||
} catch (const std::exception&)
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
std::string SecureMsgBucketFilename(int64_t bucket, uint32_t fileIndex, bool fWalletLocked)
|
||||
{
|
||||
std::string sIndex = std::to_string(fileIndex);
|
||||
if (fileIndex < 10)
|
||||
sIndex.insert(0, "0");
|
||||
|
||||
return std::to_string(bucket) + "_" + sIndex + (fWalletLocked ? "_wl.dat" : ".dat");
|
||||
};
|
||||
|
||||
void SecureMsgGetBucketFiles(const fs::path& pathSmsgDir, int64_t bucket, bool fWalletLocked, std::vector<std::pair<uint32_t, fs::path> >& bucketFiles)
|
||||
{
|
||||
bucketFiles.clear();
|
||||
|
||||
if (!fs::exists(pathSmsgDir)
|
||||
|| !fs::is_directory(pathSmsgDir))
|
||||
return;
|
||||
|
||||
fs::directory_iterator itend;
|
||||
for (fs::directory_iterator itd(pathSmsgDir) ; itd != itend ; ++itd)
|
||||
{
|
||||
if (!fs::is_regular_file(itd->status()))
|
||||
continue;
|
||||
|
||||
int64_t fileBucket;
|
||||
uint32_t fileIndex;
|
||||
bool fFileWalletLocked;
|
||||
std::string fileName = (*itd).path().filename().string();
|
||||
if (!SecureMsgParseBucketFilename(fileName, fileBucket, fileIndex, fFileWalletLocked))
|
||||
continue;
|
||||
|
||||
if (fileBucket != bucket
|
||||
|| fFileWalletLocked != fWalletLocked)
|
||||
continue;
|
||||
|
||||
bucketFiles.push_back(std::make_pair(fileIndex, (*itd).path()));
|
||||
};
|
||||
|
||||
std::sort(bucketFiles.begin(), bucketFiles.end(),
|
||||
[](const std::pair<uint32_t, fs::path>& a, const std::pair<uint32_t, fs::path>& b)
|
||||
{
|
||||
return a.first < b.first;
|
||||
});
|
||||
};
|
||||
|
||||
void SecureMsgRemoveBucketFiles(const fs::path& pathSmsgDir, int64_t bucket, bool fWalletLocked)
|
||||
{
|
||||
std::vector<std::pair<uint32_t, fs::path> > bucketFiles;
|
||||
SecureMsgGetBucketFiles(pathSmsgDir, bucket, fWalletLocked, bucketFiles);
|
||||
|
||||
for (std::vector<std::pair<uint32_t, fs::path> >::iterator it = bucketFiles.begin(); it != bucketFiles.end(); ++it)
|
||||
{
|
||||
try {
|
||||
fs::remove(it->second);
|
||||
} catch (const fs::filesystem_error& ex)
|
||||
{
|
||||
printf("Error removing %s file %s.\n", fWalletLocked ? "wallet locked" : "bucket", ex.what());
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
bool SecureMsgSelectBucketFile(const fs::path& pathSmsgDir, int64_t bucket, bool fWalletLocked, uint32_t nPayload, fs::path& fullPath, uint32_t& fileIndex)
|
||||
{
|
||||
std::vector<std::pair<uint32_t, fs::path> > bucketFiles;
|
||||
SecureMsgGetBucketFiles(pathSmsgDir, bucket, fWalletLocked, bucketFiles);
|
||||
|
||||
fileIndex = 1;
|
||||
if (!bucketFiles.empty())
|
||||
{
|
||||
fileIndex = bucketFiles.back().first;
|
||||
|
||||
try {
|
||||
uintmax_t nFileSize = fs::file_size(bucketFiles.back().second);
|
||||
if (nFileSize + SMSG_HDR_LEN + nPayload > (uintmax_t) SMSG_BUCKET_FILE_SIZE_LIMIT)
|
||||
fileIndex++;
|
||||
} catch (const fs::filesystem_error&)
|
||||
{
|
||||
fileIndex++;
|
||||
};
|
||||
};
|
||||
|
||||
fullPath = pathSmsgDir / SecureMsgBucketFilename(bucket, fileIndex, fWalletLocked);
|
||||
return true;
|
||||
};
|
||||
|
||||
bool SecureMsgWaitForThreadsToStop()
|
||||
{
|
||||
int64_t nDeadline = GetTimeMillis() + SMSG_THREAD_SHUTDOWN_WAIT_MS;
|
||||
while (nSecureMsgThreadsRunning.load() > 0
|
||||
&& GetTimeMillis() < nDeadline)
|
||||
{
|
||||
MilliSleep(SMSG_THREAD_SHUTDOWN_POLL_MS);
|
||||
};
|
||||
|
||||
if (nSecureMsgThreadsRunning.load() > 0)
|
||||
{
|
||||
printf("Timed out waiting for secure messaging threads to stop (%d still running).\n", nSecureMsgThreadsRunning.load());
|
||||
return false;
|
||||
};
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
bool SecMsgCrypter::SetKey(const std::vector<unsigned char>& vchNewKey, unsigned char* chNewIV)
|
||||
{
|
||||
|
||||
@@ -591,6 +776,7 @@ void ThreadSecureMsg(void* parg)
|
||||
{
|
||||
// -- bucket management thread
|
||||
RenameThread("shadowcoin-smsg"); // Make this thread recognisable
|
||||
CSecureMsgThreadGuard threadGuard;
|
||||
|
||||
uint32_t delay = 0;
|
||||
|
||||
@@ -627,31 +813,9 @@ void ThreadSecureMsg(void* parg)
|
||||
{
|
||||
if (fDebugSmsg)
|
||||
printf("Removing bucket %"PRId64" \n", it->first);
|
||||
std::string fileName = std::to_string(it->first) + "_01.dat";
|
||||
fs::path fullPath = GetDataDir() / "smsgStore" / fileName;
|
||||
if (fs::exists(fullPath))
|
||||
{
|
||||
try {
|
||||
fs::remove(fullPath);
|
||||
} catch (const fs::filesystem_error& ex)
|
||||
{
|
||||
printf("Error removing bucket file %s.\n", ex.what());
|
||||
};
|
||||
} else
|
||||
printf("Path %s does not exist \n", fullPath.string().c_str());
|
||||
|
||||
// -- look for a wl file, it stores incoming messages when wallet is locked
|
||||
fileName = std::to_string(it->first) + "_01_wl.dat";
|
||||
fullPath = GetDataDir() / "smsgStore" / fileName;
|
||||
if (fs::exists(fullPath))
|
||||
{
|
||||
try {
|
||||
fs::remove(fullPath);
|
||||
} catch (const fs::filesystem_error& ex)
|
||||
{
|
||||
printf("Error removing wallet locked file %s.\n", ex.what());
|
||||
};
|
||||
};
|
||||
fs::path pathSmsgDir = GetDataDir() / "smsgStore";
|
||||
SecureMsgRemoveBucketFiles(pathSmsgDir, it->first, false);
|
||||
SecureMsgRemoveBucketFiles(pathSmsgDir, it->first, true);
|
||||
|
||||
smsgBuckets.erase(it++);
|
||||
} else
|
||||
@@ -702,6 +866,7 @@ void ThreadSecureMsgPow(void* parg)
|
||||
{
|
||||
// -- proof of work thread
|
||||
RenameThread("shadowcoin-smsg-pow"); // Make this thread recognisable
|
||||
CSecureMsgThreadGuard threadGuard;
|
||||
|
||||
int rv;
|
||||
std::vector<unsigned char> vchKey;
|
||||
@@ -860,15 +1025,15 @@ int SecureMsgBuildBucketSet()
|
||||
|
||||
nFiles++;
|
||||
|
||||
// TODO files must be split if > 2GB
|
||||
// time_noFile.dat
|
||||
size_t sep = fileName.find_first_of("_");
|
||||
if (sep == std::string::npos)
|
||||
int64_t fileTime;
|
||||
uint32_t fileIndex;
|
||||
bool fWalletLocked;
|
||||
if (!SecureMsgParseBucketFilename(fileName, fileTime, fileIndex, fWalletLocked))
|
||||
{
|
||||
if (fDebugSmsg)
|
||||
printf("Skipping unrecognised bucket file: %s.\n", fileName.c_str());
|
||||
continue;
|
||||
|
||||
std::string stime = fileName.substr(0, sep);
|
||||
|
||||
int64_t fileTime = std::stoll(stime);
|
||||
};
|
||||
|
||||
if (fileTime < now - SMSG_RETENTION)
|
||||
{
|
||||
@@ -882,7 +1047,7 @@ int SecureMsgBuildBucketSet()
|
||||
continue;
|
||||
};
|
||||
|
||||
if (boost::algorithm::ends_with(fileName, "_wl.dat"))
|
||||
if (fWalletLocked)
|
||||
{
|
||||
if (fDebugSmsg)
|
||||
printf("Skipping wallet locked file: %s.\n", fileName.c_str());
|
||||
@@ -907,6 +1072,7 @@ int SecureMsgBuildBucketSet()
|
||||
{
|
||||
long int ofs = ftell(fp);
|
||||
SecMsgToken token;
|
||||
token.fileIndex = fileIndex;
|
||||
token.offset = ofs;
|
||||
errno = 0;
|
||||
if (fread(&smsg.hash[0], sizeof(unsigned char), SMSG_HDR_LEN, fp) != (size_t)SMSG_HDR_LEN)
|
||||
@@ -937,15 +1103,14 @@ int SecureMsgBuildBucketSet()
|
||||
break;
|
||||
};
|
||||
|
||||
tokenSet.insert(token);
|
||||
if (tokenSet.insert(token).second)
|
||||
nMessages++;
|
||||
};
|
||||
|
||||
fclose(fp);
|
||||
};
|
||||
smsgBuckets[fileTime].hashBucket();
|
||||
|
||||
nMessages += tokenSet.size();
|
||||
|
||||
if (fDebugSmsg)
|
||||
printf("Bucket %"PRId64" contains %"PRIszu" messages.\n", fileTime, tokenSet.size());
|
||||
};
|
||||
@@ -1176,6 +1341,7 @@ bool SecureMsgStart(bool fDontStart, bool fScanChain)
|
||||
{
|
||||
printf("SecureMsg could not start threads, secure messaging disabled.\n");
|
||||
fSecMsgEnabled = false;
|
||||
SecureMsgWaitForThreadsToStop();
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -1195,6 +1361,7 @@ bool SecureMsgShutdown()
|
||||
printf("Failed to save smsg.ini\n");
|
||||
|
||||
fSecMsgEnabled = false;
|
||||
SecureMsgWaitForThreadsToStop();
|
||||
|
||||
if (smsgDB)
|
||||
{
|
||||
@@ -1249,6 +1416,7 @@ bool SecureMsgEnable()
|
||||
{
|
||||
printf("SecureMsgEnable could not start threads, secure messaging disabled.\n");
|
||||
fSecMsgEnabled = false;
|
||||
SecureMsgWaitForThreadsToStop();
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -1308,9 +1476,7 @@ bool SecureMsgDisable()
|
||||
|
||||
}; // LOCK(cs_smsg);
|
||||
|
||||
// -- allow time for threads to stop
|
||||
MilliSleep(3000); // milliseconds
|
||||
// TODO be certain that threads have stopped
|
||||
SecureMsgWaitForThreadsToStop();
|
||||
|
||||
if (smsgDB)
|
||||
{
|
||||
@@ -1659,6 +1825,7 @@ bool SecureMsgReceiveData(CNode* pfrom, std::string strCommand, CDataStream& vRe
|
||||
} else
|
||||
{
|
||||
//printf("Have message at %"PRId64".\n", it->offset); // DEBUG
|
||||
token.fileIndex = it->fileIndex;
|
||||
token.offset = it->offset;
|
||||
//printf("winb before SecureMsgRetrieve %"PRId64".\n", token.timestamp);
|
||||
|
||||
@@ -2216,15 +2383,15 @@ bool SecureMsgScanBuckets()
|
||||
|
||||
nFiles++;
|
||||
|
||||
// TODO files must be split if > 2GB
|
||||
// time_noFile.dat
|
||||
size_t sep = fileName.find_first_of("_");
|
||||
if (sep == std::string::npos)
|
||||
int64_t fileTime;
|
||||
uint32_t fileIndex;
|
||||
bool fWalletLocked;
|
||||
if (!SecureMsgParseBucketFilename(fileName, fileTime, fileIndex, fWalletLocked))
|
||||
{
|
||||
if (fDebugSmsg)
|
||||
printf("Skipping unrecognised bucket file: %s.\n", fileName.c_str());
|
||||
continue;
|
||||
|
||||
std::string stime = fileName.substr(0, sep);
|
||||
|
||||
int64_t fileTime = std::stoll(stime);
|
||||
};
|
||||
|
||||
if (fileTime < now - SMSG_RETENTION)
|
||||
{
|
||||
@@ -2238,7 +2405,7 @@ bool SecureMsgScanBuckets()
|
||||
continue;
|
||||
};
|
||||
|
||||
if (boost::algorithm::ends_with(fileName, "_wl.dat"))
|
||||
if (fWalletLocked)
|
||||
{
|
||||
if (fDebugSmsg)
|
||||
printf("Skipping wallet locked file: %s.\n", fileName.c_str());
|
||||
@@ -2362,7 +2529,17 @@ int SecureMsgWalletUnlocked()
|
||||
|
||||
std::string fileName = (*itd).path().filename().string();
|
||||
|
||||
if (!boost::algorithm::ends_with(fileName, "_wl.dat"))
|
||||
int64_t fileTime;
|
||||
uint32_t fileIndex;
|
||||
bool fWalletLocked;
|
||||
if (!SecureMsgParseBucketFilename(fileName, fileTime, fileIndex, fWalletLocked))
|
||||
{
|
||||
if (fDebugSmsg)
|
||||
printf("Skipping unrecognised bucket file: %s.\n", fileName.c_str());
|
||||
continue;
|
||||
};
|
||||
|
||||
if (!fWalletLocked)
|
||||
continue;
|
||||
|
||||
if (fDebugSmsg)
|
||||
@@ -2370,16 +2547,6 @@ int SecureMsgWalletUnlocked()
|
||||
|
||||
nFiles++;
|
||||
|
||||
// TODO files must be split if > 2GB
|
||||
// time_noFile_wl.dat
|
||||
size_t sep = fileName.find_first_of("_");
|
||||
if (sep == std::string::npos)
|
||||
continue;
|
||||
|
||||
std::string stime = fileName.substr(0, sep);
|
||||
|
||||
int64_t fileTime = std::stoll(stime);
|
||||
|
||||
if (fileTime < now - SMSG_RETENTION)
|
||||
{
|
||||
printf("Dropping wallet locked file %s, expired.\n", fileName.c_str());
|
||||
@@ -2769,7 +2936,7 @@ int SecureMsgRetrieve(SecMsgToken &token, std::vector<unsigned char>& vchData)
|
||||
|
||||
//printf("token.offset %"PRId64".\n", token.offset); // DEBUG
|
||||
int64_t bucket = token.timestamp - (token.timestamp % SMSG_BUCKET_LEN);
|
||||
std::string fileName = std::to_string(bucket) + "_01.dat";
|
||||
std::string fileName = SecureMsgBucketFilename(bucket, token.fileIndex, false);
|
||||
fs::path fullpath = pathSmsgDir / fileName;
|
||||
|
||||
//printf("bucket %"PRId64".\n", bucket);
|
||||
@@ -2971,9 +3138,9 @@ int SecureMsgStoreUnscanned(unsigned char *pHeader, unsigned char *pPayload, uin
|
||||
};
|
||||
|
||||
int64_t bucket = psmsg->timestamp - (psmsg->timestamp % SMSG_BUCKET_LEN);
|
||||
|
||||
std::string fileName = std::to_string(bucket) + "_01_wl.dat";
|
||||
fs::path fullpath = pathSmsgDir / fileName;
|
||||
fs::path fullpath;
|
||||
uint32_t fileIndex;
|
||||
SecureMsgSelectBucketFile(pathSmsgDir, bucket, true, nPayload, fullpath, fileIndex);
|
||||
|
||||
FILE *fp;
|
||||
errno = 0;
|
||||
@@ -2982,6 +3149,31 @@ int SecureMsgStoreUnscanned(unsigned char *pHeader, unsigned char *pPayload, uin
|
||||
printf("Error opening file: %s\n", strerror(errno));
|
||||
return 1;
|
||||
};
|
||||
|
||||
errno = 0;
|
||||
if (fseek(fp, 0, SEEK_END) != 0)
|
||||
{
|
||||
printf("Error fseek failed: %s\n", strerror(errno));
|
||||
fclose(fp);
|
||||
return 1;
|
||||
};
|
||||
|
||||
long int ofs = ftell(fp);
|
||||
long int nRecordSize = SMSG_HDR_LEN + nPayload;
|
||||
if (ofs > 0
|
||||
&& ofs > SMSG_BUCKET_FILE_SIZE_LIMIT - nRecordSize)
|
||||
{
|
||||
fclose(fp);
|
||||
|
||||
fileIndex++;
|
||||
fullpath = pathSmsgDir / SecureMsgBucketFilename(bucket, fileIndex, true);
|
||||
errno = 0;
|
||||
if (!(fp = fopen(fullpath.string().c_str(), "ab")))
|
||||
{
|
||||
printf("Error opening file: %s\n", strerror(errno));
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
|
||||
if (fwrite(pHeader, sizeof(unsigned char), SMSG_HDR_LEN, fp) != (size_t)SMSG_HDR_LEN
|
||||
|| fwrite(pPayload, sizeof(unsigned char), nPayload, fp) != nPayload)
|
||||
@@ -3073,8 +3265,9 @@ int SecureMsgStore(unsigned char *pHeader, unsigned char *pPayload, uint32_t nPa
|
||||
return 1;
|
||||
};
|
||||
|
||||
std::string fileName = std::to_string(bucket) + "_01.dat";
|
||||
fs::path fullpath = pathSmsgDir / fileName;
|
||||
fs::path fullpath;
|
||||
uint32_t fileIndex;
|
||||
SecureMsgSelectBucketFile(pathSmsgDir, bucket, false, nPayload, fullpath, fileIndex);
|
||||
|
||||
FILE *fp;
|
||||
errno = 0;
|
||||
@@ -3089,11 +3282,37 @@ int SecureMsgStore(unsigned char *pHeader, unsigned char *pPayload, uint32_t nPa
|
||||
if (fseek(fp, 0, SEEK_END) != 0)
|
||||
{
|
||||
printf("Error fseek failed: %s\n", strerror(errno));
|
||||
fclose(fp);
|
||||
return 1;
|
||||
};
|
||||
|
||||
|
||||
ofs = ftell(fp);
|
||||
long int nRecordSize = SMSG_HDR_LEN + nPayload;
|
||||
if (ofs > 0
|
||||
&& ofs > SMSG_BUCKET_FILE_SIZE_LIMIT - nRecordSize)
|
||||
{
|
||||
fclose(fp);
|
||||
|
||||
fileIndex++;
|
||||
fullpath = pathSmsgDir / SecureMsgBucketFilename(bucket, fileIndex, false);
|
||||
errno = 0;
|
||||
if (!(fp = fopen(fullpath.string().c_str(), "ab")))
|
||||
{
|
||||
printf("Error opening file: %s\n", strerror(errno));
|
||||
return 1;
|
||||
};
|
||||
|
||||
errno = 0;
|
||||
if (fseek(fp, 0, SEEK_END) != 0)
|
||||
{
|
||||
printf("Error fseek failed: %s\n", strerror(errno));
|
||||
fclose(fp);
|
||||
return 1;
|
||||
};
|
||||
|
||||
ofs = ftell(fp);
|
||||
};
|
||||
|
||||
if (fwrite(pHeader, sizeof(unsigned char), SMSG_HDR_LEN, fp) != (size_t)SMSG_HDR_LEN
|
||||
|| fwrite(pPayload, sizeof(unsigned char), nPayload, fp) != nPayload)
|
||||
@@ -3105,6 +3324,7 @@ int SecureMsgStore(unsigned char *pHeader, unsigned char *pPayload, uint32_t nPa
|
||||
|
||||
fclose(fp);
|
||||
|
||||
token.fileIndex = fileIndex;
|
||||
token.offset = ofs;
|
||||
|
||||
//printf("token.offset: %"PRId64"\n", token.offset); // DEBUG
|
||||
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
class SecMsgToken
|
||||
{
|
||||
public:
|
||||
SecMsgToken(int64_t ts, unsigned char* p, int np, long int o)
|
||||
SecMsgToken(int64_t ts, unsigned char* p, int np, long int o, uint32_t nFile = 1)
|
||||
{
|
||||
timestamp = ts;
|
||||
|
||||
@@ -117,10 +117,17 @@ public:
|
||||
memset(sample, 0, 8);
|
||||
else
|
||||
memcpy(sample, p, 8);
|
||||
fileIndex = nFile;
|
||||
offset = o;
|
||||
};
|
||||
|
||||
SecMsgToken() {};
|
||||
SecMsgToken()
|
||||
{
|
||||
timestamp = 0;
|
||||
memset(sample, 0, 8);
|
||||
fileIndex = 1;
|
||||
offset = 0;
|
||||
};
|
||||
|
||||
~SecMsgToken() {};
|
||||
|
||||
@@ -134,6 +141,7 @@ public:
|
||||
|
||||
int64_t timestamp; // doesn't need to be full 64 bytes?
|
||||
unsigned char sample[8]; // first 8 bytes of payload - a hash
|
||||
uint32_t fileIndex; // rotated bucket file suffix, eg _02.dat
|
||||
int64_t offset; // offset
|
||||
|
||||
};
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
//
|
||||
// Unit tests for block-chain checkpoints
|
||||
//
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "../checkpoints.h"
|
||||
#include "../util.h"
|
||||
|
||||
using namespace std;
|
||||
#include "../uint256.h"
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(Checkpoints_tests)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(sanity)
|
||||
BOOST_AUTO_TEST_CASE(hardened_checkpoints_match_current_chain)
|
||||
{
|
||||
uint256 p11111 = uint256("0x05cbc42d0421207a39427f1b44610a30380e0e3dd593c154276d7c8d4ae8c9b2");
|
||||
uint256 p16666 = uint256("0x682d99d096441e046dae8dd86d5bdfaa32d4e29dea6cf38273bb2b261905f1e2");
|
||||
BOOST_CHECK(Checkpoints::CheckBlock(11111, p11111));
|
||||
BOOST_CHECK(Checkpoints::CheckBlock(16666, p16666));
|
||||
BOOST_CHECK(Checkpoints::CheckHardened(0, uint256("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021")));
|
||||
BOOST_CHECK(Checkpoints::CheckHardened(9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")));
|
||||
BOOST_CHECK(Checkpoints::CheckHardened(9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")));
|
||||
BOOST_CHECK(Checkpoints::CheckHardened(2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")));
|
||||
}
|
||||
|
||||
|
||||
// Wrong hashes at checkpoints should fail:
|
||||
BOOST_CHECK(!Checkpoints::CheckBlock(11111, p16666));
|
||||
BOOST_CHECK(!Checkpoints::CheckBlock(16666, p11111));
|
||||
BOOST_AUTO_TEST_CASE(hardened_checkpoints_reject_wrong_hashes_and_allow_unknown_heights)
|
||||
{
|
||||
const uint256 wrongHash("0x0000000000000000000000000000000000000000000000000000000000000001");
|
||||
|
||||
// ... but any hash not at a checkpoint should succeed:
|
||||
BOOST_CHECK(Checkpoints::CheckBlock(11111+1, p16666));
|
||||
BOOST_CHECK(Checkpoints::CheckBlock(16666+1, p11111));
|
||||
BOOST_CHECK(!Checkpoints::CheckHardened(9000, wrongHash));
|
||||
BOOST_CHECK(!Checkpoints::CheckHardened(9001, wrongHash));
|
||||
BOOST_CHECK(!Checkpoints::CheckHardened(2186940, wrongHash));
|
||||
|
||||
BOOST_CHECK(Checkpoints::GetTotalBlocksEstimate() >= 16666);
|
||||
}
|
||||
BOOST_CHECK(Checkpoints::CheckHardened(2186941, wrongHash));
|
||||
BOOST_CHECK(Checkpoints::CheckHardened(42, wrongHash));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(total_blocks_estimate_tracks_latest_hardened_checkpoint)
|
||||
{
|
||||
BOOST_CHECK(Checkpoints::GetTotalBlocksEstimate() >= 2186940);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
#include <stdint.h>
|
||||
|
||||
// Tests this internal-to-main.cpp method:
|
||||
extern bool AddOrphanTx(const CDataStream& vMsg);
|
||||
extern bool AddOrphanTx(const CTransaction& tx);
|
||||
extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans);
|
||||
extern std::map<uint256, CDataStream*> mapOrphanTransactions;
|
||||
extern std::map<uint256, std::map<uint256, CDataStream*> > mapOrphanTransactionsByPrev;
|
||||
extern std::map<uint256, CTransaction> mapOrphanTransactions;
|
||||
extern std::map<uint256, std::set<uint256> > mapOrphanTransactionsByPrev;
|
||||
|
||||
CService ip(uint32_t i)
|
||||
{
|
||||
@@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore)
|
||||
BOOST_AUTO_TEST_CASE(DoS_bantime)
|
||||
{
|
||||
CNode::ClearBanned();
|
||||
int64 nStartTime = GetTime();
|
||||
int64_t nStartTime = GetTime();
|
||||
SetMockTime(nStartTime); // Overrides future calls to GetTime()
|
||||
|
||||
CAddress addr(ip(0xa0b0c001));
|
||||
@@ -80,11 +80,11 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
|
||||
BOOST_CHECK(!CNode::IsBanned(addr));
|
||||
}
|
||||
|
||||
static bool CheckNBits(unsigned int nbits1, int64 time1, unsigned int nbits2, int64 time2)\
|
||||
static bool CheckNBits(unsigned int nbits1, int64_t time1, unsigned int nbits2, int64_t time2)\
|
||||
{
|
||||
if (time1 > time2)
|
||||
return CheckNBits(nbits2, time2, nbits1, time1);
|
||||
int64 deltaTime = time2-time1;
|
||||
int64_t deltaTime = time2-time1;
|
||||
|
||||
CBigNum required;
|
||||
required.SetCompact(ComputeMinWork(nbits1, deltaTime));
|
||||
@@ -97,7 +97,7 @@ BOOST_AUTO_TEST_CASE(DoS_checknbits)
|
||||
{
|
||||
// Timestamps,nBits from the Triangles blockchain.
|
||||
// These are the block-chain checkpoint blocks
|
||||
typedef std::map<int64, unsigned int> BlockData;
|
||||
typedef std::map<int64_t, unsigned int> BlockData;
|
||||
BlockData chainData = {
|
||||
{1239852051,486604799},{1262749024,486594666},
|
||||
{1279305360,469854461},{1280200847,469830746},{1281678674,469809688},
|
||||
@@ -131,14 +131,11 @@ BOOST_AUTO_TEST_CASE(DoS_checknbits)
|
||||
|
||||
CTransaction RandomOrphan()
|
||||
{
|
||||
std::map<uint256, CDataStream*>::iterator it;
|
||||
std::map<uint256, CTransaction>::iterator it;
|
||||
it = mapOrphanTransactions.lower_bound(GetRandHash());
|
||||
if (it == mapOrphanTransactions.end())
|
||||
it = mapOrphanTransactions.begin();
|
||||
const CDataStream* pvMsg = it->second;
|
||||
CTransaction tx;
|
||||
CDataStream(*pvMsg) >> tx;
|
||||
return tx;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
|
||||
@@ -160,9 +157,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
|
||||
tx.vout[0].nValue = 1*CENT;
|
||||
tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID());
|
||||
|
||||
CDataStream ds(SER_DISK, CLIENT_VERSION);
|
||||
ds << tx;
|
||||
AddOrphanTx(ds);
|
||||
AddOrphanTx(tx);
|
||||
}
|
||||
|
||||
// ... and 50 that depend on other orphans:
|
||||
@@ -179,9 +174,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
|
||||
tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID());
|
||||
SignSignature(keystore, txPrev, tx, 0);
|
||||
|
||||
CDataStream ds(SER_DISK, CLIENT_VERSION);
|
||||
ds << tx;
|
||||
AddOrphanTx(ds);
|
||||
AddOrphanTx(tx);
|
||||
}
|
||||
|
||||
// This really-big orphan should be ignored:
|
||||
@@ -205,9 +198,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
|
||||
for (unsigned int j = 1; j < tx.vin.size(); j++)
|
||||
tx.vin[j].scriptSig = tx.vin[0].scriptSig;
|
||||
|
||||
CDataStream ds(SER_DISK, CLIENT_VERSION);
|
||||
ds << tx;
|
||||
BOOST_CHECK(!AddOrphanTx(ds));
|
||||
BOOST_CHECK(!AddOrphanTx(tx));
|
||||
}
|
||||
|
||||
// Test LimitOrphanTxSize() function:
|
||||
@@ -243,9 +234,7 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig)
|
||||
tx.vout[0].nValue = 1*CENT;
|
||||
tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID());
|
||||
|
||||
CDataStream ds(SER_DISK, CLIENT_VERSION);
|
||||
ds << tx;
|
||||
AddOrphanTx(ds);
|
||||
AddOrphanTx(tx);
|
||||
}
|
||||
|
||||
// Create a transaction that depends on orphans:
|
||||
@@ -275,7 +264,7 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig)
|
||||
mst1 = boost::posix_time::microsec_clock::local_time();
|
||||
for (unsigned int i = 0; i < 5; i++)
|
||||
for (unsigned int j = 0; j < tx.vin.size(); j++)
|
||||
BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, SIGHASH_ALL));
|
||||
BOOST_CHECK(VerifySignature(orphans[j], tx, j, SIGHASH_ALL));
|
||||
mst2 = boost::posix_time::microsec_clock::local_time();
|
||||
msdiff = mst2 - mst1;
|
||||
long nManyValidate = msdiff.total_milliseconds();
|
||||
@@ -286,13 +275,13 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig)
|
||||
// Empty a signature, validation should fail:
|
||||
CScript save = tx.vin[0].scriptSig;
|
||||
tx.vin[0].scriptSig = CScript();
|
||||
BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, true, SIGHASH_ALL));
|
||||
BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, SIGHASH_ALL));
|
||||
tx.vin[0].scriptSig = save;
|
||||
|
||||
// Swap signatures, validation should fail:
|
||||
std::swap(tx.vin[0].scriptSig, tx.vin[1].scriptSig);
|
||||
BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, true, SIGHASH_ALL));
|
||||
BOOST_CHECK(!VerifySignature(orphans[1], tx, 1, true, SIGHASH_ALL));
|
||||
BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, SIGHASH_ALL));
|
||||
BOOST_CHECK(!VerifySignature(orphans[1], tx, 1, SIGHASH_ALL));
|
||||
std::swap(tx.vin[0].scriptSig, tx.vin[1].scriptSig);
|
||||
|
||||
// Exercise -maxsigcachesize code:
|
||||
@@ -302,7 +291,7 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig)
|
||||
BOOST_CHECK(SignSignature(keystore, orphans[0], tx, 0));
|
||||
BOOST_CHECK(tx.vin[0].scriptSig != oldSig);
|
||||
for (unsigned int j = 0; j < tx.vin.size(); j++)
|
||||
BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, SIGHASH_ALL));
|
||||
BOOST_CHECK(VerifySignature(orphans[j], tx, j, SIGHASH_ALL));
|
||||
mapArgs.erase("-maxsigcachesize");
|
||||
|
||||
LimitOrphanTxSize(0);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
BOOST_AUTO_TEST_SUITE(accounting_tests)
|
||||
|
||||
static void
|
||||
GetResults(CWalletDB& walletdb, std::map<int64, CAccountingEntry>& results)
|
||||
GetResults(CWalletDB& walletdb, std::map<int64_t, CAccountingEntry>& results)
|
||||
{
|
||||
std::list<CAccountingEntry> aes;
|
||||
|
||||
@@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
|
||||
std::vector<CWalletTx*> vpwtx;
|
||||
CWalletTx wtx;
|
||||
CAccountingEntry ae;
|
||||
std::map<int64, CAccountingEntry> results;
|
||||
std::map<int64_t, CAccountingEntry> results;
|
||||
|
||||
ae.strAccount = "";
|
||||
ae.nCreditDebit = 1;
|
||||
|
||||
@@ -46,7 +46,7 @@ BOOST_AUTO_TEST_SUITE(bignum_tests)
|
||||
// Let's force this code not to be inlined, in order to actually
|
||||
// test a generic version of the function. This increases the chance
|
||||
// that -ftrapv will detect overflows.
|
||||
NOINLINE void mysetint64(CBigNum& num, int64 n)
|
||||
NOINLINE void mysetint64(CBigNum& num, int64_t n)
|
||||
{
|
||||
num.setint64(n);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ NOINLINE void mysetint64(CBigNum& num, int64 n)
|
||||
// value to 0, then the second one with a non-inlined function.
|
||||
BOOST_AUTO_TEST_CASE(bignum_setint64)
|
||||
{
|
||||
int64 n;
|
||||
int64_t n;
|
||||
|
||||
{
|
||||
n = 0;
|
||||
@@ -103,7 +103,7 @@ BOOST_AUTO_TEST_CASE(bignum_setint64)
|
||||
BOOST_CHECK(num.ToString() == "-5");
|
||||
}
|
||||
{
|
||||
n = std::numeric_limits<int64>::min();
|
||||
n = std::numeric_limits<int64_t>::min();
|
||||
CBigNum num(n);
|
||||
BOOST_CHECK(num.ToString() == "-9223372036854775808");
|
||||
num.setulong(0);
|
||||
@@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE(bignum_setint64)
|
||||
BOOST_CHECK(num.ToString() == "-9223372036854775808");
|
||||
}
|
||||
{
|
||||
n = std::numeric_limits<int64>::max();
|
||||
n = std::numeric_limits<int64_t>::max();
|
||||
CBigNum num(n);
|
||||
BOOST_CHECK(num.ToString() == "9223372036854775807");
|
||||
num.setulong(0);
|
||||
|
||||
@@ -1,451 +1,451 @@
|
||||
[
|
||||
[
|
||||
"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i",
|
||||
"65a16059864a2fdbc7c99a4723a8395bc6f188eb",
|
||||
"TKEaa4THZFHWdrKMRZgRdhiMJFmcFaMkWa",
|
||||
"65a16059864a2fdbc7c99a4723a8395bc6f188eb",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou",
|
||||
"74f209f6ea907e2ea48f74fae05782ae8a665257",
|
||||
"CT8EuTDe8RqksPDEPFTVUyF5ksbvNTs8Yp",
|
||||
"74f209f6ea907e2ea48f74fae05782ae8a665257",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||
"53c0307d6851aa0ce7825ba883c6bd9ad242b486",
|
||||
"mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||
"53c0307d6851aa0ce7825ba883c6bd9ad242b486",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br",
|
||||
"6349a418fc4578d10a372b54b45c280cc8c4382f",
|
||||
"2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br",
|
||||
"6349a418fc4578d10a372b54b45c280cc8c4382f",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr",
|
||||
"eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19",
|
||||
"7VyRqFeF28kRkKRdAcqHxznWrNUcnh14PtzyGoBbBXFrAi1wcbq",
|
||||
"eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD",
|
||||
"55c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c4",
|
||||
"VbnTUFpdaJKHEDGf25hLuznivjWmfLJrWFQQyHit2hrqt7rK8ubv",
|
||||
"55c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c4",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko",
|
||||
"36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2",
|
||||
"9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko",
|
||||
"36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH",
|
||||
"b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3",
|
||||
"cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH",
|
||||
"b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ",
|
||||
"6d23156cbbdcc82a5a47eee4c2c7c583c18b6bf4",
|
||||
"TKvGgdGKGQHg3CXMXDJT5SF2HA5mEoH2xd",
|
||||
"6d23156cbbdcc82a5a47eee4c2c7c583c18b6bf4",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy",
|
||||
"fcc5460dd6e2487c7d75b1963625da0e8f4c5975",
|
||||
"CfWRBCKPG4PHeMnWr77qjyCq1JZZe8guVH",
|
||||
"fcc5460dd6e2487c7d75b1963625da0e8f4c5975",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ",
|
||||
"f1d470f9b02370fdec2e6b708b08ac431bf7a5f7",
|
||||
"n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ",
|
||||
"f1d470f9b02370fdec2e6b708b08ac431bf7a5f7",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n",
|
||||
"c579342c2c4c9220205e2cdc285617040c924a0a",
|
||||
"2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n",
|
||||
"c579342c2c4c9220205e2cdc285617040c924a0a",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc",
|
||||
"a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e",
|
||||
"7VQXXbk2DWzK3JRB2hATiU38L8U3QoPaMGCrnAE7Dst2yDn9avB",
|
||||
"a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi",
|
||||
"7d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb4",
|
||||
"Vd7r2GxDzKTpQr6HtoswT465nhXLVQfMURToNkAkQxKBFSfjazbE",
|
||||
"7d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb4",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj",
|
||||
"d6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203",
|
||||
"93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj",
|
||||
"d6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN",
|
||||
"a81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d9",
|
||||
"cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN",
|
||||
"a81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d9",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv",
|
||||
"7987ccaa53d02c8873487ef919677cd3db7a6912",
|
||||
"TM3oSnPSNzbdbdfdKFQydMU9327j8cn6cQ",
|
||||
"7987ccaa53d02c8873487ef919677cd3db7a6912",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks",
|
||||
"63bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb",
|
||||
"CRZFc5TAbx87MgfE8auZwY7aHFBDfdTe4J",
|
||||
"63bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk",
|
||||
"ef66444b5b17f14e8fae6e7e19b045a78c54fd79",
|
||||
"n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk",
|
||||
"ef66444b5b17f14e8fae6e7e19b045a78c54fd79",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"2NB72XtkjpnATMggui83aEtPawyyKvnbX2o",
|
||||
"c3e55fceceaa4391ed2a9677f4a4d34eacd021a0",
|
||||
"2NB72XtkjpnATMggui83aEtPawyyKvnbX2o",
|
||||
"c3e55fceceaa4391ed2a9677f4a4d34eacd021a0",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9",
|
||||
"e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252",
|
||||
"7VvZyE6THALRNNL59ei9iNJj9uQxTVXM6ynWoDreUQmYeZj91Jk",
|
||||
"e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT",
|
||||
"8248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c",
|
||||
"VdGxA5sTMmvUVmd87df1W2xyvuMHRjAVeFuG4xqmFb7opg8Eqak3",
|
||||
"8248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo",
|
||||
"44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52",
|
||||
"927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo",
|
||||
"44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7",
|
||||
"d1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c69",
|
||||
"cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7",
|
||||
"d1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c69",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu",
|
||||
"adc1cc2081a27206fae25792f28bbc55b831549d",
|
||||
"TRox4XHqJ7FxMn553gYBa1hdzorRXJzMgt",
|
||||
"adc1cc2081a27206fae25792f28bbc55b831549d",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk",
|
||||
"188f91a931947eddd7432d6e614387e32b244709",
|
||||
"CJhkmzZuQtXzoyH9qQARm82LTkHXGeT5d6",
|
||||
"188f91a931947eddd7432d6e614387e32b244709",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H",
|
||||
"1694f5bc1a7295b600f40018a618a6ea48eeb498",
|
||||
"mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H",
|
||||
"1694f5bc1a7295b600f40018a618a6ea48eeb498",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN",
|
||||
"3b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f3",
|
||||
"2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN",
|
||||
"3b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f3",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR",
|
||||
"091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0",
|
||||
"7UEfZLohKrFsq1TMA1M83axWhtCoYAV6rh3BTpQRKewx8pWSQm3",
|
||||
"091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8",
|
||||
"ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af",
|
||||
"VeeRiFn7snbVzNuVQoaCho1BfQqFn9vP4HZ6JfLuahPxukB2QePg",
|
||||
"ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq",
|
||||
"b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856",
|
||||
"92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq",
|
||||
"b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA",
|
||||
"e7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef",
|
||||
"cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA",
|
||||
"e7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4",
|
||||
"c4c1b72491ede1eedaca00618407ee0b772cad0d",
|
||||
"TTuZWEs53Sxr2nfD1ZERayXRiYSZSKhiau",
|
||||
"c4c1b72491ede1eedaca00618407ee0b772cad0d",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y",
|
||||
"f6fe69bcb548a829cce4c57bf6fff8af3a5981f9",
|
||||
"CeysaABgtaav7DvW4qAH7YtfqnNpbVDZ4A",
|
||||
"f6fe69bcb548a829cce4c57bf6fff8af3a5981f9",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6",
|
||||
"261f83568a098a8638844bd7aeca039d5f2352c0",
|
||||
"mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6",
|
||||
"261f83568a098a8638844bd7aeca039d5f2352c0",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda",
|
||||
"e930e1834a4d234702773951d627cce82fbb5d2e",
|
||||
"2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda",
|
||||
"e930e1834a4d234702773951d627cce82fbb5d2e",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg",
|
||||
"d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0",
|
||||
"7Vm9gw9RksaSdnmGjjLBpzcQ7MTSZd1QcZL5mz8iwQracmEG8fz",
|
||||
"d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi",
|
||||
"b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b3",
|
||||
"VeqF8bafQHFfUiYczUF4unECTw5AfdnYGXsCLTHB2vnzJ8BX2qE2",
|
||||
"b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b3",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys",
|
||||
"037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb",
|
||||
"91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys",
|
||||
"037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw",
|
||||
"6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de",
|
||||
"cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw",
|
||||
"6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r",
|
||||
"5eadaf9bb7121f0f192561a5a62f5e5f54210292",
|
||||
"TJbpb1B3mHkN9D9i76BDsrDEJzE8ZTo8Gc",
|
||||
"5eadaf9bb7121f0f192561a5a62f5e5f54210292",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3",
|
||||
"3f210e7277c899c3a155cc1c90f4106cbddeec6e",
|
||||
"CNDgjvmgJDQeRyzPZx9QyZ6d6DD5L4ztuL",
|
||||
"3f210e7277c899c3a155cc1c90f4106cbddeec6e",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"myoqcgYiehufrsnnkqdqbp69dddVDMopJu",
|
||||
"c8a3c2a09a298592c3e180f02487cd91ba3400b5",
|
||||
"myoqcgYiehufrsnnkqdqbp69dddVDMopJu",
|
||||
"c8a3c2a09a298592c3e180f02487cd91ba3400b5",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C",
|
||||
"99b31df7c9068d1481b596578ddbb4d3bd90baeb",
|
||||
"2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C",
|
||||
"99b31df7c9068d1481b596578ddbb4d3bd90baeb",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4",
|
||||
"c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae",
|
||||
"7VgVTJkSH3PFKeZrXGhn79C5GD3GZgBY2Dw2uUMKR8a7WjWZq84",
|
||||
"c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2",
|
||||
"07f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd",
|
||||
"VZB8Uf6QywS1MZWVTJXfAmoE3PFj61NEUirLmK6UNM52Hc63AVwg",
|
||||
"07f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV",
|
||||
"ea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801",
|
||||
"93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV",
|
||||
"ea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801",
|
||||
{
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": false,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h",
|
||||
"0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c",
|
||||
"cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h",
|
||||
"0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c",
|
||||
{
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isCompressed": true,
|
||||
"isPrivkey": true,
|
||||
"isTestnet": true
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE",
|
||||
"1ed467017f043e91ed4c44b4e8dd674db211c4e6",
|
||||
"TCnDinig2Wya59uF4BxFGyiJMRH9q9P66u",
|
||||
"1ed467017f043e91ed4c44b4e8dd674db211c4e6",
|
||||
{
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"addrType": "pubkey",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
],
|
||||
],
|
||||
[
|
||||
"3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G",
|
||||
"5ece0cadddc415b1980f001785947120acdb36fc",
|
||||
"CR7AvePmQA9M5ahZ6pJ9JvaiBQJqdU7XX7",
|
||||
"5ece0cadddc415b1980f001785947120acdb36fc",
|
||||
{
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"addrType": "script",
|
||||
"isPrivkey": false,
|
||||
"isTestnet": false
|
||||
}
|
||||
]
|
||||
|
||||
@@ -10,17 +10,17 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
static const string strSecret1 ("VgbPo2gxpQ9Wv3ukSKZyaYqaYE1gCtzpdZtLBSL4Vax17Gk9QWtr");
|
||||
static const string strSecret2 ("VZK4HxH5ThQPZjkMVGRb7GHPBhrfwvNCQxYpTsTL7zHtcK5Xmu56");
|
||||
static const string strSecret1C ("VaGAFqyHzPVbXmLRVEfNBCjGoFfkdk5us5RkxdNVu9GKzk1UKdW4");
|
||||
static const string strSecret2C ("VbGMJ5R3znfmv3harEnjEfa9to9hnAp3vGqahGLq12hF3L2aa9vr");
|
||||
static const CTrianglesAddress addr1 ("TBUTdKJg53PmmwjyzdPgXjABq2MJrz7sdC");
|
||||
static const CTrianglesAddress addr2 ("TV7guKExCcH65B5oKFhtYiVo5M59md24MZ");
|
||||
static const CTrianglesAddress addr1C("TM1W8XP18TM2zEqeRo76gXSkaM6UGppfFG");
|
||||
static const CTrianglesAddress addr2C("TD4n8bbK75ufLRT5kraXVty6MwN6FXGwai");
|
||||
static const string strSecret1 ("7VwGSZW25FEoG5zehJeaemXrwmPXrHPZYJ6zyffvHj4nNxMp54C");
|
||||
static const string strSecret2 ("7UZQX91FxJYGc4WeGd8krh2jTmuKP53goPcJ2FWdAJ6dRYZqh85");
|
||||
static const string strSecret1C ("VgiX9pT5jjquBb3TNttsXhZDSivTAXLad8VUu7gnCpfhHjiSogsy");
|
||||
static const string strSecret2C ("Vae2p2L916R1skVqVxVEaKgeRBBv8Qrrfknn8X3kc2TqmL7EH9sp");
|
||||
static const CTrianglesAddress addr1 ("TKQV7fkoFN947MkFm17NNXgm7tEiPUTa2z");
|
||||
static const CTrianglesAddress addr2 ("TBJRAJXnQxT7B6bM11emAfPSmj4xCqyw8B");
|
||||
static const CTrianglesAddress addr1C("TEjXKeQNxb8efkAXK9H7U5uKvMpcGVCqWX");
|
||||
static const CTrianglesAddress addr2C("TQe4pNNxnZWD3SNHyL13DuxJZJXFjtE76E");
|
||||
|
||||
|
||||
static const string strAddressBad("TNLwP8QiuFTWntVCrmswefDetZkgTLXeG8");
|
||||
static const string strAddressBad("TJQ5ap67UnQRQbaK8LqZbTgMkEuJ8SRZMA");
|
||||
|
||||
|
||||
#ifdef KEY_TESTS_DUMPINFO
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include "util.h"
|
||||
#include "wallet.h"
|
||||
|
||||
extern void SHA256Transform(void* pstate, void* pinput, const void* pinit);
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(miner_tests)
|
||||
|
||||
static
|
||||
@@ -198,30 +196,4 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||
pindexBest->nHeight = nHeight;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(sha256transform_equality)
|
||||
{
|
||||
unsigned int pSHA256InitState[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
|
||||
|
||||
|
||||
// unsigned char pstate[32];
|
||||
unsigned char pinput[64];
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 32; i++) {
|
||||
pinput[i] = i;
|
||||
pinput[i+32] = 0;
|
||||
}
|
||||
|
||||
uint256 hash;
|
||||
|
||||
SHA256Transform(&hash, pinput, pSHA256InitState);
|
||||
|
||||
BOOST_TEST_MESSAGE(hash.GetHex());
|
||||
|
||||
uint256 hash_reference("0x2df5e1c65ef9f8cde240d23cae2ec036d31a15ec64bc68f64be242b1da6631f3");
|
||||
|
||||
BOOST_CHECK(hash == hash_reference);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
@@ -16,7 +16,7 @@ typedef vector<unsigned char> valtype;
|
||||
|
||||
extern uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType);
|
||||
extern bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn,
|
||||
bool fValidatePayToScriptHash, int nHashType);
|
||||
int nHashType);
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(multisig_tests)
|
||||
|
||||
@@ -75,19 +75,19 @@ BOOST_AUTO_TEST_CASE(multisig_verify)
|
||||
keys.clear();
|
||||
keys.push_back(key[0]); keys.push_back(key[1]);
|
||||
s = sign_multisig(a_and_b, keys, txTo[0], 0);
|
||||
BOOST_CHECK(VerifyScript(s, a_and_b, txTo[0], 0, true, 0));
|
||||
BOOST_CHECK(VerifyScript(s, a_and_b, txTo[0], 0, 0));
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
keys.clear();
|
||||
keys.push_back(key[i]);
|
||||
s = sign_multisig(a_and_b, keys, txTo[0], 0);
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, true, 0), strprintf("a&b 1: %d", i));
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, 0), strprintf("a&b 1: %d", i));
|
||||
|
||||
keys.clear();
|
||||
keys.push_back(key[1]); keys.push_back(key[i]);
|
||||
s = sign_multisig(a_and_b, keys, txTo[0], 0);
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, true, 0), strprintf("a&b 2: %d", i));
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, txTo[0], 0, 0), strprintf("a&b 2: %d", i));
|
||||
}
|
||||
|
||||
// Test a OR b:
|
||||
@@ -97,16 +97,16 @@ BOOST_AUTO_TEST_CASE(multisig_verify)
|
||||
keys.push_back(key[i]);
|
||||
s = sign_multisig(a_or_b, keys, txTo[1], 0);
|
||||
if (i == 0 || i == 1)
|
||||
BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, txTo[1], 0, true, 0), strprintf("a|b: %d", i));
|
||||
BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, txTo[1], 0, 0), strprintf("a|b: %d", i));
|
||||
else
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, txTo[1], 0, true, 0), strprintf("a|b: %d", i));
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, txTo[1], 0, 0), strprintf("a|b: %d", i));
|
||||
}
|
||||
s.clear();
|
||||
s << OP_0 << OP_0;
|
||||
BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, 0));
|
||||
s.clear();
|
||||
s << OP_0 << OP_1;
|
||||
BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(s, a_or_b, txTo[1], 0, 0));
|
||||
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
@@ -116,9 +116,9 @@ BOOST_AUTO_TEST_CASE(multisig_verify)
|
||||
keys.push_back(key[i]); keys.push_back(key[j]);
|
||||
s = sign_multisig(escrow, keys, txTo[2], 0);
|
||||
if (i < j && i < 3 && j < 3)
|
||||
BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, txTo[2], 0, true, 0), strprintf("escrow 1: %d %d", i, j));
|
||||
BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, txTo[2], 0, 0), strprintf("escrow 1: %d %d", i, j));
|
||||
else
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, txTo[2], 0, true, 0), strprintf("escrow 2: %d %d", i, j));
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, txTo[2], 0, 0), strprintf("escrow 2: %d %d", i, j));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using namespace std;
|
||||
// Test routines internal to script.cpp:
|
||||
extern uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType);
|
||||
extern bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn,
|
||||
bool fValidatePayToScriptHash, int nHashType);
|
||||
int nHashType);
|
||||
|
||||
// Helpers:
|
||||
static std::vector<unsigned char>
|
||||
@@ -36,7 +36,7 @@ Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict)
|
||||
txTo.vin[0].scriptSig = scriptSig;
|
||||
txTo.vout[0].nValue = 1;
|
||||
|
||||
return VerifyScript(scriptSig, scriptPubKey, txTo, 0, fStrict, 0);
|
||||
return VerifyScript(scriptSig, scriptPubKey, txTo, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(sign)
|
||||
{
|
||||
CScript sigSave = txTo[i].vin[0].scriptSig;
|
||||
txTo[i].vin[0].scriptSig = txTo[j].vin[0].scriptSig;
|
||||
bool sigOK = VerifySignature(txFrom, txTo[i], 0, true, 0);
|
||||
bool sigOK = VerifySignature(txFrom, txTo[i], 0, 0);
|
||||
if (i == j)
|
||||
BOOST_CHECK_MESSAGE(sigOK, strprintf("VerifySignature %d %d", i, j));
|
||||
else
|
||||
@@ -221,7 +221,7 @@ BOOST_AUTO_TEST_CASE(is)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(switchover)
|
||||
{
|
||||
// Test switch over code
|
||||
// Triangles always enforces P2SH; verify that an invalid inner script fails
|
||||
CScript notValid;
|
||||
notValid << OP_11 << OP_12 << OP_EQUALVERIFY;
|
||||
CScript scriptSig;
|
||||
@@ -230,16 +230,13 @@ BOOST_AUTO_TEST_CASE(switchover)
|
||||
CScript fund;
|
||||
fund.SetDestination(notValid.GetID());
|
||||
|
||||
|
||||
// Validation should succeed under old rules (hash is correct):
|
||||
BOOST_CHECK(Verify(scriptSig, fund, false));
|
||||
// Fail under new:
|
||||
// P2SH inner script is invalid (11 != 12), must fail:
|
||||
BOOST_CHECK(!Verify(scriptSig, fund, true));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||
{
|
||||
std::map<uint256, std::pair<CTxIndex, CTransaction> > mapInputs;
|
||||
MapPrevTx mapInputs;
|
||||
CBasicKeyStore keystore;
|
||||
CKey key[3];
|
||||
vector<CKey> keys;
|
||||
@@ -276,7 +273,19 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||
oneOfEleven << OP_11 << OP_CHECKMULTISIG;
|
||||
txFrom.vout[5].scriptPubKey.SetDestination(oneOfEleven.GetID());
|
||||
|
||||
mapInputs[txFrom.GetHash()] = make_pair(CTxIndex(), txFrom);
|
||||
// Populate UTXO entries for each output of txFrom
|
||||
uint256 hashFrom = txFrom.GetHash();
|
||||
for (unsigned int i = 0; i < txFrom.vout.size(); i++)
|
||||
{
|
||||
CUtxoEntry entry;
|
||||
entry.nValue = txFrom.vout[i].nValue;
|
||||
entry.nHeight = 1;
|
||||
entry.scriptPubKey = txFrom.vout[i].scriptPubKey;
|
||||
entry.fCoinBase = false;
|
||||
entry.fCoinStake = false;
|
||||
entry.nTxTime = 0;
|
||||
mapInputs[COutPoint(hashFrom, i)] = entry;
|
||||
}
|
||||
|
||||
CTransaction txTo;
|
||||
txTo.vout.resize(1);
|
||||
|
||||