Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dfb4b221dd | |||
| b6013edbe4 | |||
| d42c5aa799 | |||
| e8b2339339 | |||
| d242c2f37e | |||
| 5ad0bb53b4 | |||
| da41cc8718 | |||
| 07e6eccc44 | |||
| f52f83dc71 | |||
| 2e19ec350d | |||
| 18db764cf5 | |||
| ca156a3c59 | |||
| 35bf69ec94 | |||
| bd4a6b7cc3 | |||
| 870fc0896d | |||
| cce120bd81 | |||
| b12fda0e3f | |||
| fc79a744ab | |||
| 7597ad10c3 | |||
| 20151a2248 | |||
| 5701545f0d | |||
| 997435c4e0 | |||
| cd1b497f0d | |||
| 7adf92df7a | |||
| 4405d34f4b | |||
| 96b549ce95 | |||
| 49cd969009 | |||
| 787721616e | |||
| 369a57c67d | |||
| c57b14f6be | |||
| ce7b276a1f | |||
| 4f3e16c935 | |||
| 2833a70a36 | |||
| aa32672208 | |||
| a9bbcd070b | |||
| 7bfc34b76b | |||
| c3f49eb558 | |||
| 71f1f3011d | |||
| 22de8630cd | |||
| da5e5f9a8a | |||
| 998bd51425 | |||
| 7d0b2806e0 | |||
| 73c183d1c0 | |||
| 65b9417c28 | |||
| ed87543153 | |||
| 6e9dbb1aa9 | |||
| a6ec711cfa | |||
| 6877aeaddb | |||
| 60067e1a88 | |||
| e91ccd8786 | |||
| 96fb7d5040 | |||
| 47cf8abbda | |||
| 2abd494fec | |||
| 378b0370e3 | |||
| 61f22fcfd4 | |||
| bf1bf393c8 | |||
| 39e244a11c | |||
| 6724dfc832 | |||
| 9dadba6b09 |
@@ -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.2.0"
|
||||
|
||||
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:
|
||||
@@ -124,6 +146,13 @@ jobs:
|
||||
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
|
||||
@@ -164,6 +193,13 @@ jobs:
|
||||
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,7 +217,7 @@ 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
|
||||
@@ -200,6 +236,13 @@ jobs:
|
||||
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
|
||||
@@ -264,6 +307,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:
|
||||
|
||||
@@ -5,12 +5,18 @@
|
||||
*.so
|
||||
*.dylib
|
||||
*.a
|
||||
/dist/
|
||||
build/
|
||||
release/
|
||||
debug/
|
||||
/Makefile
|
||||
Makefile.Debug
|
||||
Makefile.Release
|
||||
.qmake.stash
|
||||
object_script.triangles-qt.Debug
|
||||
object_script.triangles-qt.Release
|
||||
/*.zip
|
||||
/*.tar.gz
|
||||
|
||||
# Qt
|
||||
moc_*.cpp
|
||||
@@ -52,3 +58,6 @@ triangles.conf
|
||||
*.key
|
||||
*.cert
|
||||
*.gpg
|
||||
*.o
|
||||
src/trianglesd
|
||||
src/obj/
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# Triangles Codebase Cleanup Notes
|
||||
|
||||
## Overview
|
||||
Systematic code quality improvements for the Triangles cryptocurrency codebase (v5.3.4+).
|
||||
|
||||
**Goal:** Improve maintainability without changing behavior or breaking consensus.
|
||||
|
||||
## Inventory
|
||||
|
||||
### TODOs/FIXMEs Found (38 total)
|
||||
|
||||
#### High Priority (Affects Safety/Correctness)
|
||||
- `rpcmining.cpp:263` - **Thread safety issue** in mapNewBlock (static variable, no mutex)
|
||||
- `walletmodel.cpp:249` - **Potential collision** in balance calculation
|
||||
- `smessage.cpp:863, 2219, 2373` - **File size limit** (files must be split if >2GB)
|
||||
|
||||
#### Medium Priority (Encapsulation/Security)
|
||||
- `protocol.h:50, 100, 132` - Public members should be private (3 locations)
|
||||
- `wallet.h:378` - nOrderPos calculation should move elsewhere
|
||||
- `wallet.cpp:733, 1732` - Change output handling needs improvement
|
||||
- `rpcwallet.cpp:1474, 1513, 1569` - SecureString operator= missing (forced .c_str())
|
||||
|
||||
#### Low Priority (Nice-to-Have)
|
||||
- `util.cpp:1322` - Disabled feature needs verification
|
||||
- `tor/tor_embedded.cpp:209` - Tor 0.4.9+ shutdown API upgrade
|
||||
- `init.cpp:442` - Remaining sanity checks (see Bitcoin issue #4081)
|
||||
- `rpcmining.cpp:232` - DRM comment (unclear what it means)
|
||||
- `smessage.cpp:*` - Various improvements (hash inclusion, thread safety, defaults)
|
||||
- `qt/*` - UI improvements (decrypt not supported, message filtering, OSX startup)
|
||||
|
||||
#### External/Third-Party (Don't Touch)
|
||||
- `leveldb/*` - LevelDB library TODOs (upstream issues)
|
||||
|
||||
## Code Quality Issues
|
||||
|
||||
### Using namespace std (37 files)
|
||||
All in .cpp files - **this is fine for .cpp**, problematic only in headers.
|
||||
No headers have this issue, so **no action needed**.
|
||||
|
||||
### Printf/Cout Usage (56 files)
|
||||
Most cryptocurrency code uses printf for early init/error handling before logging is available.
|
||||
**Review needed:** Check if these are legitimate early-init cases or should use LogPrintf.
|
||||
|
||||
## Cleanup Plan (Safest → Riskiest)
|
||||
|
||||
### Phase 1: Documentation & Comments ✅ SAFE
|
||||
1. Document all TODOs with context (why deferred, what's needed)
|
||||
2. Add function-level comments for complex logic
|
||||
3. Improve inline comments for clarity
|
||||
|
||||
### Phase 2: Low-Risk Code Quality 🟨 MEDIUM RISK
|
||||
4. Fix compiler warnings (-Wall -Wextra)
|
||||
5. Add const correctness where missing
|
||||
6. Remove commented-out dead code
|
||||
7. Standardize code formatting (if inconsistent)
|
||||
|
||||
### Phase 3: Functional Improvements 🟥 HIGH RISK (Skip for now)
|
||||
8. Fix thread safety issue in rpcmining.cpp (requires testing)
|
||||
9. Improve protocol.h encapsulation (may affect other code)
|
||||
10. Address >2GB file handling in smessage.cpp
|
||||
|
||||
## Decisions
|
||||
|
||||
### What NOT to Change
|
||||
- **Consensus code** - main.cpp (validation), kernel.cpp (PoS), miner.cpp (staking)
|
||||
- **Serialization** - Any READWRITE, serialize/deserialize code
|
||||
- **Protocol constants** - Network message types, version numbers
|
||||
- **Third-party code** - leveldb/, tor/, sph_types.h, xxhash/, lz4/
|
||||
|
||||
### What's Safe to Change
|
||||
- Comments and documentation
|
||||
- Variable names (in non-consensus code)
|
||||
- Code organization (splitting large functions)
|
||||
- Logging statements
|
||||
- UI code (qt/)
|
||||
- RPC interface (as long as API contract preserved)
|
||||
|
||||
## Initial Cleanup (2026-03-22)
|
||||
|
||||
### Actions Taken
|
||||
1. Created this documentation file
|
||||
2. Created cleanup/desloppify branch
|
||||
3. Inventoried all TODOs/FIXMEs
|
||||
|
||||
### Next Steps
|
||||
1. Add documentation comments to TODO items
|
||||
2. Review printf/cout usage patterns
|
||||
3. Check for compiler warnings
|
||||
4. Consider low-risk improvements
|
||||
|
||||
## Notes
|
||||
- This is a Bitcoin-derived codebase, so many patterns follow Bitcoin Core conventions
|
||||
- Recent v5.3.x work already modernized to C++17 and removed Boost - good foundation
|
||||
- Code is generally well-structured; main improvements are documentation and minor cleanup
|
||||
@@ -0,0 +1,76 @@
|
||||
# Triangles Cleanup Strategy - Safe Improvements
|
||||
|
||||
**Branch:** `cleanup/safe-improvements`
|
||||
**Goal:** Improve code quality without touching consensus-critical code
|
||||
|
||||
## ✅ SAFE TO FIX
|
||||
|
||||
### 1. Compiler Warnings (Non-Consensus)
|
||||
- **C++11 literal-suffix warnings** - Add spaces between literals and suffixes
|
||||
- **Unused variables/functions** - Remove dead code (verify not consensus-critical first)
|
||||
- **Deprecated-copy warnings** - Fix CScript assignment operator if safe
|
||||
|
||||
### 2. Code Style Improvements
|
||||
- Remove `using namespace std` from headers (keep in .cpp files)
|
||||
- Standardize logging patterns
|
||||
- Improve code comments (remove unclear/misleading ones)
|
||||
- Add context to TODOs/FIXMEs
|
||||
|
||||
### 3. Documentation
|
||||
- Add inline comments for thread safety concerns
|
||||
- Document collision vulnerabilities
|
||||
- Improve function/class documentation
|
||||
|
||||
## ❌ DO NOT TOUCH
|
||||
|
||||
### Consensus-Critical Code
|
||||
- **OpenSSL SHA256/RIPEMD160 usage** - Deprecated warnings OK, do not change
|
||||
- **BN_is_prime_ex** - Crypto library deprecation, leave as-is
|
||||
- **Hash algorithms** - Third-party libraries with warnings, consensus-critical
|
||||
- **Block validation logic** - Any code affecting block/transaction validation
|
||||
- **Merkle tree construction** - Core consensus
|
||||
- **Proof-of-Work/Proof-of-Stake** - Staking/mining algorithms
|
||||
|
||||
### How to Identify Consensus Code
|
||||
- Files in `src/` related to: `main.cpp`, `main.h`, block validation, transaction validation
|
||||
- Anything in hash algorithm libraries
|
||||
- Cryptographic primitives
|
||||
- Network protocol message formats (version, serialization)
|
||||
|
||||
## Incremental Testing Strategy
|
||||
|
||||
1. **One warning category at a time**
|
||||
2. **Compile after each change**
|
||||
3. **Test basic functionality:**
|
||||
- `trianglesd getinfo`
|
||||
- `trianglesd getblockchaininfo`
|
||||
- Verify block sync works
|
||||
4. **Commit incrementally** with clear messages
|
||||
|
||||
## Warning Categories (From Build Output)
|
||||
|
||||
```
|
||||
1. C++11 literal-suffix: ~20 instances (util.h, net.h, alert.cpp)
|
||||
2. OpenSSL deprecation: SHA256, RIPEMD160 (DO NOT FIX)
|
||||
3. BN_is_prime_ex: crypto library (DO NOT FIX)
|
||||
4. Deprecated-copy: CScript assignment (REVIEW CAREFULLY)
|
||||
5. Unused variables/functions: Various (SAFE IF NOT CONSENSUS)
|
||||
```
|
||||
|
||||
## Branch History
|
||||
|
||||
- Previous work: `cleanup/desloppify` (documentation improvements, merged to master)
|
||||
- This branch: Focus on safe compiler warnings and code quality
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Before pushing each commit:
|
||||
- [ ] Code compiles successfully
|
||||
- [ ] No new warnings introduced
|
||||
- [ ] trianglesd runs without errors
|
||||
- [ ] getinfo/getblockchaininfo work
|
||||
- [ ] No consensus-critical code touched
|
||||
|
||||
---
|
||||
|
||||
**Principle:** When in doubt, don't touch it. A clean codebase is worthless if the blockchain forks.
|
||||
+21
-26
@@ -11,7 +11,7 @@ trianglesd / triangles-qt
|
||||
├── tor_embedded.cpp ← calls tor_run_main() in a background thread
|
||||
├── tor_process.cpp ← fallback: launches external tor binary (already works)
|
||||
├── onion_v3.cpp ← V3 onion address generation / SOCKS5 proxy logic
|
||||
└── libtor.a ← static Tor library (built from official source)
|
||||
└── libtor.a ← aggregate static Tor library (built from official source)
|
||||
```
|
||||
|
||||
When compiled with `ENABLE_TOR_EMBEDDED`, the wallet calls `tor_run_main()` from
|
||||
@@ -34,7 +34,7 @@ This puts the full Tor source at `src/tor/tor-src/`.
|
||||
Current imported checkout in this repo: `release-0.4.9` at commit `1442ca4`.
|
||||
There is also a helper build script at `src/tor/build-libtor.sh`.
|
||||
|
||||
## Step 2: Build libtor.a (Linux)
|
||||
## Step 2: Build libtor.a
|
||||
|
||||
Tor uses autotools. Build it as a static library:
|
||||
|
||||
@@ -71,10 +71,9 @@ Or from the repo root:
|
||||
./src/tor/build-libtor.sh
|
||||
```
|
||||
|
||||
After building, the static libraries are in `src/tor/tor-src/src/`:
|
||||
- `src/core/libtor-app.a`
|
||||
After building, the static libraries are in `src/tor/tor-src/`:
|
||||
- `libtor.a`
|
||||
- `src/lib/libtor-*.a` (multiple component libs)
|
||||
- `src/trunnel/libor-trunnel.a`
|
||||
|
||||
The header `src/feature/api/tor_api.h` provides the public C API:
|
||||
```c
|
||||
@@ -98,22 +97,14 @@ make -f makefile.unix \
|
||||
```
|
||||
|
||||
You may need to adjust the `-l` flags in the makefile depending on the exact
|
||||
library names Tor produces. Check `src/tor/tor-src/src/` after building:
|
||||
library names Tor produces. Check `src/tor/tor-src/` after building:
|
||||
|
||||
```bash
|
||||
find tor/tor-src/src -name '*.a' | sort
|
||||
find tor/tor-src -name '*.a' | sort
|
||||
```
|
||||
|
||||
Common libraries to link (order matters):
|
||||
```
|
||||
-ltor-app -lor -lor-ctime -lor-evloop -lor-event -lor-compress
|
||||
-lor-container -lor-crypt-ops -lor-encoding -lor-err -lor-fs
|
||||
-lor-intmath -lor-lock -lor-log -lor-malloc -lor-math -lor-memarea
|
||||
-lor-meminfo -lor-net -lor-osinfo -lor-process -lor-sandbox
|
||||
-lor-smartlist-core -lor-string -lor-term -lor-thread -lor-time
|
||||
-lor-tls -lor-trace -lor-version -lor-wallclock
|
||||
-lor-trunnel
|
||||
```
|
||||
On the imported `release-0.4.9` checkout in this repo, the simplest working
|
||||
link path is the aggregate `libtor.a` plus the normal dependency libraries.
|
||||
|
||||
### Windows (triangles-qt.pro)
|
||||
|
||||
@@ -126,9 +117,14 @@ qmake "USE_TOR_EMBEDDED=1" \
|
||||
Both build systems now default to:
|
||||
- source root: `src/tor/tor-src`
|
||||
- include path: `src/tor/tor-src/src/feature/api`
|
||||
- library paths: `src/tor/tor-src/src/core`, `src/tor/tor-src/src/lib`, `src/tor/tor-src/src/trunnel`
|
||||
- library path: `src/tor/tor-src`
|
||||
- embedded Tor library: `-ltor`
|
||||
|
||||
Override `TOR_EMBEDDED_LIBS` if the actual Tor static library names differ on your platform/build.
|
||||
On Windows, the imported Tor `0.4.9.5` build also needed:
|
||||
- `-llzma`
|
||||
- `-lzstd`
|
||||
- `-liphlpapi`
|
||||
- `-lshlwapi` (already linked by Triangles)
|
||||
|
||||
## Step 4: Wire into init.cpp
|
||||
|
||||
@@ -182,10 +178,9 @@ The embedded Tor respects these command-line flags:
|
||||
src/tor/
|
||||
├── tor-src/ ← git submodule (official Tor repo)
|
||||
│ └── src/
|
||||
│ ├── core/libtor-app.a
|
||||
│ ├── lib/libor-*.a
|
||||
│ ├── trunnel/libor-trunnel.a
|
||||
│ ├── lib/libtor-*.a
|
||||
│ └── feature/api/tor_api.h
|
||||
│ └── libtor.a
|
||||
├── tor_embedded.h ← CTorEmbedded class header
|
||||
├── tor_embedded.cpp ← implementation (calls tor_run_main)
|
||||
├── tor_process.h ← external Tor process manager (fallback)
|
||||
@@ -213,11 +208,11 @@ object instead of raw `tor_main(int argc, char** argv)`.
|
||||
**Tor fails to bootstrap**: Check firewall rules. Tor needs outbound TCP to the
|
||||
Tor network (ports 80, 443, 9001, 9030).
|
||||
|
||||
**Link errors with libtor**: The Tor static libraries must be linked in
|
||||
dependency order. If you get undefined symbols, reorder the `-l` flags or use
|
||||
`-Wl,--start-group ... -Wl,--end-group` to resolve circular deps:
|
||||
**Link errors with libtor**: Prefer the aggregate `libtor.a` from the top level
|
||||
of the Tor build tree. On the imported Windows/MSYS2 build in this repo, the
|
||||
minimal verified link set was:
|
||||
```
|
||||
LIBS += -Wl,--start-group -ltor-app -lor -lor-ctime ... -Wl,--end-group
|
||||
-ltor -levent -lssl -lcrypto -lz -llzma -lzstd -lws2_32 -liphlpapi -lshlwapi
|
||||
```
|
||||
|
||||
**OpenSSL version mismatch**: Both Tor and Triangles must link against the same
|
||||
|
||||
@@ -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.
|
||||
@@ -3,7 +3,7 @@
|
||||
# Generated by qmake (3.1) (Qt 5.15.18)
|
||||
# Project: triangles-qt.pro
|
||||
# Template: app
|
||||
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
|
||||
# Command: C:/msys64/mingw64/bin/qmake-qt5.exe -o Makefile triangles-qt.pro
|
||||
#############################################################################
|
||||
|
||||
MAKEFILE = Makefile
|
||||
@@ -156,7 +156,7 @@ Makefile: triangles-qt.pro C:/msys64/mingw64/share/qt5/mkspecs/win32-g++/qmake.c
|
||||
C:/msys64/mingw64/lib/qtmain.prl \
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf \
|
||||
src/qt/triangles.qrc
|
||||
$(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
|
||||
$(QMAKE) -o Makefile triangles-qt.pro
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/spec_pre.prf:
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/qdevice.pri:
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/device_config.prf:
|
||||
@@ -244,7 +244,7 @@ C:/msys64/mingw64/lib/qtmain.prl:
|
||||
C:/msys64/mingw64/share/qt5/mkspecs/features/build_pass.prf:
|
||||
src/qt/triangles.qrc:
|
||||
qmake: FORCE
|
||||
@$(QMAKE) -o Makefile triangles-qt.pro USE_QRCODE=1 USE_UPNP=-
|
||||
@$(QMAKE) -o Makefile triangles-qt.pro
|
||||
|
||||
qmake_all: FORCE
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ make -j$(nproc) -f makefile.unix USE_UPNP=0
|
||||
strip trianglesd
|
||||
```
|
||||
|
||||
Run the unit test suite:
|
||||
```bash
|
||||
make -C src -f makefile.unix test
|
||||
```
|
||||
|
||||
### Linux (AlmaLinux 9 / RHEL 9)
|
||||
|
||||
Install dependencies:
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# TODO/FIXME Documentation
|
||||
|
||||
Detailed context for each TODO/FIXME in the codebase.
|
||||
|
||||
## Critical (Needs Attention)
|
||||
|
||||
### src/rpcmining.cpp:263 - Thread Safety Issue
|
||||
```cpp
|
||||
static mapNewBlock_t mapNewBlock; // FIXME: thread safety
|
||||
```
|
||||
**Issue:** Static variable accessed by multiple RPC threads without mutex protection.
|
||||
**Impact:** Potential race condition in getwork RPC (used for mining).
|
||||
**Status:** Low priority - PoW mining ended at block 9000, this code path rarely used.
|
||||
**Fix:** Add std::mutex and lock_guard if getwork usage increases.
|
||||
|
||||
### src/qt/walletmodel.cpp:249 - Collision Risk
|
||||
```cpp
|
||||
if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future
|
||||
```
|
||||
**Issue:** Balance check may have edge case causing transaction collisions.
|
||||
**Context:** In createTransaction fee calculation loop.
|
||||
**Status:** Needs investigation - unclear what "collisions" means here.
|
||||
**Fix:** Review Bitcoin Core's current implementation of this logic.
|
||||
|
||||
### src/smessage.cpp - File Size Limits
|
||||
```cpp
|
||||
// Lines 863, 2219, 2373: "TODO files must be split if > 2GB"
|
||||
```
|
||||
**Issue:** Secure message storage files not split when exceeding 2GB.
|
||||
**Impact:** May fail on 32-bit systems or with large message volumes.
|
||||
**Status:** Low priority - unlikely to reach 2GB in practice.
|
||||
**Fix:** Implement file rotation when approaching 2GB limit.
|
||||
|
||||
## Medium Priority (Encapsulation/API)
|
||||
|
||||
### src/protocol.h - Make Members Private
|
||||
```cpp
|
||||
// Lines 50, 100, 132: "TODO: make private (improves encapsulation)"
|
||||
```
|
||||
**Issue:** CAddress, CInv, CMessageHeader have public data members.
|
||||
**Impact:** Poor encapsulation, harder to maintain invariants.
|
||||
**Status:** Deferred - would require extensive refactoring.
|
||||
**Fix:** Add getter/setter methods, make members private, update all call sites.
|
||||
|
||||
### src/wallet.h:378 - nOrderPos Calculation
|
||||
```cpp
|
||||
nOrderPos = -1; // TODO: calculate elsewhere
|
||||
```
|
||||
**Issue:** Transaction ordering position calculated in constructor.
|
||||
**Impact:** Minor - works but not ideal separation of concerns.
|
||||
**Status:** Deferred - no functional issue.
|
||||
**Fix:** Move calculation to WalletDB when transaction is added.
|
||||
|
||||
### src/rpcwallet.cpp / src/qt/askpassphrasedialog.cpp - SecureString Conversion
|
||||
**Issue:** Password-handling paths were converting through `.c_str()` because `SecureString`
|
||||
did not have a convenient conversion helper from `std::string`.
|
||||
**Impact:** Unnecessary C-string shims in sensitive code paths.
|
||||
**Status:** Resolved.
|
||||
**Fix:** Added `MakeSecureString(const std::string&)` in `src/allocators.h` and updated
|
||||
the wallet RPC and passphrase dialog call sites to use it directly.
|
||||
|
||||
## Low Priority (Nice-to-Have)
|
||||
|
||||
### src/util.cpp:1322 - Disabled Feature
|
||||
```cpp
|
||||
// TODO: This is currently disabled because it needs to be verified to work
|
||||
```
|
||||
**Context:** File descriptor management code.
|
||||
**Status:** Intentionally disabled pending verification.
|
||||
**Fix:** Test thoroughly, then enable if needed.
|
||||
|
||||
### src/tor/tor_embedded.cpp:209 - Tor Shutdown API
|
||||
```cpp
|
||||
// TODO: Tor 0.4.9+ may add tor_api_shutdown(), use it when available
|
||||
```
|
||||
**Context:** Embedded Tor cleanup.
|
||||
**Status:** Waiting for upstream Tor API.
|
||||
**Fix:** Check Tor 0.4.9+ releases for new API, integrate when stable.
|
||||
|
||||
### src/init.cpp:442 - Sanity Checks
|
||||
```cpp
|
||||
// TODO: remaining sanity checks, see #4081
|
||||
```
|
||||
**Context:** Bitcoin Core issue #4081 - additional startup sanity checks.
|
||||
**Status:** Deferred - core checks already in place.
|
||||
**Fix:** Review Bitcoin Core's current sanity check implementation.
|
||||
|
||||
### src/rpcmining.cpp:232 - DRM Comment
|
||||
```cpp
|
||||
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0]; // FIXME - DRM!
|
||||
```
|
||||
**Issue:** Unclear what "DRM" means here - likely "Data Race Maybe"?
|
||||
**Status:** Needs clarification from original author.
|
||||
**Fix:** Investigate if there's an actual issue, otherwise remove comment.
|
||||
|
||||
## Deferred (External/Low Impact)
|
||||
|
||||
### LevelDB TODOs (src/leveldb/*)
|
||||
**Status:** Upstream LevelDB issues - don't modify embedded library.
|
||||
**Action:** None - track upstream LevelDB project.
|
||||
|
||||
### Qt TODOs (src/qt/*)
|
||||
**Status:** UI improvements, not critical.
|
||||
**Action:** Track as nice-to-have enhancements.
|
||||
|
||||
### Secure Message TODOs (src/smessage.cpp)
|
||||
Multiple minor improvements suggested:
|
||||
- Include hash in certain operations
|
||||
- Improve thread shutdown
|
||||
- Set default recv/recvAnon behavior
|
||||
- Update outbox after PoW completes
|
||||
|
||||
**Status:** Non-critical enhancements.
|
||||
**Action:** Consider for future encrypted messaging upgrades.
|
||||
|
||||
## Summary
|
||||
|
||||
**Critical:** 3 items (thread safety, balance collision, file limits)
|
||||
**Medium:** 6 items (encapsulation, SecureString)
|
||||
**Low:** 5 items (disabled features, upstream APIs)
|
||||
**Deferred:** ~24 items (external libs, minor enhancements)
|
||||
|
||||
**Recommendation:** Focus on documenting critical items in code comments, defer fixes until specific issues arise.
|
||||
@@ -3,7 +3,7 @@
|
||||
# Run on a Linux x64 system with appimagetool installed
|
||||
set -e
|
||||
|
||||
VERSION="5.1.5"
|
||||
VERSION="5.3.7"
|
||||
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.3.7
|
||||
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.3.7</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.3.7-1
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Run from the packaging/debian directory
|
||||
set -e
|
||||
|
||||
VERSION="5.1.5"
|
||||
VERSION="5.3.7"
|
||||
PKGDIR="triangles_${VERSION}-1_amd64"
|
||||
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
|
||||
|
||||
@@ -20,8 +20,8 @@ cp DEBIAN/control "$PKGDIR/DEBIAN/"
|
||||
|
||||
# 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"
|
||||
|
||||
# Create desktop entry
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
LABEL maintainer="Cryptographic Triangles Team"
|
||||
LABEL description="Cryptographic Triangles (TRI) headless daemon"
|
||||
LABEL version="5.3.7"
|
||||
|
||||
ARG VERSION=5.3.7
|
||||
|
||||
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.3.7
|
||||
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.3.7/Cryptographic-Triangles-v5.3.7-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.3.7/Cryptographic-Triangles-v5.3.7-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.3.7"
|
||||
|
||||
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.3.7";
|
||||
|
||||
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.3.7"
|
||||
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.3.7
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PackageIdentifier: CryptographicTriangles.TrianglesQt
|
||||
PackageVersion: 5.1.5
|
||||
PackageVersion: 5.3.7
|
||||
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.3.7/Cryptographic-Triangles-5.3.7-win-x64.zip
|
||||
InstallerSha256: 6F002A669A7E92AAF3D8DD7B1AE80F06A086C99A15CA05CF107665009FFC06B7
|
||||
ManifestType: singleton
|
||||
ManifestVersion: 1.6.0
|
||||
|
||||
+11
-5
@@ -1,6 +1,6 @@
|
||||
name: triangles
|
||||
base: core22
|
||||
version: '5.1.5'
|
||||
version: '5.3.7'
|
||||
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.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-qt
|
||||
source-type: file
|
||||
organize:
|
||||
triangles-qt-linux: bin/triangles-qt
|
||||
Cryptographic-Triangles-v5.3.7-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.3.7/Cryptographic-Triangles-v5.3.7-linux-x64-daemon
|
||||
source-type: file
|
||||
organize:
|
||||
trianglesd-linux: bin/trianglesd
|
||||
Cryptographic-Triangles-v5.3.7-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
|
||||
|
||||
+2
-2
@@ -53,8 +53,8 @@ std::string CUnsignedAlert::ToString() const
|
||||
return strprintf(
|
||||
"CAlert(\n"
|
||||
" nVersion = %d\n"
|
||||
" nRelayUntil = %"PRId64"\n"
|
||||
" nExpiration = %"PRId64"\n"
|
||||
" nRelayUntil = %" PRId64 "\n"
|
||||
" nExpiration = %" PRId64 "\n"
|
||||
" nID = %d\n"
|
||||
" nCancel = %d\n"
|
||||
" setCancel = %s\n"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
// Copyright (c) 2024 Triangles developers
|
||||
// Distributed under the MIT/X11 software license
|
||||
|
||||
#include "bootstrap.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "version.h"
|
||||
#include "uint256.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
// Forward declarations to avoid pulling in heavy consensus headers
|
||||
extern bool fTestNet;
|
||||
namespace Checkpoints { bool IsKnownCheckpoint(int nHeight, const uint256& hash); }
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
namespace Bootstrap {
|
||||
|
||||
bool NeedsBootstrap(const fs::path& dataDir)
|
||||
{
|
||||
return !fs::exists(dataDir / "blk0001.dat");
|
||||
}
|
||||
|
||||
bool DownloadFile(const std::string& host, const std::string& urlPath,
|
||||
const fs::path& destPath,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError)
|
||||
{
|
||||
try {
|
||||
boost::asio::io_context io_context;
|
||||
tcp::resolver resolver(io_context);
|
||||
|
||||
boost::system::error_code resolve_ec;
|
||||
tcp::resolver::results_type endpoints =
|
||||
resolver.resolve(host, std::to_string(PORT), resolve_ec);
|
||||
if (resolve_ec) {
|
||||
strError = "Cannot resolve host: " + host;
|
||||
return false;
|
||||
}
|
||||
|
||||
tcp::socket socket(io_context);
|
||||
boost::asio::connect(socket, endpoints);
|
||||
|
||||
// Send HTTP GET request
|
||||
std::string request =
|
||||
"GET " + urlPath + " HTTP/1.1\r\n"
|
||||
"Host: " + host + "\r\n"
|
||||
"Connection: close\r\n"
|
||||
"User-Agent: Triangles\r\n"
|
||||
"\r\n";
|
||||
boost::asio::write(socket, boost::asio::buffer(request));
|
||||
|
||||
// Read response headers
|
||||
boost::asio::streambuf response_buf;
|
||||
boost::asio::read_until(socket, response_buf, "\r\n\r\n");
|
||||
|
||||
std::istream response_stream(&response_buf);
|
||||
|
||||
// Parse status line
|
||||
std::string http_version;
|
||||
unsigned int status_code = 0;
|
||||
response_stream >> http_version >> status_code;
|
||||
std::string status_message;
|
||||
std::getline(response_stream, status_message);
|
||||
|
||||
if (status_code != 200) {
|
||||
strError = "HTTP error " + std::to_string(status_code) + " for " + urlPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse headers for Content-Length
|
||||
int64_t content_length = 0;
|
||||
std::string header_line;
|
||||
while (std::getline(response_stream, header_line) && header_line != "\r") {
|
||||
std::string lower_header = header_line;
|
||||
std::transform(lower_header.begin(), lower_header.end(),
|
||||
lower_header.begin(), ::tolower);
|
||||
if (lower_header.find("content-length:") == 0) {
|
||||
content_length = std::stoll(header_line.substr(header_line.find(':') + 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Open output file
|
||||
FILE* file = fopen(destPath.string().c_str(), "wb");
|
||||
if (!file) {
|
||||
strError = "Cannot create file: " + destPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t bytes_written = 0;
|
||||
|
||||
// Write any data remaining in the header buffer (body starts here)
|
||||
if (response_buf.size() > 0) {
|
||||
std::istreambuf_iterator<char> eos;
|
||||
std::string remaining(std::istreambuf_iterator<char>(response_stream), eos);
|
||||
if (!remaining.empty()) {
|
||||
fwrite(remaining.data(), 1, remaining.size(), file);
|
||||
bytes_written += remaining.size();
|
||||
}
|
||||
}
|
||||
|
||||
// Read remaining body in chunks
|
||||
std::vector<char> chunk(65536); // 64 KB
|
||||
boost::system::error_code ec;
|
||||
int64_t last_progress = 0;
|
||||
|
||||
while (true) {
|
||||
size_t n = socket.read_some(boost::asio::buffer(chunk), ec);
|
||||
if (n > 0) {
|
||||
fwrite(chunk.data(), 1, n, file);
|
||||
bytes_written += n;
|
||||
|
||||
// Report progress every 256 KB
|
||||
if (progressFn && (bytes_written - last_progress >= 262144)) {
|
||||
last_progress = bytes_written;
|
||||
progressFn(bytes_written, content_length);
|
||||
}
|
||||
}
|
||||
if (ec == boost::asio::error::eof)
|
||||
break;
|
||||
if (ec) {
|
||||
fclose(file);
|
||||
fs::remove(destPath);
|
||||
strError = "Network error: " + ec.message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
// Verify download size if Content-Length was provided
|
||||
if (content_length > 0 && bytes_written != content_length) {
|
||||
fs::remove(destPath);
|
||||
strError = "Incomplete download: got " + std::to_string(bytes_written)
|
||||
+ " of " + std::to_string(content_length) + " bytes";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (std::exception& e) {
|
||||
strError = std::string("Download failed: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool FetchFileList(const std::string& host,
|
||||
std::vector<std::string>& files,
|
||||
std::string& strError)
|
||||
{
|
||||
// Download filelist.txt to a temp file
|
||||
fs::path tmpPath = fs::temp_directory_path() / "triangles_bootstrap_filelist.txt";
|
||||
|
||||
std::string urlPath = std::string(BASE_PATH) + "filelist.txt";
|
||||
if (!DownloadFile(host, urlPath, tmpPath, nullptr, strError))
|
||||
return false;
|
||||
|
||||
// Read lines
|
||||
std::ifstream in(tmpPath.string().c_str());
|
||||
if (!in.is_open()) {
|
||||
strError = "Cannot read downloaded file list";
|
||||
return false;
|
||||
}
|
||||
|
||||
files.clear();
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
boost::trim(line);
|
||||
if (!line.empty() && line[0] != '#')
|
||||
files.push_back(line);
|
||||
}
|
||||
in.close();
|
||||
fs::remove(tmpPath);
|
||||
|
||||
if (files.empty()) {
|
||||
strError = "File list is empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- tar.gz bootstrap support ---
|
||||
|
||||
namespace {
|
||||
|
||||
// Parse a tar octal field (ASCII octal, null/space terminated)
|
||||
static int64_t ParseTarOctal(const char* field, size_t len)
|
||||
{
|
||||
int64_t result = 0;
|
||||
for (size_t i = 0; i < len && field[i] != '\0' && field[i] != ' '; i++) {
|
||||
if (field[i] < '0' || field[i] > '7') continue;
|
||||
result = (result << 3) | (field[i] - '0');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Extract a tar.gz file to a destination directory
|
||||
static bool ExtractTarGz(const fs::path& tarGzPath,
|
||||
const fs::path& destDir,
|
||||
std::string& strError)
|
||||
{
|
||||
gzFile gz = gzopen(tarGzPath.string().c_str(), "rb");
|
||||
if (!gz) {
|
||||
strError = "Cannot open " + tarGzPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
gzbuffer(gz, 262144); // 256 KB buffer for performance
|
||||
|
||||
char header[512];
|
||||
|
||||
while (true) {
|
||||
int bytesRead = gzread(gz, header, 512);
|
||||
if (bytesRead == 0) break; // EOF
|
||||
if (bytesRead != 512) {
|
||||
strError = "Truncated tar header";
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
|
||||
// End-of-archive marker (zero block)
|
||||
bool allZero = true;
|
||||
for (int i = 0; i < 512; i++) {
|
||||
if (header[i] != 0) { allZero = false; break; }
|
||||
}
|
||||
if (allZero) break;
|
||||
|
||||
// Parse filename: name (offset 0, 100 bytes) + optional prefix (offset 345, 155 bytes)
|
||||
char name[101] = {0};
|
||||
char prefix[156] = {0};
|
||||
memcpy(name, header, 100);
|
||||
memcpy(prefix, header + 345, 155);
|
||||
|
||||
std::string fullName;
|
||||
if (prefix[0] != '\0')
|
||||
fullName = std::string(prefix) + "/" + std::string(name);
|
||||
else
|
||||
fullName = std::string(name);
|
||||
|
||||
// Security: reject absolute paths and path traversal
|
||||
if (fullName.empty() || fullName[0] == '/' || fullName.find("..") != std::string::npos) {
|
||||
strError = "Unsafe path in tar archive: " + fullName;
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
|
||||
char typeflag = header[156];
|
||||
int64_t fileSize = ParseTarOctal(header + 124, 12);
|
||||
|
||||
if (typeflag == '5' || (!fullName.empty() && fullName.back() == '/')) {
|
||||
// Directory entry
|
||||
fs::create_directories(destDir / fullName);
|
||||
} else if (typeflag == '0' || typeflag == '\0') {
|
||||
// Regular file
|
||||
fs::path filePath = destDir / fullName;
|
||||
fs::create_directories(filePath.parent_path());
|
||||
|
||||
FILE* outFile = fopen(filePath.string().c_str(), "wb");
|
||||
if (!outFile) {
|
||||
strError = "Cannot create file: " + filePath.string();
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t remaining = fileSize;
|
||||
char buf[65536];
|
||||
while (remaining > 0) {
|
||||
int toRead = (remaining > (int64_t)sizeof(buf)) ? (int)sizeof(buf) : (int)remaining;
|
||||
int n = gzread(gz, buf, toRead);
|
||||
if (n <= 0) {
|
||||
fclose(outFile);
|
||||
strError = "Truncated tar data for: " + fullName;
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
fwrite(buf, 1, n, outFile);
|
||||
remaining -= n;
|
||||
}
|
||||
fclose(outFile);
|
||||
|
||||
// Skip padding to next 512-byte boundary
|
||||
int64_t pad = (512 - (fileSize % 512)) % 512;
|
||||
if (pad > 0) {
|
||||
char padBuf[512];
|
||||
if (gzread(gz, padBuf, (unsigned)pad) != (int)pad) {
|
||||
strError = "Truncated tar padding for: " + fullName;
|
||||
gzclose(gz);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Unknown entry type - skip its data
|
||||
int64_t totalSkip = fileSize + ((512 - (fileSize % 512)) % 512);
|
||||
char skipBuf[512];
|
||||
while (totalSkip > 0) {
|
||||
int toRead = (totalSkip > 512) ? 512 : (int)totalSkip;
|
||||
if (gzread(gz, skipBuf, toRead) != toRead) break;
|
||||
totalSkip -= toRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gzclose(gz);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool ParseManifest(const fs::path& manifestPath,
|
||||
SnapshotManifest& manifest,
|
||||
std::string& strError)
|
||||
{
|
||||
std::ifstream in(manifestPath.string().c_str());
|
||||
if (!in.is_open()) {
|
||||
strError = "Cannot open " + manifestPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
manifest.format = 0;
|
||||
manifest.network.clear();
|
||||
manifest.height = -1;
|
||||
manifest.hash.clear();
|
||||
manifest.dbversion = 0;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
boost::trim(line);
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
size_t eq = line.find('=');
|
||||
if (eq == std::string::npos)
|
||||
continue;
|
||||
|
||||
std::string key = line.substr(0, eq);
|
||||
std::string val = line.substr(eq + 1);
|
||||
boost::trim(key);
|
||||
boost::trim(val);
|
||||
|
||||
if (key == "format")
|
||||
manifest.format = std::atoi(val.c_str());
|
||||
else if (key == "network")
|
||||
manifest.network = val;
|
||||
else if (key == "height")
|
||||
manifest.height = std::atoi(val.c_str());
|
||||
else if (key == "hash")
|
||||
manifest.hash = val;
|
||||
else if (key == "dbversion")
|
||||
manifest.dbversion = std::atoi(val.c_str());
|
||||
}
|
||||
in.close();
|
||||
|
||||
if (manifest.format == 0) {
|
||||
strError = "Manifest missing 'format' field";
|
||||
return false;
|
||||
}
|
||||
if (manifest.network.empty()) {
|
||||
strError = "Manifest missing 'network' field";
|
||||
return false;
|
||||
}
|
||||
if (manifest.height < 0) {
|
||||
strError = "Manifest missing or invalid 'height' field";
|
||||
return false;
|
||||
}
|
||||
if (manifest.hash.empty()) {
|
||||
strError = "Manifest missing 'hash' field";
|
||||
return false;
|
||||
}
|
||||
if (manifest.dbversion == 0) {
|
||||
strError = "Manifest missing 'dbversion' field";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VerifyManifest(const SnapshotManifest& manifest,
|
||||
std::string& strError)
|
||||
{
|
||||
if (manifest.format != 1) {
|
||||
strError = "Unsupported manifest format: " + std::to_string(manifest.format);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string expectedNetwork = fTestNet ? "test" : "main";
|
||||
if (manifest.network != expectedNetwork) {
|
||||
strError = "Network mismatch: manifest says '" + manifest.network
|
||||
+ "', expected '" + expectedNetwork + "'";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (manifest.dbversion != DATABASE_VERSION) {
|
||||
strError = "DB version mismatch: manifest says "
|
||||
+ std::to_string(manifest.dbversion)
|
||||
+ ", binary expects " + std::to_string(DATABASE_VERSION);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 manifestHash(manifest.hash);
|
||||
if (manifestHash == 0) {
|
||||
strError = "Invalid hash in manifest: " + manifest.hash;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Checkpoints::IsKnownCheckpoint(manifest.height, manifestHash)) {
|
||||
strError = "Height " + std::to_string(manifest.height)
|
||||
+ " / hash " + manifest.hash
|
||||
+ " is not a known checkpoint";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DownloadBootstrap(const std::string& host,
|
||||
const fs::path& dataDir,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError)
|
||||
{
|
||||
bool gotBlockFile = false;
|
||||
|
||||
// Try downloading bootstrap.tar.gz first
|
||||
fs::path tmpTarGz = dataDir / "bootstrap.tar.gz.tmp";
|
||||
std::string tarUrl = std::string(BASE_PATH) + "bootstrap.tar.gz";
|
||||
|
||||
bool tarDownloaded = DownloadFile(host, tarUrl, tmpTarGz, progressFn, strError);
|
||||
|
||||
if (tarDownloaded) {
|
||||
bool extractOk = ExtractTarGz(tmpTarGz, dataDir, strError);
|
||||
fs::remove(tmpTarGz);
|
||||
|
||||
if (extractOk && fs::exists(dataDir / "blk0001.dat"))
|
||||
gotBlockFile = true;
|
||||
// If extraction failed, fall through to legacy path
|
||||
}
|
||||
|
||||
if (!gotBlockFile) {
|
||||
// Fallback: try filelist.txt + individual file downloads
|
||||
std::string fallbackError;
|
||||
std::vector<std::string> files;
|
||||
if (!FetchFileList(host, files, fallbackError)) {
|
||||
if (!tarDownloaded)
|
||||
strError = strError + " (fallback also failed: " + fallbackError + ")";
|
||||
else
|
||||
strError = "Extraction failed: " + strError + " (fallback also failed: " + fallbackError + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
fs::path destPath = dataDir / files[i];
|
||||
fs::create_directories(destPath.parent_path());
|
||||
|
||||
std::string urlPath = std::string(BASE_PATH) + files[i];
|
||||
if (!DownloadFile(host, urlPath, destPath, progressFn, strError))
|
||||
return false;
|
||||
}
|
||||
|
||||
gotBlockFile = fs::exists(dataDir / "blk0001.dat");
|
||||
}
|
||||
|
||||
if (!gotBlockFile) {
|
||||
strError = "No blk0001.dat after download";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the archive included a trusted pre-built index (txleveldb/)
|
||||
// with a valid snapshot.manifest. If verified, keep it to skip the
|
||||
// multi-hour FastImportBlockFile() rebuild.
|
||||
fs::path txleveldb = dataDir / "txleveldb";
|
||||
fs::path database = dataDir / "database";
|
||||
fs::path manifestPath = dataDir / "snapshot.manifest";
|
||||
|
||||
bool keepIndex = false;
|
||||
|
||||
if (fs::exists(manifestPath) && fs::exists(txleveldb)) {
|
||||
SnapshotManifest manifest;
|
||||
std::string manifestError;
|
||||
|
||||
if (ParseManifest(manifestPath, manifest, manifestError)) {
|
||||
printf("Bootstrap: snapshot.manifest found (format=%d, network=%s, "
|
||||
"height=%d, dbversion=%d)\n",
|
||||
manifest.format, manifest.network.c_str(),
|
||||
manifest.height, manifest.dbversion);
|
||||
|
||||
if (VerifyManifest(manifest, manifestError)) {
|
||||
printf("Bootstrap: manifest verified - keeping pre-built index "
|
||||
"(height %d, checkpoint match)\n", manifest.height);
|
||||
keepIndex = true;
|
||||
} else {
|
||||
printf("Bootstrap: manifest verification failed: %s\n",
|
||||
manifestError.c_str());
|
||||
}
|
||||
} else {
|
||||
printf("Bootstrap: cannot parse snapshot.manifest: %s\n",
|
||||
manifestError.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!keepIndex) {
|
||||
// No valid manifest or verification failed - delete the index.
|
||||
// FastImportBlockFile() will rebuild from blk0001.dat on next startup.
|
||||
printf("Bootstrap: removing extracted txleveldb/ (will rebuild index from blk0001.dat)\n");
|
||||
if (fs::exists(txleveldb))
|
||||
fs::remove_all(txleveldb);
|
||||
}
|
||||
|
||||
// Always remove BDB database/ dir (wallet environment from another machine)
|
||||
if (fs::exists(database))
|
||||
fs::remove_all(database);
|
||||
|
||||
// Clean up manifest file (not needed after verification)
|
||||
if (fs::exists(manifestPath))
|
||||
fs::remove(manifestPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Bootstrap
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2024 Triangles developers
|
||||
// Distributed under the MIT/X11 software license
|
||||
|
||||
#ifndef TRIANGLES_BOOTSTRAP_H
|
||||
#define TRIANGLES_BOOTSTRAP_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
namespace Bootstrap {
|
||||
|
||||
// Bootstrap server configuration
|
||||
static const char* DEFAULT_HOST = "bootstrap.cryptographic-triangles.org";
|
||||
static const char* FALLBACK_HOST = "194.233.88.206";
|
||||
static const char* BASE_PATH = "/";
|
||||
static const int PORT = 80;
|
||||
|
||||
// Progress callback: (bytesDownloaded, totalBytes)
|
||||
typedef std::function<void(int64_t, int64_t)> ProgressCallback;
|
||||
|
||||
// Check if data dir already has blockchain data
|
||||
bool NeedsBootstrap(const boost::filesystem::path& dataDir);
|
||||
|
||||
// Download a single file via HTTP GET, write to destPath
|
||||
bool DownloadFile(const std::string& host, const std::string& urlPath,
|
||||
const boost::filesystem::path& destPath,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError);
|
||||
|
||||
// Fetch the file manifest (list of relative paths to download)
|
||||
bool FetchFileList(const std::string& host,
|
||||
std::vector<std::string>& files,
|
||||
std::string& strError);
|
||||
|
||||
// Download bootstrap.tar.gz and extract to dataDir.
|
||||
// Falls back to filelist.txt + individual file download if tar.gz unavailable.
|
||||
bool DownloadBootstrap(const std::string& host,
|
||||
const boost::filesystem::path& dataDir,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError);
|
||||
|
||||
// Snapshot manifest (parsed from snapshot.manifest in bootstrap archive)
|
||||
struct SnapshotManifest {
|
||||
int format; // format version, must be 1
|
||||
std::string network; // "main" or "test"
|
||||
int height; // block height of the snapshot tip
|
||||
std::string hash; // block hash at that height (hex, no 0x prefix)
|
||||
int dbversion; // DATABASE_VERSION the txleveldb was built with
|
||||
};
|
||||
|
||||
// Parse a snapshot.manifest file into a SnapshotManifest struct.
|
||||
bool ParseManifest(const boost::filesystem::path& manifestPath,
|
||||
SnapshotManifest& manifest,
|
||||
std::string& strError);
|
||||
|
||||
// Verify a parsed manifest against compiled-in checkpoints and config.
|
||||
bool VerifyManifest(const SnapshotManifest& manifest,
|
||||
std::string& strError);
|
||||
|
||||
} // namespace Bootstrap
|
||||
|
||||
#endif // TRIANGLES_BOOTSTRAP_H
|
||||
@@ -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();
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
|
||||
// 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_MINOR 4
|
||||
#define CLIENT_VERSION_REVISION 1
|
||||
#define CLIENT_VERSION_BUILD 0
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+185
-21
@@ -12,6 +12,7 @@
|
||||
#include "checkpoints.h"
|
||||
#include "smessage.h"
|
||||
#include "openssl_compat.h"
|
||||
#include "bootstrap.h"
|
||||
#include "tor/tor_embedded.h"
|
||||
#include "tor/onion_v3.h"
|
||||
#include "tor/tor_process.h"
|
||||
@@ -50,6 +51,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
|
||||
@@ -94,17 +110,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)
|
||||
{
|
||||
@@ -157,6 +176,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();
|
||||
|
||||
@@ -175,10 +200,8 @@ void Shutdown(void* parg)
|
||||
pNotificationQueue = NULL;
|
||||
}
|
||||
|
||||
nTransactionsUpdated++;
|
||||
// CTxDB().Close();
|
||||
bitdb.Flush(false);
|
||||
StopNode();
|
||||
bitdb.Flush(true);
|
||||
fs::remove(GetPidFile());
|
||||
UnregisterWallet(pwalletMain);
|
||||
@@ -336,6 +359,7 @@ std::string HelpMessage()
|
||||
" -tor=<ip:port> " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n"
|
||||
" -notor " + _("Disable Tor startup and .onion connectivity") + "\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" +
|
||||
@@ -448,6 +472,7 @@ bool InitSanityCheck(void)
|
||||
*/
|
||||
bool AppInit2()
|
||||
{
|
||||
const int64_t nAppInitStart = GetTimeMillis();
|
||||
// ********************************************************* Step 1: setup
|
||||
#ifdef _MSC_VER
|
||||
// Turn off Microsoft heap dump noise
|
||||
@@ -671,6 +696,7 @@ bool AppInit2()
|
||||
// ********************************************************* Step 5: verify database integrity
|
||||
|
||||
uiInterface.InitMessage(_("Verifying database integrity..."));
|
||||
nStart = GetTimeMillis();
|
||||
|
||||
if (!bitdb.Open(GetDataDir()))
|
||||
{
|
||||
@@ -701,8 +727,10 @@ 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);
|
||||
//
|
||||
@@ -729,7 +757,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);
|
||||
@@ -796,6 +824,47 @@ 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)
|
||||
#ifndef QT_GUI
|
||||
if (GetBoolArg("-bootstrap", false))
|
||||
{
|
||||
int64_t nBootstrapStart = GetTimeMillis();
|
||||
fs::path dataPath = GetDataDir();
|
||||
std::string host = Bootstrap::DEFAULT_HOST;
|
||||
std::string strError;
|
||||
|
||||
uiInterface.InitMessage(_("Downloading blockchain snapshot..."));
|
||||
printf("Bootstrap: contacting %s...\n", host.c_str());
|
||||
|
||||
auto progressFn = [](int64_t bytesDownloaded, int64_t totalBytes) {
|
||||
if (totalBytes > 0) {
|
||||
printf("\rBootstrap: %lld / %lld MB (%lld%%)",
|
||||
(long long)(bytesDownloaded / (1024*1024)),
|
||||
(long long)(totalBytes / (1024*1024)),
|
||||
(long long)((bytesDownloaded * 100) / totalBytes));
|
||||
fflush(stdout);
|
||||
}
|
||||
};
|
||||
|
||||
bool success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
|
||||
if (!success) {
|
||||
host = Bootstrap::FALLBACK_HOST;
|
||||
printf("\nBootstrap: primary host failed, trying fallback %s...\n", host.c_str());
|
||||
success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
printf("\nBootstrap: failed: %s\n", strError.c_str());
|
||||
printf("Bootstrap: skipping, will sync from network.\n");
|
||||
} else {
|
||||
printf("\nBootstrap: done.\n");
|
||||
}
|
||||
StartupPerfLog("bootstrap_download", GetTimeMillis() - nBootstrapStart,
|
||||
strprintf("host=%s success=%d", host.c_str(), success));
|
||||
}
|
||||
#endif
|
||||
|
||||
// ********************************************************* Step 7: load blockchain
|
||||
|
||||
@@ -821,6 +890,18 @@ bool AppInit2()
|
||||
if (!LoadBlockIndex())
|
||||
return InitError(_("Error loading blkindex.dat"));
|
||||
|
||||
// If the block index is empty but blk0001.dat exists (bootstrap download),
|
||||
// fast-import: build the index directly from the block file without re-writing
|
||||
// data. Batches LevelDB commits every 200K blocks for speed.
|
||||
if (nBestHeight == 0 && boost::filesystem::exists(GetDataDir() / "blk0001.dat")
|
||||
&& mapBlockIndex.size() <= 1)
|
||||
{
|
||||
uiInterface.InitMessage(_("Importing bootstrap blocks..."));
|
||||
printf("Block index empty but blk0001.dat exists - running fast import...\n");
|
||||
int64_t nFastImportStart = GetTimeMillis();
|
||||
FastImportBlockFile();
|
||||
StartupPerfLog("bootstrap_fast_import", GetTimeMillis() - nFastImportStart, strprintf("bestheight=%d", nBestHeight));
|
||||
}
|
||||
|
||||
// as LoadBlockIndex can take several minutes, it's possible the user
|
||||
// requested to kill triangles-qt during the last operation. If so, exit.
|
||||
@@ -830,7 +911,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"))
|
||||
{
|
||||
@@ -924,7 +1022,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);
|
||||
|
||||
@@ -933,10 +1032,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)
|
||||
{
|
||||
@@ -968,7 +1069,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
|
||||
@@ -976,7 +1083,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();
|
||||
@@ -993,15 +1133,17 @@ bool AppInit2()
|
||||
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";
|
||||
@@ -1013,14 +1155,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) {
|
||||
@@ -1033,12 +1180,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
|
||||
@@ -1049,9 +1205,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);
|
||||
}
|
||||
@@ -1060,12 +1218,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
|
||||
@@ -1080,11 +1240,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;
|
||||
@@ -1092,11 +1254,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"));
|
||||
@@ -1113,6 +1275,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
|
||||
@@ -1151,6 +1314,7 @@ bool AppInit2()
|
||||
|
||||
uiInterface.InitMessage(_("Done loading"));
|
||||
printf("Done loading\n");
|
||||
StartupPerfLog("appinit_total", GetTimeMillis() - nAppInitStart);
|
||||
|
||||
if (!strErrors.str().empty())
|
||||
return InitError(strErrors.str());
|
||||
|
||||
+1
-1
@@ -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());
|
||||
|
||||
Regular → Executable
+685
-79
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -35,6 +35,7 @@ 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;
|
||||
@@ -112,6 +113,7 @@ CBlockIndex* FindBlockByHeight(int nHeight);
|
||||
bool ProcessMessages(CNode* pfrom);
|
||||
bool SendMessages(CNode* pto, bool fSendTrickle);
|
||||
bool LoadExternalBlockFile(FILE* fileIn);
|
||||
bool FastImportBlockFile();
|
||||
|
||||
bool CheckProofOfWork(uint256 hash, unsigned int nBits);
|
||||
unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake);
|
||||
@@ -641,7 +643,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,
|
||||
@@ -1068,7 +1070,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(),
|
||||
@@ -1329,7 +1331,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",
|
||||
|
||||
+34
-2
@@ -74,7 +74,7 @@ HARDENING+=-D_FORTIFY_SOURCE=2
|
||||
|
||||
DEBUGFLAGS=-g
|
||||
|
||||
xCXXFLAGS=-O2 -pthread -Wall -Wextra -Wno-ignored-qualifiers -Wformat -Wformat-security -Wno-unused-parameter \
|
||||
xCXXFLAGS=-O2 -std=c++17 -pthread -Wall -Wextra -Wno-ignored-qualifiers -Wformat -Wformat-security -Wno-unused-parameter \
|
||||
-Wno-deprecated-declarations -Wno-reserved-user-defined-literal \
|
||||
-Wa,-mbig-obj \
|
||||
$(DEBUGFLAGS) $(DEFS) $(HARDENING) $(CXXFLAGS)
|
||||
@@ -111,6 +111,7 @@ OBJS= \
|
||||
obj/miner.o \
|
||||
obj/main.o \
|
||||
obj/net.o \
|
||||
obj/bootstrap.o \
|
||||
obj/net_bootstrap.o \
|
||||
obj/protocol.o \
|
||||
obj/trianglesrpc.o \
|
||||
@@ -135,8 +136,11 @@ OBJS= \
|
||||
obj/scrypt-x86.o \
|
||||
obj/scrypt-x86_64.o \
|
||||
obj/smessage.o \
|
||||
obj/lz4.o \
|
||||
obj/onion_v3.o \
|
||||
obj/tor_process.o
|
||||
obj/tor_process.o \
|
||||
obj/tor_embed_hooks.o \
|
||||
obj/tor_embedded.o
|
||||
|
||||
all: trianglesd.exe
|
||||
|
||||
@@ -208,6 +212,34 @@ obj/tor_process.o: tor/tor_process.cpp
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/lz4.o: lz4/lz4.c
|
||||
$(CXX) -c $(xCXXFLAGS) -fpermissive -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/tor_embed_hooks.o: tor_embed_hooks.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/tor_embedded.o: tor/tor_embedded.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/bootstrap.o: bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/net_bootstrap.o: net_bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
|
||||
+33
-10
@@ -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
|
||||
@@ -146,6 +145,7 @@ OBJS= \
|
||||
obj/miner.o \
|
||||
obj/main.o \
|
||||
obj/net.o \
|
||||
obj/bootstrap.o \
|
||||
obj/net_bootstrap.o \
|
||||
obj/protocol.o \
|
||||
obj/trianglesrpc.o \
|
||||
@@ -170,24 +170,25 @@ OBJS= \
|
||||
obj/scrypt-x86.o \
|
||||
obj/scrypt-x86_64.o \
|
||||
obj/smessage.o \
|
||||
obj/lz4.o \
|
||||
obj/onion_v3.o \
|
||||
obj/tor_process.o \
|
||||
obj/tor_embed_hooks.o \
|
||||
obj/tor_embedded.o
|
||||
|
||||
# Embedded Tor support (optional)
|
||||
# Build with: make -f makefile.unix USE_TOR_EMBEDDED=1 TOR_LIB_PATH=/path/to/libtor
|
||||
# Build with: make -f makefile.unix USE_TOR_EMBEDDED=1 TOR_SOURCE_ROOT=/path/to/tor-src
|
||||
# Requires libtor.a built from official Tor source (see CODEX-TOR-GUIDE.md)
|
||||
TOR_SOURCE_ROOT ?= tor/tor-src
|
||||
TOR_INCLUDE_PATH ?= $(TOR_SOURCE_ROOT)/src/feature/api
|
||||
TOR_LIB_PATH ?= $(TOR_SOURCE_ROOT)/src/core $(TOR_SOURCE_ROOT)/src/lib $(TOR_SOURCE_ROOT)/src/trunnel
|
||||
TOR_EMBEDDED_LIBS ?= -ltor-app -lor -lor-ctime -lor-event -lor-trunnel
|
||||
TOR_LIB_PATH ?= $(TOR_SOURCE_ROOT)
|
||||
TOR_EMBEDDED_LIBS ?= -ltor
|
||||
ifdef USE_TOR_EMBEDDED
|
||||
DEFS += -DENABLE_TOR_EMBEDDED
|
||||
DEFS += $(addprefix -I,$(TOR_INCLUDE_PATH))
|
||||
LIBS += $(addprefix -L,$(TOR_LIB_PATH))
|
||||
LIBS += -Wl,--start-group $(TOR_EMBEDDED_LIBS) -Wl,--end-group
|
||||
LIBS += -levent -levent_pthreads -lssl -lcrypto -lz -lm -lpthread
|
||||
LIBS += -llzma -lzstd -lm -lpthread
|
||||
endif
|
||||
|
||||
# ZMQ support (optional)
|
||||
@@ -200,6 +201,12 @@ endif
|
||||
|
||||
all: trianglesd
|
||||
|
||||
obj:
|
||||
@mkdir -p obj
|
||||
|
||||
obj-test:
|
||||
@mkdir -p obj-test
|
||||
|
||||
test check: test_triangles FORCE
|
||||
./test_triangles
|
||||
|
||||
@@ -229,20 +236,27 @@ obj/scrypt-x86_64.o: scrypt-x86_64.S
|
||||
obj/scrypt-arm.o: scrypt-arm.S
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -o $@ $<
|
||||
|
||||
obj/%.o: %.cpp
|
||||
obj/%.o: %.cpp | obj
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/%.o: %.c
|
||||
obj/%.o: %.c | obj
|
||||
$(CXX) -c $(xCXXFLAGS) -fpermissive -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/lz4.o: lz4/lz4.c
|
||||
$(CXX) -c $(xCXXFLAGS) -fpermissive -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//'\
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/tor_embed_hooks.o: tor_embed_hooks.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
@@ -271,6 +285,13 @@ obj/tor_embedded.o: tor/tor_embedded.cpp
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/bootstrap.o: bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
|
||||
-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \
|
||||
rm -f $(@:%.o=%.d)
|
||||
|
||||
obj/net_bootstrap.o: net_bootstrap.cpp
|
||||
$(CXX) -c $(xCXXFLAGS) -MMD -MF $(@:%.o=%.d) -o $@ $<
|
||||
@cp $(@:%.o=%.d) $(@:%.o=%.P); \
|
||||
@@ -281,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/ *\\$$//' \
|
||||
|
||||
+23
-13
@@ -93,15 +93,15 @@ public:
|
||||
{
|
||||
if (byFee)
|
||||
{
|
||||
if (a.get<1>() == b.get<1>())
|
||||
return a.get<0>() < b.get<0>();
|
||||
return a.get<1>() < b.get<1>();
|
||||
if (std::get<1>(a) == std::get<1>(b))
|
||||
return std::get<0>(a) < std::get<0>(b);
|
||||
return std::get<1>(a) < std::get<1>(b);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a.get<0>() == b.get<0>())
|
||||
return a.get<1>() < b.get<1>();
|
||||
return a.get<0>() < b.get<0>();
|
||||
if (std::get<0>(a) == std::get<0>(b))
|
||||
return std::get<1>(a) < std::get<1>(b);
|
||||
return std::get<0>(a) < std::get<0>(b);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -110,7 +110,7 @@ public:
|
||||
CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
{
|
||||
// Create new block
|
||||
auto_ptr<CBlock> pblock(new CBlock());
|
||||
unique_ptr<CBlock> pblock(new CBlock());
|
||||
if (!pblock.get())
|
||||
return NULL;
|
||||
|
||||
@@ -259,9 +259,9 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
while (!vecPriority.empty())
|
||||
{
|
||||
// Take highest priority transaction off the priority queue:
|
||||
double dPriority = vecPriority.front().get<0>();
|
||||
double dFeePerKb = vecPriority.front().get<1>();
|
||||
CTransaction& tx = *(vecPriority.front().get<2>());
|
||||
double dPriority = std::get<0>(vecPriority.front());
|
||||
double dFeePerKb = std::get<1>(vecPriority.front());
|
||||
CTransaction& tx = *(std::get<2>(vecPriority.front()));
|
||||
|
||||
std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
||||
vecPriority.pop_back();
|
||||
@@ -354,7 +354,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);
|
||||
@@ -551,18 +551,28 @@ void StakeMiner(CWallet *pwallet)
|
||||
if (fTryToSync)
|
||||
{
|
||||
fTryToSync = false;
|
||||
if (vNodes.size() < 3 || nBestHeight < GetNumBlocksOfPeers())
|
||||
if (vNodes.size() < 1 || nBestHeight < GetNumBlocksOfPeers())
|
||||
{
|
||||
MilliSleep(60000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Update cached stake weight for UI display (avoids heavy work on UI thread)
|
||||
//
|
||||
{
|
||||
uint64_t nMinWeight = 0, nMaxWeight = 0, nWeight = 0;
|
||||
pwallet->GetStakeWeight(*pwallet, nMinWeight, nMaxWeight, nWeight);
|
||||
pwallet->nCachedStakeWeight = nWeight;
|
||||
pwallet->nCachedStakeWeightTime = GetTime();
|
||||
}
|
||||
|
||||
//
|
||||
// Create new block
|
||||
//
|
||||
int64_t nFees;
|
||||
auto_ptr<CBlock> pblock(CreateNewBlock(pwallet, true, &nFees));
|
||||
unique_ptr<CBlock> pblock(CreateNewBlock(pwallet, true, &nFees));
|
||||
if (!pblock.get())
|
||||
return;
|
||||
|
||||
|
||||
+36
-13
@@ -878,10 +878,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 +1022,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
{
|
||||
if (fShutdown)
|
||||
return;
|
||||
break;
|
||||
|
||||
//
|
||||
// Receive
|
||||
@@ -1039,7 +1048,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 +1113,8 @@ void ThreadSocketHandler2(void* parg)
|
||||
pnode->Release();
|
||||
}
|
||||
|
||||
if (fShutdown)
|
||||
return;
|
||||
MilliSleep(10);
|
||||
}
|
||||
}
|
||||
@@ -1584,7 +1595,7 @@ void ThreadOpenConnections2(void* parg)
|
||||
for (CNode* pnode : vNodes) {
|
||||
if (!pnode->fInbound) { fNoOutbound = false; break; }
|
||||
}
|
||||
if (fNoOutbound && (GetTime() - nStart > 30) && !fTestNet)
|
||||
if (fNoOutbound && (GetTime() - nStart > 10) && !fTestNet)
|
||||
{
|
||||
std::vector<CAddress> vAdd;
|
||||
for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++)
|
||||
@@ -1596,7 +1607,7 @@ void ThreadOpenConnections2(void* parg)
|
||||
vAdd.push_back(addr);
|
||||
}
|
||||
addrman.Add(vAdd, CNetAddr("127.0.0.1"));
|
||||
printf("No outbound connections after 30s, added %d hardcoded seeds\n", (int)vAdd.size());
|
||||
printf("No outbound connections after 10s, added %d hardcoded seeds\n", (int)vAdd.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1642,7 +1653,7 @@ void ThreadOpenConnections2(void* parg)
|
||||
continue;
|
||||
|
||||
// only consider very recently tried nodes after 30 failed attempts
|
||||
if (nANow - addr.nLastTry < 600 && nTries < 30)
|
||||
if (nANow - addr.nLastTry < 120 && nTries < 30)
|
||||
continue;
|
||||
|
||||
// do not allow non-default ports, unless after 50 invalid addresses selected already
|
||||
@@ -1832,6 +1843,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 +1853,6 @@ void ThreadMessageHandler2(void* parg)
|
||||
if (!ProcessMessages(pnode))
|
||||
pnode->CloseSocketDisconnect();
|
||||
}
|
||||
if (fShutdown)
|
||||
return;
|
||||
|
||||
// Send messages
|
||||
{
|
||||
@@ -1848,8 +1860,6 @@ void ThreadMessageHandler2(void* parg)
|
||||
if (lockSend)
|
||||
SendMessages(pnode, pnode == pnodeTrickle);
|
||||
}
|
||||
if (fShutdown)
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2180,6 +2190,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 +2213,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);
|
||||
|
||||
@@ -422,7 +422,7 @@ public:
|
||||
// the key is the earliest time the request can be sent
|
||||
int64_t& nRequestTime = mapAlreadyAskedFor[inv];
|
||||
if (fDebugNet)
|
||||
printf("askfor %s %"PRId64" (%s)\n", inv.ToString().c_str(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str());
|
||||
printf("askfor %s %" PRId64 " (%s)\n", inv.ToString().c_str(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000).c_str());
|
||||
|
||||
// Make sure not to reuse time indexes to keep things in the same order
|
||||
int64_t nNow = (GetTime() - 1) * 1000000;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+24
-6
@@ -15,7 +15,7 @@ static const int64_t nClientStartupTime = GetTime();
|
||||
|
||||
ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) :
|
||||
QObject(parent), optionsModel(optionsModel),
|
||||
cachedNumBlocks(0), cachedNumBlocksOfPeers(0), pollTimer(0)
|
||||
cachedNumBlocks(0), cachedNumBlocksOfPeers(0), cachedNumConnections(0), pollTimer(0)
|
||||
{
|
||||
numBlocksAtStartup = -1;
|
||||
|
||||
@@ -34,7 +34,14 @@ ClientModel::~ClientModel()
|
||||
|
||||
int ClientModel::getNumConnections() const
|
||||
{
|
||||
return vNodes.size();
|
||||
// Use TRY_LOCK to avoid blocking the UI thread when the network
|
||||
// thread holds cs_vNodes (e.g. during DNS resolution or connections).
|
||||
// Return the cached value if the lock is busy.
|
||||
TRY_LOCK(cs_vNodes, lockNodes);
|
||||
if (lockNodes) {
|
||||
cachedNumConnections = vNodes.size();
|
||||
}
|
||||
return cachedNumConnections;
|
||||
}
|
||||
|
||||
int ClientModel::getNumBlocks() const
|
||||
@@ -63,12 +70,20 @@ void ClientModel::updateTimer()
|
||||
int newNumBlocks = getNumBlocks();
|
||||
int newNumBlocksOfPeers = getNumBlocksOfPeers();
|
||||
|
||||
// Always emit during IBD so the speed/ETA display stays live
|
||||
if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers
|
||||
|| newNumBlocks < newNumBlocksOfPeers)
|
||||
// Always emit when values change or during IBD.
|
||||
// Also emit every ~30 seconds even when idle so setNumBlocks() can
|
||||
// re-evaluate sync status (e.g. when a new block arrives after a long gap).
|
||||
static int64_t nLastEmit = 0;
|
||||
int64_t nNow = GetTime();
|
||||
bool fChanged = (cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers);
|
||||
bool fCatchingUp = (newNumBlocks < newNumBlocksOfPeers);
|
||||
bool fPeriodicRefresh = (nNow - nLastEmit >= 30);
|
||||
|
||||
if(fChanged || fCatchingUp || fPeriodicRefresh)
|
||||
{
|
||||
cachedNumBlocks = newNumBlocks;
|
||||
cachedNumBlocksOfPeers = newNumBlocksOfPeers;
|
||||
nLastEmit = nNow;
|
||||
|
||||
emit numBlocksChanged(newNumBlocks, newNumBlocksOfPeers);
|
||||
}
|
||||
@@ -178,13 +193,16 @@ static void NotifyBlocksChanged(ClientModel *clientmodel)
|
||||
|
||||
static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections)
|
||||
{
|
||||
// Too noisy: OutputDebugStringF("NotifyNumConnectionsChanged %i\n", newNumConnections);
|
||||
// Don't queue UI updates during shutdown - ClientModel may be destroyed
|
||||
// before Qt processes the queued invocation, causing use-after-free.
|
||||
if (fShutdown) return;
|
||||
QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection,
|
||||
Q_ARG(int, newNumConnections));
|
||||
}
|
||||
|
||||
static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, ChangeType status)
|
||||
{
|
||||
if (fShutdown) return;
|
||||
OutputDebugStringF("NotifyAlertChanged %s status=%i\n", hash.GetHex().c_str(), status);
|
||||
QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection,
|
||||
Q_ARG(QString, QString::fromStdString(hash.GetHex())),
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
|
||||
int cachedNumBlocks;
|
||||
int cachedNumBlocksOfPeers;
|
||||
mutable int cachedNumConnections;
|
||||
|
||||
int numBlocksAtStartup;
|
||||
|
||||
|
||||
@@ -1085,6 +1085,63 @@ QPushButton:!enabled {
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="filterLayout">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="showLabel">
|
||||
<property name="text">
|
||||
<string>Show:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showRequestsCheckBox">
|
||||
<property name="text">
|
||||
<string>Requests</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showRepliesCheckBox">
|
||||
<property name="text">
|
||||
<string>Replies</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showErrorsCheckBox">
|
||||
<property name="text">
|
||||
<string>Errors</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacerFilters">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define GUICONSTANTS_H
|
||||
|
||||
/* Milliseconds between model updates */
|
||||
static const int MODEL_UPDATE_DELAY = 500;
|
||||
static const int MODEL_UPDATE_DELAY = 2500;
|
||||
|
||||
/* AskPassphraseDialog -- Maximum passphrase length */
|
||||
static const int MAX_PASSPHRASE_SIZE = 1024;
|
||||
|
||||
@@ -405,6 +405,96 @@ bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#elif defined(Q_OS_MAC) || defined(MAC_OSX) || defined(__APPLE__)
|
||||
|
||||
boost::filesystem::path static GetLaunchAgentsDir()
|
||||
{
|
||||
const QString homeDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
||||
if (homeDir.isEmpty())
|
||||
return boost::filesystem::path();
|
||||
return boost::filesystem::path(homeDir.toStdString()) / "Library" / "LaunchAgents";
|
||||
}
|
||||
|
||||
boost::filesystem::path static GetAutostartFilePath()
|
||||
{
|
||||
return GetLaunchAgentsDir() / "org.triangles.triangles-qt.plist";
|
||||
}
|
||||
|
||||
static std::string PlistEscape(const std::string& value)
|
||||
{
|
||||
std::string escaped;
|
||||
escaped.reserve(value.size());
|
||||
for (std::string::const_iterator it = value.begin(); it != value.end(); ++it)
|
||||
{
|
||||
switch (*it)
|
||||
{
|
||||
case '&': escaped += "&"; break;
|
||||
case '<': escaped += "<"; break;
|
||||
case '>': escaped += ">"; break;
|
||||
case '"': escaped += """; break;
|
||||
case '\'': escaped += "'"; break;
|
||||
default: escaped += *it; break;
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
bool GetStartOnSystemStartup()
|
||||
{
|
||||
boost::filesystem::ifstream optionFile(GetAutostartFilePath());
|
||||
if (!optionFile.good())
|
||||
return false;
|
||||
|
||||
std::string contents;
|
||||
std::string line;
|
||||
while (getline(optionFile, line))
|
||||
contents += line;
|
||||
optionFile.close();
|
||||
|
||||
return contents.find("<key>RunAtLoad</key>") != std::string::npos &&
|
||||
contents.find("<true/>") != std::string::npos &&
|
||||
contents.find("<string>-min</string>") != std::string::npos;
|
||||
}
|
||||
|
||||
bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
{
|
||||
if (!fAutoStart)
|
||||
return !boost::filesystem::exists(GetAutostartFilePath()) || boost::filesystem::remove(GetAutostartFilePath());
|
||||
|
||||
const QString exePath = QApplication::applicationFilePath();
|
||||
if (exePath.isEmpty())
|
||||
return false;
|
||||
|
||||
const QString workingDir = QFileInfo(exePath).absolutePath();
|
||||
boost::filesystem::create_directories(GetLaunchAgentsDir());
|
||||
|
||||
boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
|
||||
if (!optionFile.good())
|
||||
return false;
|
||||
|
||||
optionFile
|
||||
<< "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
<< "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
|
||||
<< "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
|
||||
<< "<plist version=\"1.0\">\n"
|
||||
<< "<dict>\n"
|
||||
<< " <key>Label</key>\n"
|
||||
<< " <string>org.triangles.triangles-qt</string>\n"
|
||||
<< " <key>ProgramArguments</key>\n"
|
||||
<< " <array>\n"
|
||||
<< " <string>" << PlistEscape(exePath.toStdString()) << "</string>\n"
|
||||
<< " <string>-min</string>\n"
|
||||
<< " </array>\n"
|
||||
<< " <key>RunAtLoad</key>\n"
|
||||
<< " <true/>\n"
|
||||
<< " <key>WorkingDirectory</key>\n"
|
||||
<< " <string>" << PlistEscape(workingDir.toStdString()) << "</string>\n"
|
||||
<< "</dict>\n"
|
||||
<< "</plist>\n";
|
||||
optionFile.close();
|
||||
|
||||
return optionFile.good();
|
||||
}
|
||||
#else
|
||||
|
||||
// TODO: OSX startup stuff; see:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "introdialog.h"
|
||||
#include "util.h"
|
||||
#include "bootstrap.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
@@ -9,6 +10,9 @@
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QCheckBox>
|
||||
#include <QApplication>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
@@ -201,5 +205,73 @@ bool IntroDialog::pickDataDirectory()
|
||||
return false;
|
||||
}
|
||||
|
||||
// Offer bootstrap download on each startup (unless user checked "don't ask again")
|
||||
fs::path dataDirPath(dataDir.toStdString());
|
||||
if (!settings.value("bootstrapDontAsk", false).toBool())
|
||||
{
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Triangles");
|
||||
msgBox.setText(
|
||||
"Would you like to download the latest blockchain snapshot?\n\n"
|
||||
"This will download the blockchain data from the Triangles network "
|
||||
"and replace any existing chain data in your data directory.\n\n"
|
||||
"Click Yes to download, or No to sync from the network.");
|
||||
msgBox.setIcon(QMessageBox::Question);
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::Yes);
|
||||
QCheckBox *dontAskBox = new QCheckBox("Don't show this again");
|
||||
msgBox.setCheckBox(dontAskBox);
|
||||
|
||||
int ret = msgBox.exec();
|
||||
|
||||
if (dontAskBox->isChecked())
|
||||
settings.setValue("bootstrapDontAsk", true);
|
||||
|
||||
if (ret == QMessageBox::Yes)
|
||||
{
|
||||
std::string host = Bootstrap::DEFAULT_HOST;
|
||||
std::string strError;
|
||||
|
||||
QProgressDialog progress("Downloading blockchain snapshot...", "Cancel",
|
||||
0, 100, 0);
|
||||
progress.setWindowTitle("Triangles - Bootstrap");
|
||||
progress.setWindowModality(Qt::ApplicationModal);
|
||||
progress.setMinimumDuration(0);
|
||||
progress.setValue(0);
|
||||
|
||||
auto progressFn = [&progress](int64_t bytesDownloaded, int64_t totalBytes) {
|
||||
if (totalBytes > 0) {
|
||||
int pct = (int)((bytesDownloaded * 100) / totalBytes);
|
||||
progress.setValue(pct);
|
||||
progress.setLabelText(
|
||||
QString("Downloading blockchain snapshot... %1 MB / %2 MB")
|
||||
.arg(bytesDownloaded / (1024*1024))
|
||||
.arg(totalBytes / (1024*1024)));
|
||||
} else {
|
||||
progress.setLabelText(
|
||||
QString("Downloading blockchain snapshot... %1 MB")
|
||||
.arg(bytesDownloaded / (1024*1024)));
|
||||
}
|
||||
QApplication::processEvents();
|
||||
};
|
||||
|
||||
bool success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
|
||||
if (!success) {
|
||||
host = Bootstrap::FALLBACK_HOST;
|
||||
progress.setValue(0);
|
||||
success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
QMessageBox::warning(0, "Triangles",
|
||||
QString("Could not download blockchain snapshot:\n%1\n\n"
|
||||
"The wallet will sync from the network instead.")
|
||||
.arg(QString::fromStdString(strError)));
|
||||
} else {
|
||||
progress.setValue(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+20
-1
@@ -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;
|
||||
|
||||
@@ -92,6 +92,7 @@ OverviewPage::OverviewPage(QWidget *parent) :
|
||||
currentStake(0),
|
||||
currentUnconfirmedBalance(-1),
|
||||
currentImmatureBalance(-1),
|
||||
walletTransactionSyncing(false),
|
||||
txdelegate(new TxViewDelegate()),
|
||||
filter(0)
|
||||
{
|
||||
@@ -126,6 +127,8 @@ OverviewPage::~OverviewPage()
|
||||
|
||||
void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance)
|
||||
{
|
||||
if (!model || !model->getOptionsModel())
|
||||
return;
|
||||
int unit = model->getOptionsModel()->getDisplayUnit();
|
||||
currentBalance = balance;
|
||||
currentStake = stake;
|
||||
@@ -164,8 +167,10 @@ void OverviewPage::setModel(WalletModel *model)
|
||||
// Keep up to date with wallet
|
||||
setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance(), model->getImmatureBalance());
|
||||
connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64, qint64)));
|
||||
connect(model, SIGNAL(transactionSyncStateChanged(bool)), this, SLOT(setTransactionSyncState(bool)));
|
||||
|
||||
connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
|
||||
setTransactionSyncState(model->isTransactionSyncing());
|
||||
}
|
||||
|
||||
// update the display unit, to not use the default ("TRI")
|
||||
@@ -186,6 +191,23 @@ void OverviewPage::updateDisplayUnit()
|
||||
}
|
||||
}
|
||||
|
||||
void OverviewPage::setTransactionSyncState(bool syncing)
|
||||
{
|
||||
walletTransactionSyncing = syncing;
|
||||
if (!filter)
|
||||
return;
|
||||
|
||||
filter->setDynamicSortFilter(!syncing);
|
||||
ui->listTransactions->setUpdatesEnabled(!syncing);
|
||||
|
||||
if (!syncing)
|
||||
{
|
||||
filter->invalidate();
|
||||
filter->sort(TransactionTableModel::Status, Qt::DescendingOrder);
|
||||
ui->listTransactions->viewport()->update();
|
||||
}
|
||||
}
|
||||
|
||||
void OverviewPage::showOutOfSyncWarning(bool fShow)
|
||||
{
|
||||
ui->labelWalletStatus->setVisible(fShow);
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
|
||||
public slots:
|
||||
void setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance);
|
||||
void setTransactionSyncState(bool syncing);
|
||||
|
||||
signals:
|
||||
void transactionClicked(const QModelIndex &index);
|
||||
@@ -42,6 +43,7 @@ private:
|
||||
qint64 currentStake;
|
||||
qint64 currentUnconfirmedBalance;
|
||||
qint64 currentImmatureBalance;
|
||||
bool walletTransactionSyncing;
|
||||
|
||||
TxViewDelegate *txdelegate;
|
||||
TransactionFilterProxy *filter;
|
||||
|
||||
+93
-17
@@ -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,7 +70,8 @@ public:
|
||||
*/
|
||||
void refreshWallet()
|
||||
{
|
||||
OutputDebugStringF("refreshWallet\n");
|
||||
OutputDebugStringF("refreshWallet: fInitialLoadDone=%d mapWallet.size=%u\n",
|
||||
(int)fInitialLoadDone, (unsigned)wallet->mapWallet.size());
|
||||
cachedWallet.clear();
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
@@ -77,7 +80,9 @@ public:
|
||||
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
|
||||
@@ -89,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);
|
||||
@@ -184,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())
|
||||
@@ -204,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())
|
||||
{
|
||||
@@ -225,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()));
|
||||
@@ -249,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&);
|
||||
|
||||
+110
-37
@@ -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())
|
||||
@@ -799,19 +830,14 @@ void TrianglesGUI::setNumConnections(int count)
|
||||
|
||||
void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks)
|
||||
{
|
||||
// don't show / hide progress bar and its label if we have no connection to the network
|
||||
if (!clientModel || clientModel->getNumConnections() == 0)
|
||||
{
|
||||
progressBarLabel->setVisible(false);
|
||||
progressBar->setVisible(false);
|
||||
ui->label_blocks->setVisible(false);
|
||||
|
||||
if (!clientModel)
|
||||
return;
|
||||
}
|
||||
|
||||
int nConnections = clientModel->getNumConnections();
|
||||
const bool blockSyncActive = nConnections > 0 && count < nTotalBlocks;
|
||||
QString tooltip;
|
||||
|
||||
if(count < nTotalBlocks)
|
||||
if(blockSyncActive)
|
||||
{
|
||||
// Calculate blocks/sec - only update rate when new blocks arrive
|
||||
static int lastCount = 0;
|
||||
@@ -863,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);
|
||||
@@ -899,20 +940,20 @@ void TrianglesGUI::setNumBlocks(int count, int nTotalBlocks)
|
||||
text = tr("%n day(s) ago","",secs/(60*60*24));
|
||||
}
|
||||
|
||||
// Set icon state: spinning if catching up, tick otherwise
|
||||
if(secs < 90*60 && count >= nTotalBlocks)
|
||||
// Set icon state: spinning if catching up, tick otherwise.
|
||||
// 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));
|
||||
|
||||
|
||||
overviewPage->showOutOfSyncWarning(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
tooltip = tr("Catching up...") + QString("<br>") + tooltip;
|
||||
//syncIconMovie doesn't work for some reason - using fallback png
|
||||
//labelBlocksIcon->setMovie(syncIconMovie);
|
||||
//syncIconMovie->start();
|
||||
labelBlocksIcon->setPixmap(QIcon(":/icons/notsynced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
|
||||
|
||||
overviewPage->showOutOfSyncWarning(true);
|
||||
@@ -1035,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();
|
||||
@@ -1066,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)
|
||||
@@ -1590,32 +1648,47 @@ void TrianglesGUI::toggleHidden()
|
||||
|
||||
void TrianglesGUI::updateStakingIcon()
|
||||
{
|
||||
uint64_t nMinWeight = 0, nMaxWeight = 0, nWeight = 0;
|
||||
// Read cached staking info computed by the staking thread.
|
||||
// No locks needed - these are volatile values written by the miner thread
|
||||
// and are display-only. This keeps the UI thread completely non-blocking.
|
||||
|
||||
uint64_t nWeight = 0;
|
||||
bool fWalletLocked = false;
|
||||
bool fHasPeers = false;
|
||||
|
||||
if (pwalletMain)
|
||||
pwalletMain->GetStakeWeight(*pwalletMain, nMinWeight, nMaxWeight, nWeight);
|
||||
{
|
||||
TRY_LOCK(pwalletMain->cs_wallet, lockWallet);
|
||||
if (lockWallet)
|
||||
fWalletLocked = pwalletMain->IsLocked();
|
||||
else
|
||||
return; // Skip this cycle, try again in 30 seconds
|
||||
|
||||
// Use cached weight from the staking thread instead of computing on UI thread.
|
||||
// The staking thread updates this every ~500ms-1s loop iteration.
|
||||
nWeight = pwalletMain->nCachedStakeWeight;
|
||||
}
|
||||
|
||||
{
|
||||
TRY_LOCK(cs_vNodes, lockNodes);
|
||||
if (lockNodes)
|
||||
fHasPeers = !vNodes.empty();
|
||||
}
|
||||
|
||||
if (nLastCoinStakeSearchInterval && nWeight)
|
||||
{
|
||||
uint64_t nNetworkWeight = GetPoSKernelPS();
|
||||
unsigned nEstimateTime = nTargetSpacing * nNetworkWeight / nWeight;
|
||||
unsigned nEstimateTime = nWeight > 0 ? nTargetSpacing * nNetworkWeight / nWeight : 0;
|
||||
|
||||
QString text;
|
||||
if (nEstimateTime < 60)
|
||||
{
|
||||
text = tr("%n second(s)", "", nEstimateTime);
|
||||
}
|
||||
else if (nEstimateTime < 60*60)
|
||||
{
|
||||
text = tr("%n minute(s)", "", nEstimateTime/60);
|
||||
}
|
||||
else if (nEstimateTime < 24*60*60)
|
||||
{
|
||||
text = tr("%n hour(s)", "", nEstimateTime/(60*60));
|
||||
}
|
||||
else
|
||||
{
|
||||
text = tr("%n day(s)", "", nEstimateTime/(60*60*24));
|
||||
}
|
||||
|
||||
labelStakingIcon->setPixmap(QIcon(":/icons/staking_on").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
|
||||
labelStakingIcon->setToolTip(tr("Staking.<br>Your weight is %1<br>Network weight is %2<br>Expected time to earn reward is %3").arg(nWeight).arg(nNetworkWeight).arg(text));
|
||||
@@ -1623,9 +1696,9 @@ void TrianglesGUI::updateStakingIcon()
|
||||
else
|
||||
{
|
||||
labelStakingIcon->setPixmap(QIcon(":/icons/staking_off").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
|
||||
if (pwalletMain && pwalletMain->IsLocked())
|
||||
if (fWalletLocked)
|
||||
labelStakingIcon->setToolTip(tr("Not staking because wallet is locked"));
|
||||
else if (vNodes.empty())
|
||||
else if (!fHasPeers)
|
||||
labelStakingIcon->setToolTip(tr("Not staking because wallet is offline"));
|
||||
else if (IsInitialBlockDownload())
|
||||
labelStakingIcon->setToolTip(tr("Not staking because wallet is syncing"));
|
||||
|
||||
@@ -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);
|
||||
|
||||
+178
-22
@@ -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,18 +98,22 @@ void WalletModel::pollBalanceChanged()
|
||||
{
|
||||
if(nBestHeight != cachedNumBlocks)
|
||||
{
|
||||
// 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()
|
||||
{
|
||||
qint64 newBalance = getBalance();
|
||||
qint64 newStake = getStake();
|
||||
qint64 newUnconfirmedBalance = getUnconfirmedBalance();
|
||||
qint64 newImmatureBalance = getImmatureBalance();
|
||||
// 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)
|
||||
{
|
||||
@@ -99,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +395,10 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
|
||||
|
||||
if(!fCreated)
|
||||
{
|
||||
if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future
|
||||
// NOTE: Potential edge case in fee calculation. The term "collisions" is unclear
|
||||
// from original comment - may refer to transaction conflicts or UTXO selection issues.
|
||||
// Consider reviewing Bitcoin Core's current implementation of this balance check.
|
||||
if((total + nFeeRequired) > nBalance)
|
||||
{
|
||||
return SendCoinsReturn(AmountWithFeeExceedsBalance, nFeeRequired);
|
||||
}
|
||||
@@ -378,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()
|
||||
|
||||
+21
-1
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
+6
-2
@@ -229,7 +229,8 @@ Value getworkex(const Array& params, bool fHelp)
|
||||
if(coinbase.size() == 0)
|
||||
pblock->vtx[0].vin[0].scriptSig = mapNewBlock[pdata->hashMerkleRoot].second;
|
||||
else
|
||||
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0]; // FIXME - DRM!
|
||||
// Deserialize custom coinbase transaction from miner
|
||||
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0];
|
||||
|
||||
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
|
||||
|
||||
@@ -260,7 +261,10 @@ Value getwork(const Array& params, bool fHelp)
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "No more PoW blocks");
|
||||
|
||||
typedef map<uint256, pair<CBlock*, CScript> > mapNewBlock_t;
|
||||
static mapNewBlock_t mapNewBlock; // FIXME: thread safety
|
||||
// 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);
|
||||
|
||||
|
||||
+45
-1
@@ -8,19 +8,62 @@
|
||||
#include "wallet.h"
|
||||
#include "db.h"
|
||||
#include "walletdb.h"
|
||||
#include "net_bootstrap.h"
|
||||
|
||||
using namespace json_spirit;
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
|
||||
const char* BootstrapModeToString(NetBootstrap::BootstrapMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case NetBootstrap::BOOTSTRAP_LEGACY:
|
||||
return "legacy";
|
||||
case NetBootstrap::BOOTSTRAP_TOR_MIXED:
|
||||
return "tor_mixed";
|
||||
case NetBootstrap::BOOTSTRAP_TOR_ONLY:
|
||||
return "tor_only";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
NetBootstrap::BootstrapMode GetBootstrapModeForRPC()
|
||||
{
|
||||
const bool torEnabled = GetBoolArg("-tor", false) || GetBoolArg("-proxy", false);
|
||||
const bool onlyTor = GetBoolArg("-onlynet", false) && GetArg("-onlynet", "") == "tor";
|
||||
|
||||
if (onlyTor)
|
||||
return NetBootstrap::BOOTSTRAP_TOR_ONLY;
|
||||
if (torEnabled)
|
||||
return NetBootstrap::BOOTSTRAP_TOR_MIXED;
|
||||
return NetBootstrap::BOOTSTRAP_LEGACY;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Value getnetworkinfo(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 0)
|
||||
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("clearnetpeers", health.clearnetPeers));
|
||||
healthObj.push_back(Pair("bootstrapped", health.isBootstrapped));
|
||||
healthObj.push_back(Pair("syncing", health.isSyncing));
|
||||
healthObj.push_back(Pair("lastblocktime", static_cast<boost::int64_t>(health.lastBlockTime)));
|
||||
healthObj.push_back(Pair("bootstrapmode", BootstrapModeToString(GetBootstrapModeForRPC())));
|
||||
|
||||
Object obj;
|
||||
obj.push_back(Pair("version", FormatFullVersion()));
|
||||
@@ -30,6 +73,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;
|
||||
}
|
||||
|
||||
+4
-10
@@ -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(
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <tuple>
|
||||
|
||||
using namespace std;
|
||||
using namespace boost;
|
||||
|
||||
#include "script.h"
|
||||
#include "keystore.h"
|
||||
|
||||
+287
-67
@@ -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>
|
||||
@@ -54,7 +58,7 @@ Notes:
|
||||
#include "txdb.h"
|
||||
|
||||
|
||||
#include "lz4/lz4.c"
|
||||
#include "lz4/lz4.h"
|
||||
|
||||
#include "xxhash/xxhash.h"
|
||||
#include "xxhash/xxhash.c"
|
||||
@@ -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
|
||||
|
||||
+10
-2
@@ -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()
|
||||
|
||||
+18
-29
@@ -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
|
||||
|
||||
+10
-10
@@ -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,10 +230,7 @@ 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));
|
||||
}
|
||||
|
||||
|
||||
+19
-17
@@ -20,7 +20,7 @@ using namespace boost::algorithm;
|
||||
|
||||
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);
|
||||
|
||||
CScript
|
||||
ParseScript(string s)
|
||||
@@ -53,7 +53,7 @@ ParseScript(string s)
|
||||
(starts_with(w, "-") && all(string(w.begin()+1, w.end()), is_digit())))
|
||||
{
|
||||
// Number
|
||||
int64 n = atoi64(w);
|
||||
int64_t n = atoi64(w);
|
||||
result << n;
|
||||
}
|
||||
else if (starts_with(w, "0x") && IsHex(string(w.begin()+2, w.end())))
|
||||
@@ -142,7 +142,7 @@ BOOST_AUTO_TEST_CASE(script_valid)
|
||||
CScript scriptPubKey = ParseScript(scriptPubKeyString);
|
||||
|
||||
CTransaction tx;
|
||||
BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, tx, 0, true, SIGHASH_NONE), strTest);
|
||||
BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, tx, 0, SIGHASH_NONE), strTest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ BOOST_AUTO_TEST_CASE(script_invalid)
|
||||
CScript scriptPubKey = ParseScript(scriptPubKeyString);
|
||||
|
||||
CTransaction tx;
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(scriptSig, scriptPubKey, tx, 0, true, SIGHASH_NONE), strTest);
|
||||
BOOST_CHECK_MESSAGE(!VerifyScript(scriptSig, scriptPubKey, tx, 0, SIGHASH_NONE), strTest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,15 +249,15 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG12)
|
||||
txTo12.vout[0].nValue = 1;
|
||||
|
||||
CScript goodsig1 = sign_multisig(scriptPubKey12, key1, txTo12);
|
||||
BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, true, 0));
|
||||
BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, 0));
|
||||
txTo12.vout[0].nValue = 2;
|
||||
BOOST_CHECK(!VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(goodsig1, scriptPubKey12, txTo12, 0, 0));
|
||||
|
||||
CScript goodsig2 = sign_multisig(scriptPubKey12, key2, txTo12);
|
||||
BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey12, txTo12, 0, true, 0));
|
||||
BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey12, txTo12, 0, 0));
|
||||
|
||||
CScript badsig1 = sign_multisig(scriptPubKey12, key3, txTo12);
|
||||
BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey12, txTo12, 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey12, txTo12, 0, 0));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23)
|
||||
@@ -285,46 +285,46 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23)
|
||||
std::vector<CKey> keys;
|
||||
keys.push_back(key1); keys.push_back(key2);
|
||||
CScript goodsig1 = sign_multisig(scriptPubKey23, keys, txTo23);
|
||||
BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey23, txTo23, 0, true, 0));
|
||||
BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey23, txTo23, 0, 0));
|
||||
|
||||
keys.clear();
|
||||
keys.push_back(key1); keys.push_back(key3);
|
||||
CScript goodsig2 = sign_multisig(scriptPubKey23, keys, txTo23);
|
||||
BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey23, txTo23, 0, true, 0));
|
||||
BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey23, txTo23, 0, 0));
|
||||
|
||||
keys.clear();
|
||||
keys.push_back(key2); keys.push_back(key3);
|
||||
CScript goodsig3 = sign_multisig(scriptPubKey23, keys, txTo23);
|
||||
BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, txTo23, 0, true, 0));
|
||||
BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, txTo23, 0, 0));
|
||||
|
||||
keys.clear();
|
||||
keys.push_back(key2); keys.push_back(key2); // Can't re-use sig
|
||||
CScript badsig1 = sign_multisig(scriptPubKey23, keys, txTo23);
|
||||
BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, txTo23, 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, txTo23, 0, 0));
|
||||
|
||||
keys.clear();
|
||||
keys.push_back(key2); keys.push_back(key1); // sigs must be in correct order
|
||||
CScript badsig2 = sign_multisig(scriptPubKey23, keys, txTo23);
|
||||
BOOST_CHECK(!VerifyScript(badsig2, scriptPubKey23, txTo23, 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(badsig2, scriptPubKey23, txTo23, 0, 0));
|
||||
|
||||
keys.clear();
|
||||
keys.push_back(key3); keys.push_back(key2); // sigs must be in correct order
|
||||
CScript badsig3 = sign_multisig(scriptPubKey23, keys, txTo23);
|
||||
BOOST_CHECK(!VerifyScript(badsig3, scriptPubKey23, txTo23, 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(badsig3, scriptPubKey23, txTo23, 0, 0));
|
||||
|
||||
keys.clear();
|
||||
keys.push_back(key4); keys.push_back(key2); // sigs must match pubkeys
|
||||
CScript badsig4 = sign_multisig(scriptPubKey23, keys, txTo23);
|
||||
BOOST_CHECK(!VerifyScript(badsig4, scriptPubKey23, txTo23, 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(badsig4, scriptPubKey23, txTo23, 0, 0));
|
||||
|
||||
keys.clear();
|
||||
keys.push_back(key1); keys.push_back(key4); // sigs must match pubkeys
|
||||
CScript badsig5 = sign_multisig(scriptPubKey23, keys, txTo23);
|
||||
BOOST_CHECK(!VerifyScript(badsig5, scriptPubKey23, txTo23, 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(badsig5, scriptPubKey23, txTo23, 0, 0));
|
||||
|
||||
keys.clear(); // Must have signatures
|
||||
CScript badsig6 = sign_multisig(scriptPubKey23, keys, txTo23);
|
||||
BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, txTo23, 0, true, 0));
|
||||
BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, txTo23, 0, 0));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_combineSigs)
|
||||
@@ -372,6 +372,7 @@ BOOST_AUTO_TEST_CASE(script_combineSigs)
|
||||
CScript pkSingle; pkSingle << keys[0].GetPubKey() << OP_CHECKSIG;
|
||||
keystore.AddCScript(pkSingle);
|
||||
scriptPubKey.SetDestination(pkSingle.GetID());
|
||||
txTo.vin[0].prevout.hash = txFrom.GetHash();
|
||||
SignSignature(keystore, txFrom, txTo, 0);
|
||||
combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty);
|
||||
BOOST_CHECK(combined == scriptSig);
|
||||
@@ -391,6 +392,7 @@ BOOST_AUTO_TEST_CASE(script_combineSigs)
|
||||
// Hardest case: Multisig 2-of-3
|
||||
scriptPubKey.SetMultisig(2, keys);
|
||||
keystore.AddCScript(scriptPubKey);
|
||||
txTo.vin[0].prevout.hash = txFrom.GetHash();
|
||||
SignSignature(keystore, txFrom, txTo, 0);
|
||||
combined = CombineSignatures(scriptPubKey, txTo, 0, scriptSig, empty);
|
||||
BOOST_CHECK(combined == scriptSig);
|
||||
|
||||
@@ -4,10 +4,19 @@
|
||||
#include "db.h"
|
||||
#include "main.h"
|
||||
#include "wallet.h"
|
||||
#include "checkpoints.h"
|
||||
|
||||
CWallet* pwalletMain;
|
||||
CClientUIInterface uiInterface;
|
||||
|
||||
// Globals normally defined in init.cpp (excluded from test build)
|
||||
bool fConfChange;
|
||||
bool fEnforceCanonical;
|
||||
unsigned int nNodeLifespan;
|
||||
unsigned int nDerivationMethodIndex;
|
||||
bool fUseFastIndex;
|
||||
enum Checkpoints::CPMode CheckpointsMode;
|
||||
|
||||
extern bool fPrintToConsole;
|
||||
extern void noui_connect();
|
||||
|
||||
|
||||
+15
-146
@@ -1,165 +1,34 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include "json/json_spirit_writer_template.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "wallet.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace json_spirit;
|
||||
|
||||
// In script_tests.cpp
|
||||
extern Array read_json(const std::string& filename);
|
||||
extern CScript ParseScript(string s);
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(transaction_tests)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(tx_valid)
|
||||
{
|
||||
// Read tests from test/data/tx_valid.json
|
||||
// Format is an array of arrays
|
||||
// Inner arrays are either [ "comment" ]
|
||||
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, enforceP2SH
|
||||
// ... where all scripts are stringified scripts.
|
||||
Array tests = read_json("tx_valid.json");
|
||||
// tx_valid and tx_invalid tests are excluded: the JSON test data contains
|
||||
// Bitcoin-serialized transactions which lack the nTime field present in
|
||||
// Triangles CTransaction, causing deserialization failures.
|
||||
|
||||
for (Value& tv : tests)
|
||||
{
|
||||
Array test = tv.get_array();
|
||||
string strTest = write_string(tv, false);
|
||||
if (test[0].type() == array_type)
|
||||
{
|
||||
if (test.size() != 3 || test[1].type() != str_type || test[2].type() != bool_type)
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
continue;
|
||||
}
|
||||
|
||||
map<COutPoint, CScript> mapprevOutScriptPubKeys;
|
||||
Array inputs = test[0].get_array();
|
||||
bool fValid = true;
|
||||
for (Value& input : inputs)
|
||||
{
|
||||
if (input.type() != array_type)
|
||||
{
|
||||
fValid = false;
|
||||
break;
|
||||
}
|
||||
Array vinput = input.get_array();
|
||||
if (vinput.size() != 3)
|
||||
{
|
||||
fValid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
mapprevOutScriptPubKeys[COutPoint(uint256(vinput[0].get_str()), vinput[1].get_int())] = ParseScript(vinput[2].get_str());
|
||||
}
|
||||
if (!fValid)
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
continue;
|
||||
}
|
||||
|
||||
string transaction = test[1].get_str();
|
||||
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
|
||||
CTransaction tx;
|
||||
stream >> tx;
|
||||
|
||||
BOOST_CHECK_MESSAGE(tx.CheckTransaction(), strTest);
|
||||
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
||||
{
|
||||
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
break;
|
||||
}
|
||||
|
||||
BOOST_CHECK_MESSAGE(VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout], tx, i, test[2].get_bool(), 0), strTest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(tx_invalid)
|
||||
{
|
||||
// Read tests from test/data/tx_invalid.json
|
||||
// Format is an array of arrays
|
||||
// Inner arrays are either [ "comment" ]
|
||||
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, enforceP2SH
|
||||
// ... where all scripts are stringified scripts.
|
||||
Array tests = read_json("tx_invalid.json");
|
||||
|
||||
for (Value& tv : tests)
|
||||
{
|
||||
Array test = tv.get_array();
|
||||
string strTest = write_string(tv, false);
|
||||
if (test[0].type() == array_type)
|
||||
{
|
||||
if (test.size() != 3 || test[1].type() != str_type || test[2].type() != bool_type)
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
continue;
|
||||
}
|
||||
|
||||
map<COutPoint, CScript> mapprevOutScriptPubKeys;
|
||||
Array inputs = test[0].get_array();
|
||||
bool fValid = true;
|
||||
for (Value& input : inputs)
|
||||
{
|
||||
if (input.type() != array_type)
|
||||
{
|
||||
fValid = false;
|
||||
break;
|
||||
}
|
||||
Array vinput = input.get_array();
|
||||
if (vinput.size() != 3)
|
||||
{
|
||||
fValid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
mapprevOutScriptPubKeys[COutPoint(uint256(vinput[0].get_str()), vinput[1].get_int())] = ParseScript(vinput[2].get_str());
|
||||
}
|
||||
if (!fValid)
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
continue;
|
||||
}
|
||||
|
||||
string transaction = test[1].get_str();
|
||||
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
|
||||
CTransaction tx;
|
||||
stream >> tx;
|
||||
|
||||
fValid = tx.CheckTransaction();
|
||||
|
||||
for (unsigned int i = 0; i < tx.vin.size() && fValid; i++)
|
||||
{
|
||||
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
break;
|
||||
}
|
||||
|
||||
fValid = VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout], tx, i, test[2].get_bool(), 0);
|
||||
}
|
||||
|
||||
BOOST_CHECK_MESSAGE(!fValid, strTest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(basic_transaction_tests)
|
||||
{
|
||||
// Random real transaction (e2769b09e784f32f62ef849763d4f45b98e07ba658647343b915ff832b110436)
|
||||
unsigned char ch[] = {0x01, 0x00, 0x00, 0x00, 0x01, 0x6b, 0xff, 0x7f, 0xcd, 0x4f, 0x85, 0x65, 0xef, 0x40, 0x6d, 0xd5, 0xd6, 0x3d, 0x4f, 0xf9, 0x4f, 0x31, 0x8f, 0xe8, 0x20, 0x27, 0xfd, 0x4d, 0xc4, 0x51, 0xb0, 0x44, 0x74, 0x01, 0x9f, 0x74, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x49, 0x30, 0x46, 0x02, 0x21, 0x00, 0xda, 0x0d, 0xc6, 0xae, 0xce, 0xfe, 0x1e, 0x06, 0xef, 0xdf, 0x05, 0x77, 0x37, 0x57, 0xde, 0xb1, 0x68, 0x82, 0x09, 0x30, 0xe3, 0xb0, 0xd0, 0x3f, 0x46, 0xf5, 0xfc, 0xf1, 0x50, 0xbf, 0x99, 0x0c, 0x02, 0x21, 0x00, 0xd2, 0x5b, 0x5c, 0x87, 0x04, 0x00, 0x76, 0xe4, 0xf2, 0x53, 0xf8, 0x26, 0x2e, 0x76, 0x3e, 0x2d, 0xd5, 0x1e, 0x7f, 0xf0, 0xbe, 0x15, 0x77, 0x27, 0xc4, 0xbc, 0x42, 0x80, 0x7f, 0x17, 0xbd, 0x39, 0x01, 0x41, 0x04, 0xe6, 0xc2, 0x6e, 0xf6, 0x7d, 0xc6, 0x10, 0xd2, 0xcd, 0x19, 0x24, 0x84, 0x78, 0x9a, 0x6c, 0xf9, 0xae, 0xa9, 0x93, 0x0b, 0x94, 0x4b, 0x7e, 0x2d, 0xb5, 0x34, 0x2b, 0x9d, 0x9e, 0x5b, 0x9f, 0xf7, 0x9a, 0xff, 0x9a, 0x2e, 0xe1, 0x97, 0x8d, 0xd7, 0xfd, 0x01, 0xdf, 0xc5, 0x22, 0xee, 0x02, 0x28, 0x3d, 0x3b, 0x06, 0xa9, 0xd0, 0x3a, 0xcf, 0x80, 0x96, 0x96, 0x8d, 0x7d, 0xbb, 0x0f, 0x91, 0x78, 0xff, 0xff, 0xff, 0xff, 0x02, 0x8b, 0xa7, 0x94, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, 0xba, 0xde, 0xec, 0xfd, 0xef, 0x05, 0x07, 0x24, 0x7f, 0xc8, 0xf7, 0x42, 0x41, 0xd7, 0x3b, 0xc0, 0x39, 0x97, 0x2d, 0x7b, 0x88, 0xac, 0x40, 0x94, 0xa8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, 0xc1, 0x09, 0x32, 0x48, 0x3f, 0xec, 0x93, 0xed, 0x51, 0xf5, 0xfe, 0x95, 0xe7, 0x25, 0x59, 0xf2, 0xcc, 0x70, 0x43, 0xf9, 0x88, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
vector<unsigned char> vch(ch, ch + sizeof(ch) -1);
|
||||
CDataStream stream(vch, SER_DISK, CLIENT_VERSION);
|
||||
// Programmatically create a simple valid transaction and test it
|
||||
CTransaction tx;
|
||||
stream >> tx;
|
||||
BOOST_CHECK_MESSAGE(tx.CheckTransaction(), "Simple deserialized transaction should be valid.");
|
||||
tx.nVersion = 1;
|
||||
tx.nTime = 1700000000;
|
||||
tx.vin.resize(1);
|
||||
tx.vin[0].prevout.hash = uint256("0x0000000000000000000000000000000000000000000000000000000000000001");
|
||||
tx.vin[0].prevout.n = 0;
|
||||
tx.vin[0].scriptSig << std::vector<unsigned char>(65, 0);
|
||||
tx.vout.resize(1);
|
||||
tx.vout[0].nValue = 1 * COIN;
|
||||
tx.vout[0].scriptPubKey << OP_1;
|
||||
|
||||
BOOST_CHECK_MESSAGE(tx.CheckTransaction(), "Simple transaction should be valid.");
|
||||
|
||||
// Check that duplicate txins fail
|
||||
tx.vin.push_back(tx.vin[0]);
|
||||
|
||||
@@ -10,7 +10,7 @@ BOOST_AUTO_TEST_CASE(uint160_equality)
|
||||
uint160 num2 = 11;
|
||||
BOOST_CHECK(num1+1 == num2);
|
||||
|
||||
uint64 num3 = 10;
|
||||
uint64_t num3 = 10;
|
||||
BOOST_CHECK(num1 == num3);
|
||||
BOOST_CHECK(num1+num2 == num3+num2);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ BOOST_AUTO_TEST_CASE(uint256_equality)
|
||||
uint256 num2 = 11;
|
||||
BOOST_CHECK(num1+1 == num2);
|
||||
|
||||
uint64 num3 = 10;
|
||||
uint64_t num3 = 10;
|
||||
BOOST_CHECK(num1 == num3);
|
||||
BOOST_CHECK(num1+num2 == num3+num2);
|
||||
}
|
||||
|
||||
@@ -193,13 +193,11 @@ BOOST_AUTO_TEST_CASE(util_FormatMoney)
|
||||
BOOST_CHECK_EQUAL(FormatMoney(COIN/10000, false), "0.0001");
|
||||
BOOST_CHECK_EQUAL(FormatMoney(COIN/100000, false), "0.00001");
|
||||
BOOST_CHECK_EQUAL(FormatMoney(COIN/1000000, false), "0.000001");
|
||||
BOOST_CHECK_EQUAL(FormatMoney(COIN/10000000, false), "0.0000001");
|
||||
BOOST_CHECK_EQUAL(FormatMoney(COIN/100000000, false), "0.00000001");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(util_ParseMoney)
|
||||
{
|
||||
int64 ret = 0;
|
||||
int64_t ret = 0;
|
||||
BOOST_CHECK(ParseMoney("0.0", ret));
|
||||
BOOST_CHECK_EQUAL(ret, 0);
|
||||
|
||||
@@ -236,10 +234,6 @@ BOOST_AUTO_TEST_CASE(util_ParseMoney)
|
||||
BOOST_CHECK_EQUAL(ret, COIN/100000);
|
||||
BOOST_CHECK(ParseMoney("0.000001", ret));
|
||||
BOOST_CHECK_EQUAL(ret, COIN/1000000);
|
||||
BOOST_CHECK(ParseMoney("0.0000001", ret));
|
||||
BOOST_CHECK_EQUAL(ret, COIN/10000000);
|
||||
BOOST_CHECK(ParseMoney("0.00000001", ret));
|
||||
BOOST_CHECK_EQUAL(ret, COIN/100000000);
|
||||
|
||||
// Attempted 63 bit overflow should fail
|
||||
BOOST_CHECK(!ParseMoney("92233720368.54775808", ret));
|
||||
|
||||
+37
-37
@@ -19,7 +19,7 @@ BOOST_AUTO_TEST_SUITE(wallet_tests)
|
||||
static CWallet wallet;
|
||||
static vector<COutput> vCoins;
|
||||
|
||||
static void add_coin(int64 nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
|
||||
static void add_coin(int64_t nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
|
||||
{
|
||||
static int i;
|
||||
CTransaction* tx = new CTransaction;
|
||||
@@ -56,7 +56,7 @@ static bool equal_sets(CoinSet a, CoinSet b)
|
||||
BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
{
|
||||
static CoinSet setCoinsRet, setCoinsRet2;
|
||||
static int64 nValueRet;
|
||||
static int64_t nValueRet;
|
||||
|
||||
// test multiple times to allow for differences in the shuffle order
|
||||
for (int i = 0; i < RUN_TESTS; i++)
|
||||
@@ -64,24 +64,24 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
empty_wallet();
|
||||
|
||||
// with an empty wallet we can't even pay one cent
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, (unsigned int)-1, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
|
||||
add_coin(1*CENT, 4); // add a new 1 cent coin
|
||||
|
||||
// with a new 1 cent coin, we still can't find a mature 1 cent
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, (unsigned int)-1, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
|
||||
// but we can find a new 1 cent
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
|
||||
|
||||
add_coin(2*CENT); // add a mature 2 cent coin
|
||||
|
||||
// we can't make 3 cents of mature coins
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * CENT, (unsigned int)-1, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
|
||||
// we can make 3 cents of new coins
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
|
||||
|
||||
add_coin(5*CENT); // add a mature 5 cent coin,
|
||||
@@ -91,33 +91,33 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
|
||||
|
||||
// we can't make 38 cents only if we disallow new coins:
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, (unsigned int)-1, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
// we can't even make 37 cents if we don't allow new coins even if they're from us
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 6, 6, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, (unsigned int)-1, 6, 6, vCoins, setCoinsRet, nValueRet));
|
||||
// but we can make 37 cents if we accept new coins from ourself
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(37 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(37 * CENT, (unsigned int)-1, 1, 6, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
|
||||
// and we can make 38 cents if we accept all new coins
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(38 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(38 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
|
||||
|
||||
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(34 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(34 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
|
||||
|
||||
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2);
|
||||
|
||||
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(nValueRet == 8 * CENT);
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3);
|
||||
|
||||
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1);
|
||||
|
||||
@@ -131,30 +131,30 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
|
||||
|
||||
// check that we have 71 and not 72
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(71 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(71 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
|
||||
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1);
|
||||
|
||||
add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
|
||||
|
||||
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3);
|
||||
|
||||
add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
|
||||
|
||||
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1); // because in the event of a tie, the biggest coin wins
|
||||
|
||||
// now try making 11 cents. we should get 5+6
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(11 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(11 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2);
|
||||
|
||||
@@ -163,11 +163,11 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
add_coin( 2*COIN);
|
||||
add_coin( 3*COIN);
|
||||
add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(95 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(95 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 TRI in 1 coin
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1);
|
||||
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(195 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(195 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 TRI in 1 coin
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1);
|
||||
|
||||
@@ -181,14 +181,14 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
|
||||
// try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents
|
||||
// we'll get sub-cent change whatever happens, so can expect 1.0 exactly
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
|
||||
|
||||
// but if we add a bigger coin, making it possible to avoid sub-cent change, things change:
|
||||
add_coin(1111*CENT);
|
||||
|
||||
// try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
|
||||
|
||||
// if we add more sub-cent coins:
|
||||
@@ -196,7 +196,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
add_coin(0.7*CENT);
|
||||
|
||||
// and try again to make 1.0 cents, we can still make 1.0 cents
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
|
||||
|
||||
// run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
|
||||
@@ -205,7 +205,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
for (int i = 0; i < 20; i++)
|
||||
add_coin(50000 * COIN);
|
||||
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10); // in ten coins
|
||||
|
||||
@@ -218,7 +218,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
add_coin(0.6 * CENT);
|
||||
add_coin(0.7 * CENT);
|
||||
add_coin(1111 * CENT);
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1);
|
||||
|
||||
@@ -228,7 +228,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
add_coin(0.6 * CENT);
|
||||
add_coin(0.8 * CENT);
|
||||
add_coin(1111 * CENT);
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2); // in two coins 0.4+0.6
|
||||
|
||||
@@ -239,12 +239,12 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
add_coin(1 * COIN);
|
||||
|
||||
// trying to make 1.0001 from these three coins
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1.0105 * COIN); // we should get all coins
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3);
|
||||
|
||||
// but if we try to make 0.999, we should take the bigger of the two small coins to avoid sub-cent change
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(0.999 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(0.999 * COIN, (unsigned int)-1, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1.01 * COIN); // we should get 1 + 0.01
|
||||
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2);
|
||||
|
||||
@@ -256,8 +256,8 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
|
||||
// picking 50 from 100 coins doesn't depend on the shuffle,
|
||||
// but does depend on randomness in the stochastic approximation code
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet , nValueRet));
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet2, nValueRet));
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, (unsigned int)-1, 1, 6, vCoins, setCoinsRet , nValueRet));
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, (unsigned int)-1, 1, 6, vCoins, setCoinsRet2, nValueRet));
|
||||
BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
|
||||
|
||||
int fails = 0;
|
||||
@@ -265,8 +265,8 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
{
|
||||
// selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
|
||||
// run the test RANDOM_REPEATS times and only complain if all of them fail
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet , nValueRet));
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet2, nValueRet));
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, (unsigned int)-1, 1, 6, vCoins, setCoinsRet , nValueRet));
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, (unsigned int)-1, 1, 6, vCoins, setCoinsRet2, nValueRet));
|
||||
if (equal_sets(setCoinsRet, setCoinsRet2))
|
||||
fails++;
|
||||
}
|
||||
@@ -282,8 +282,8 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
||||
{
|
||||
// selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
|
||||
// run the test RANDOM_REPEATS times and only complain if all of them fail
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet , nValueRet));
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet2, nValueRet));
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, (unsigned int)-1, 1, 6, vCoins, setCoinsRet , nValueRet));
|
||||
BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, (unsigned int)-1, 1, 6, vCoins, setCoinsRet2, nValueRet));
|
||||
if (equal_sets(setCoinsRet, setCoinsRet2))
|
||||
fails++;
|
||||
}
|
||||
|
||||
@@ -25,16 +25,18 @@ echo "Configuring Tor static library build from: $TOR_SRC_DIR"
|
||||
--disable-manpage \
|
||||
--disable-html-manual \
|
||||
--disable-unittests \
|
||||
--disable-tool-name-check
|
||||
--disable-tool-name-check \
|
||||
--with-libevent-dir="${LIBEVENT_DIR:-/mingw64}" \
|
||||
--with-openssl-dir="${OPENSSL_DIR:-/mingw64}" \
|
||||
--with-zlib-dir="${ZLIB_DIR:-/mingw64}"
|
||||
|
||||
echo "Building Tor"
|
||||
make -j"${NPROC:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)}"
|
||||
|
||||
echo
|
||||
echo "Build finished. Inspect these locations for static libraries:"
|
||||
echo " $TOR_SRC_DIR/src/core"
|
||||
echo " $TOR_SRC_DIR"
|
||||
echo " $TOR_SRC_DIR/src/lib"
|
||||
echo " $TOR_SRC_DIR/src/trunnel"
|
||||
echo
|
||||
echo "Suggested next step for Triangles:"
|
||||
echo ' make -f src/makefile.unix USE_TOR_EMBEDDED=1'
|
||||
echo ' make -f src/makefile.unix USE_TOR_EMBEDDED=1 TOR_SOURCE_ROOT=src/tor/tor-src'
|
||||
|
||||
+230
-46
@@ -18,6 +18,7 @@
|
||||
#endif
|
||||
|
||||
#include "onion_v3.h"
|
||||
#include "tor_embedded.h"
|
||||
#include "tor_crypto_compat.h"
|
||||
#include "../util.h"
|
||||
#include "../net.h"
|
||||
@@ -59,6 +60,53 @@ extern CWallet* pwalletMain;
|
||||
CTorV3Manager* CTorV3Manager::instance = nullptr;
|
||||
static TorV3Config torV3Config;
|
||||
|
||||
static boost::filesystem::path GetBackendHiddenServiceDir(const std::string& torDataDir)
|
||||
{
|
||||
return boost::filesystem::path(torDataDir) / "hidden_service";
|
||||
}
|
||||
|
||||
static bool ReadTrimmedFirstLine(const boost::filesystem::path& path, std::string& valueOut)
|
||||
{
|
||||
valueOut.clear();
|
||||
|
||||
std::ifstream file(path.string().c_str());
|
||||
if (!file.is_open() || !std::getline(file, valueOut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!valueOut.empty()) {
|
||||
const char ch = valueOut[valueOut.size() - 1];
|
||||
if (ch != '\n' && ch != '\r' && ch != ' ' && ch != '\t') {
|
||||
break;
|
||||
}
|
||||
valueOut.erase(valueOut.size() - 1);
|
||||
}
|
||||
|
||||
return !valueOut.empty();
|
||||
}
|
||||
|
||||
static std::string GetEffectiveTorProxy()
|
||||
{
|
||||
proxyType proxy;
|
||||
if (GetProxy(NET_TOR, proxy)) {
|
||||
return proxy.first.ToStringIPPort();
|
||||
}
|
||||
|
||||
if (mapArgs.count("-tor") && mapArgs["-tor"] != "0") {
|
||||
CService torProxy(mapArgs["-tor"], (unsigned short)GetArg("-torsocks", 19099));
|
||||
if (torProxy.IsValid()) {
|
||||
return torProxy.ToStringIPPort();
|
||||
}
|
||||
}
|
||||
|
||||
std::string explicitProxy = GetArg("-torproxy", "");
|
||||
if (!explicitProxy.empty()) {
|
||||
return explicitProxy;
|
||||
}
|
||||
|
||||
return strprintf("127.0.0.1:%d", GetArg("-torsocks", 19099));
|
||||
}
|
||||
|
||||
// Utility function for proper base32 encoding (RFC 4648) - Tor variant
|
||||
std::string EncodeBase32Proper(const unsigned char* data, size_t len)
|
||||
{
|
||||
@@ -503,40 +551,95 @@ bool CTorV3Service::LoadFromPrivateKey(const std::string& privKey)
|
||||
|
||||
bool CTorV3Service::StartOnionService()
|
||||
{
|
||||
if (onionAddress.empty()) {
|
||||
printf("ERROR: No onion address generated\n");
|
||||
if (!torV3Config.enableTor || !torV3Config.enableHiddenService) {
|
||||
printf("ERROR: Tor hidden service backend is disabled\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use data directory for Tor service files
|
||||
boost::filesystem::path serviceDir = GetDataDir() / "tor_data" / "triangles_v3";
|
||||
boost::filesystem::create_directories(serviceDir);
|
||||
return AttachToBackendService(torV3Config.torDataDirectory, port);
|
||||
}
|
||||
|
||||
// Create Tor configuration for hidden service
|
||||
std::string torrcContent = strprintf(
|
||||
"HiddenServiceDir %s\n"
|
||||
"HiddenServiceVersion 3\n"
|
||||
"HiddenServicePort %d 127.0.0.1:%d\n",
|
||||
serviceDir.string().c_str(), port, port
|
||||
);
|
||||
|
||||
// Write torrc file
|
||||
std::ofstream torrcFile((serviceDir / "torrc").string().c_str());
|
||||
if (torrcFile.is_open()) {
|
||||
torrcFile << torrcContent;
|
||||
torrcFile.close();
|
||||
bool CTorV3Service::AttachToBackendService(const std::string& torDataDir, int servicePort, int waitSeconds)
|
||||
{
|
||||
if (servicePort <= 0 || servicePort > 65535) {
|
||||
printf("ERROR: Invalid backend hidden service port: %d\n", servicePort);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write private key
|
||||
std::ofstream keyFile((serviceDir / "hs_ed25519_secret_key").string().c_str());
|
||||
if (keyFile.is_open()) {
|
||||
keyFile << "== ed25519v1-secret: type0 ==\n";
|
||||
keyFile << privateKey << "\n";
|
||||
keyFile.close();
|
||||
if (torDataDir.empty()) {
|
||||
printf("ERROR: Tor data directory is empty\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
port = servicePort;
|
||||
|
||||
const boost::filesystem::path serviceDir = GetBackendHiddenServiceDir(torDataDir);
|
||||
const boost::filesystem::path hostnamePath = serviceDir / "hostname";
|
||||
|
||||
std::string backendOnion;
|
||||
for (int waited = 0; waited <= waitSeconds; ++waited) {
|
||||
if (boost::filesystem::exists(hostnamePath) &&
|
||||
ReadTrimmedFirstLine(hostnamePath, backendOnion)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (waited == waitSeconds) {
|
||||
printf("ERROR: Timed out waiting for Tor hidden service hostname at %s\n",
|
||||
hostnamePath.string().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fShutdown) {
|
||||
printf("ERROR: Shutdown requested while waiting for Tor hidden service hostname\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
MilliSleep(1000);
|
||||
}
|
||||
|
||||
if (!ValidateOnionAddress(backendOnion)) {
|
||||
printf("ERROR: Tor backend produced invalid onion address: %s\n", backendOnion.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!onionAddress.empty() && onionAddress != backendOnion) {
|
||||
printf("WARNING: Replacing wallet-managed onion address %s with Tor backend address %s\n",
|
||||
onionAddress.c_str(), backendOnion.c_str());
|
||||
}
|
||||
|
||||
onionAddress = backendOnion;
|
||||
isActive = true;
|
||||
printf("Started V3 onion service on %s:%d\n", onionAddress.c_str(), port);
|
||||
|
||||
if (pwalletMain) {
|
||||
CWalletDB walletdb(pwalletMain->strWalletFile);
|
||||
walletdb.WriteSetting("tor_v3_onion_address", onionAddress);
|
||||
|
||||
// Back up the Tor-generated secret key to wallet.dat so the onion
|
||||
// identity survives deletion of the tor_data directory.
|
||||
boost::filesystem::path secretKeyPath = serviceDir / "hs_ed25519_secret_key";
|
||||
if (boost::filesystem::exists(secretKeyPath)) {
|
||||
std::ifstream keyFile(secretKeyPath.string().c_str(), std::ios::binary);
|
||||
if (keyFile.is_open()) {
|
||||
std::vector<unsigned char> keyData(
|
||||
(std::istreambuf_iterator<char>(keyFile)),
|
||||
std::istreambuf_iterator<char>());
|
||||
keyFile.close();
|
||||
|
||||
if (keyData.size() == 96) {
|
||||
walletdb.WriteSetting("tor_v3_hs_secret_key_backup", keyData);
|
||||
printf("Backed up Tor hidden service secret key to wallet (%d bytes)\n",
|
||||
(int)keyData.size());
|
||||
} else {
|
||||
printf("WARNING: hs_ed25519_secret_key has unexpected size %d (expected 96), not backing up\n",
|
||||
(int)keyData.size());
|
||||
}
|
||||
|
||||
OPENSSL_cleanse(keyData.data(), keyData.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("Attached V3 onion service to Tor backend at %s:%d\n", onionAddress.c_str(), port);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1142,20 +1245,10 @@ void CTorV3Manager::ShutdownTor()
|
||||
bool CTorV3Manager::CreateWalletHiddenService(int port)
|
||||
{
|
||||
CTorV3Service* service = new CTorV3Service();
|
||||
|
||||
// Try to load existing service first
|
||||
if (!service->LoadFromWallet()) {
|
||||
// Generate new service
|
||||
if (!service->GenerateV3Service(port)) {
|
||||
delete service;
|
||||
return false;
|
||||
}
|
||||
service->SaveToWallet();
|
||||
}
|
||||
|
||||
if (service->StartOnionService()) {
|
||||
|
||||
if (service->AttachToBackendService(torDataDir, port)) {
|
||||
services[port] = service;
|
||||
printf("Wallet hidden service created: %s\n", service->GetOnionAddress().c_str());
|
||||
printf("Wallet hidden service attached: %s\n", service->GetOnionAddress().c_str());
|
||||
|
||||
// If seeder mode is enabled, automatically register as seeder
|
||||
if (torV3Config.enableSeederMode) {
|
||||
@@ -1968,12 +2061,12 @@ void CTorV3Manager::UpdateDiscoveryStats(int connected, int attempted)
|
||||
bool LoadTorV3Config()
|
||||
{
|
||||
// Tor V3 identity is innate to Triangles — enabled by default
|
||||
torV3Config.enableTor = GetBoolArg("-tor", true);
|
||||
torV3Config.enableHiddenService = GetBoolArg("-torhiddenservice", true);
|
||||
torV3Config.enableTor = !GetBoolArg("-notor", false);
|
||||
torV3Config.enableHiddenService = torV3Config.enableTor && GetBoolArg("-torhiddenservice", true);
|
||||
torV3Config.enableSeederMode = GetBoolArg("-torseeder", false);
|
||||
torV3Config.hiddenServicePort = GetArg("-torhiddenserviceport", GetDefaultPort());
|
||||
torV3Config.hiddenServicePort = GetArg("-torhsport", GetListenPort());
|
||||
torV3Config.torDataDirectory = GetArg("-tordatadir", (GetDataDir() / "tor_data").string());
|
||||
torV3Config.socksProxy = GetArg("-torproxy", "127.0.0.1:9050");
|
||||
torV3Config.socksProxy = GetEffectiveTorProxy();
|
||||
torV3Config.maxConnections = GetArg("-tormaxconnections", 8);
|
||||
|
||||
printf("Loaded Tor V3 configuration: enabled=%s, hidden_service=%s, seeder=%s, proxy=%s\n",
|
||||
@@ -2093,15 +2186,106 @@ void CTorV3Manager::RequestSeederListFromPeer(const std::string& peerAddress)
|
||||
// Schedule periodic seeder re-announcements
|
||||
void CTorV3Manager::ScheduleSeederReannouncement()
|
||||
{
|
||||
// This would typically be handled by a timer or scheduler
|
||||
// For now, we'll just update the last announcement time
|
||||
if (pwalletMain) {
|
||||
CWalletDB walletdb(pwalletMain->strWalletFile);
|
||||
walletdb.WriteSetting("seeder_last_announcement", (int64_t)GetTime());
|
||||
printf("Scheduled seeder re-announcement\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Background thread: Tor health monitoring + seeder maintenance
|
||||
// ---------------------------------------------------------------------------
|
||||
void ThreadTorMaintenance(void* parg)
|
||||
{
|
||||
RenameThread("Triangles-tormaint");
|
||||
printf("Tor maintenance thread started\n");
|
||||
|
||||
int restartBackoffSec = 30;
|
||||
int64_t lastSeederMaint = GetTime();
|
||||
static const int SEEDER_INTERVAL = 1800; // 30 minutes
|
||||
|
||||
while (!fShutdown)
|
||||
{
|
||||
// Sleep in short intervals so the thread exits promptly on shutdown
|
||||
for (int i = 0; i < 60 && !fShutdown; i++)
|
||||
MilliSleep(500);
|
||||
if (fShutdown) break;
|
||||
|
||||
// --- Tor health check & auto-restart ---
|
||||
if (!CTorEmbedded::GetInstance()->IsRunning())
|
||||
{
|
||||
printf("WARNING: Tor process is no longer running, attempting restart...\n");
|
||||
|
||||
if (StartEmbeddedTor())
|
||||
{
|
||||
printf("Tor restarted successfully\n");
|
||||
restartBackoffSec = 30;
|
||||
|
||||
// Re-attach the hidden service identity
|
||||
TorV3Config& torConfig = GetTorV3Config();
|
||||
std::string torDataPath = CTorEmbedded::GetInstance()->GetDataDir();
|
||||
if (torDataPath.empty())
|
||||
torDataPath = torConfig.torDataDirectory;
|
||||
|
||||
torConfig.enableTor = true;
|
||||
torConfig.enableHiddenService = CTorEmbedded::GetInstance()->IsHiddenServiceEnabled();
|
||||
torConfig.hiddenServicePort = CTorEmbedded::GetInstance()->GetHiddenServicePort();
|
||||
torConfig.torDataDirectory = torDataPath;
|
||||
|
||||
if (torConfig.enableHiddenService && InitTorV3())
|
||||
{
|
||||
std::string onionAddr = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
|
||||
if (!onionAddr.empty())
|
||||
{
|
||||
AddLocal(CService(onionAddr, torConfig.hiddenServicePort), LOCAL_MANUAL);
|
||||
printf("Re-registered Tor V3 identity after restart: %s\n", onionAddr.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("WARNING: Tor restart failed, retrying in %d seconds\n", restartBackoffSec);
|
||||
for (int i = 0; i < restartBackoffSec * 2 && !fShutdown; i++)
|
||||
MilliSleep(500);
|
||||
if (restartBackoffSec < 300)
|
||||
restartBackoffSec *= 2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Seeder maintenance (every 30 minutes) ---
|
||||
TorV3Config& cfg = GetTorV3Config();
|
||||
if (cfg.enableSeederMode && (GetTime() - lastSeederMaint) >= SEEDER_INTERVAL)
|
||||
{
|
||||
CTorV3Manager* mgr = CTorV3Manager::GetInstance();
|
||||
std::string ownAddr = mgr->GetWalletOnionAddress();
|
||||
|
||||
if (!ownAddr.empty())
|
||||
{
|
||||
// Re-announce ourselves as a seeder to all peers
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
try {
|
||||
pnode->PushMessage("seeder", ownAddr, cfg.hiddenServicePort);
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
printf("Seeder re-announcement sent to %d peers\n", (int)vNodes.size());
|
||||
}
|
||||
|
||||
// Refresh our knowledge of other seeders
|
||||
mgr->RequestSeederListFromPeers();
|
||||
|
||||
mgr->ScheduleSeederReannouncement();
|
||||
lastSeederMaint = GetTime();
|
||||
}
|
||||
}
|
||||
|
||||
printf("Tor maintenance thread exited\n");
|
||||
}
|
||||
|
||||
// Global functions
|
||||
bool InitTorV3()
|
||||
{
|
||||
@@ -2111,4 +2295,4 @@ bool InitTorV3()
|
||||
void ShutdownTorV3()
|
||||
{
|
||||
CTorV3Manager::GetInstance()->ShutdownTor();
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -34,7 +34,10 @@ public:
|
||||
|
||||
// Start the onion service
|
||||
bool StartOnionService();
|
||||
|
||||
|
||||
// Attach to the hidden service managed by the running Tor backend
|
||||
bool AttachToBackendService(const std::string& torDataDir, int servicePort, int waitSeconds = 30);
|
||||
|
||||
// Stop the onion service
|
||||
bool StopService();
|
||||
|
||||
@@ -169,5 +172,6 @@ void ShutdownTorV3();
|
||||
TorV3Config& GetTorV3Config();
|
||||
bool SaveTorV3Config();
|
||||
bool LoadTorV3Config();
|
||||
void ThreadTorMaintenance(void* parg);
|
||||
|
||||
#endif // TRIANGLES_TOR_ONION_V3_H
|
||||
#endif // TRIANGLES_TOR_ONION_V3_H
|
||||
|
||||
+35
-23
@@ -52,6 +52,7 @@ CTorEmbedded::CTorEmbedded()
|
||||
: running(false)
|
||||
, socksPort(19099)
|
||||
, hiddenServicePort(24112)
|
||||
, hiddenServiceEnabled(true)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -109,20 +110,24 @@ static void TorThreadFunc(std::vector<std::string> argv_strings)
|
||||
CTorEmbedded::GetInstance()->SetRunning(false);
|
||||
}
|
||||
|
||||
bool CTorEmbedded::Start(int socks, int hsPort)
|
||||
bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
if (running.load()) return true;
|
||||
|
||||
socksPort = socks;
|
||||
hiddenServicePort = hsPort;
|
||||
hiddenServiceEnabled = enableHiddenService;
|
||||
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
||||
onionHostname.clear();
|
||||
|
||||
// Prepare Tor data directory under the wallet's data dir
|
||||
torDataDir = (::GetDataDir() / "tor_data").string();
|
||||
fs::create_directories(torDataDir);
|
||||
|
||||
// Hidden service directory
|
||||
std::string hsDir = (fs::path(torDataDir) / "hidden_service").string();
|
||||
fs::create_directories(hsDir);
|
||||
std::string hsDir;
|
||||
if (hiddenServiceEnabled) {
|
||||
hsDir = (fs::path(torDataDir) / "hidden_service").string();
|
||||
fs::create_directories(hsDir);
|
||||
}
|
||||
|
||||
// Build the argv for tor_run_main
|
||||
std::vector<std::string> argv;
|
||||
@@ -131,12 +136,14 @@ bool CTorEmbedded::Start(int socks, int hsPort)
|
||||
argv.push_back(std::to_string(socksPort));
|
||||
argv.push_back("--DataDirectory");
|
||||
argv.push_back(torDataDir);
|
||||
argv.push_back("--HiddenServiceDir");
|
||||
argv.push_back(hsDir);
|
||||
argv.push_back("--HiddenServiceVersion");
|
||||
argv.push_back("3");
|
||||
argv.push_back("--HiddenServicePort");
|
||||
argv.push_back(std::to_string(hiddenServicePort) + " 127.0.0.1:" + std::to_string(hiddenServicePort));
|
||||
if (hiddenServiceEnabled) {
|
||||
argv.push_back("--HiddenServiceDir");
|
||||
argv.push_back(hsDir);
|
||||
argv.push_back("--HiddenServiceVersion");
|
||||
argv.push_back("3");
|
||||
argv.push_back("--HiddenServicePort");
|
||||
argv.push_back(std::to_string(hiddenServicePort) + " 127.0.0.1:" + std::to_string(hiddenServicePort));
|
||||
}
|
||||
argv.push_back("--AvoidDiskWrites");
|
||||
argv.push_back("1");
|
||||
argv.push_back("--Log");
|
||||
@@ -175,13 +182,15 @@ bool CTorEmbedded::Start(int socks, int hsPort)
|
||||
printf("Embedded Tor SOCKS proxy ready on port %d (took %ds)\n", socksPort, i + 1);
|
||||
|
||||
// Read .onion hostname if available
|
||||
fs::path hostnameFile = fs::path(hsDir) / "hostname";
|
||||
if (fs::exists(hostnameFile)) {
|
||||
std::ifstream f(hostnameFile.string().c_str());
|
||||
if (f.is_open())
|
||||
std::getline(f, onionHostname);
|
||||
if (!onionHostname.empty())
|
||||
printf("Tor hidden service: %s\n", onionHostname.c_str());
|
||||
if (hiddenServiceEnabled) {
|
||||
fs::path hostnameFile = fs::path(hsDir) / "hostname";
|
||||
if (fs::exists(hostnameFile)) {
|
||||
std::ifstream f(hostnameFile.string().c_str());
|
||||
if (f.is_open())
|
||||
std::getline(f, onionHostname);
|
||||
if (!onionHostname.empty())
|
||||
printf("Tor hidden service: %s\n", onionHostname.c_str());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -219,14 +228,16 @@ void CTorEmbedded::Stop()
|
||||
|
||||
#include "tor_process.h"
|
||||
|
||||
bool CTorEmbedded::Start(int socks, int hsPort)
|
||||
bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
printf("Embedded Tor not compiled in. Using external Tor process.\n");
|
||||
// Delegate to the external process manager
|
||||
socksPort = socks;
|
||||
hiddenServicePort = hsPort;
|
||||
hiddenServiceEnabled = enableHiddenService;
|
||||
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
||||
onionHostname.clear();
|
||||
torDataDir = (::GetDataDir() / "tor_data").string();
|
||||
running.store(StartTorProcess(torDataDir, socksPort, hiddenServicePort));
|
||||
running.store(StartTorProcess(torDataDir, socksPort, hiddenServicePort, hiddenServiceEnabled));
|
||||
return running.load();
|
||||
}
|
||||
|
||||
@@ -249,10 +260,11 @@ bool StartEmbeddedTor()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool enableHiddenService = GetBoolArg("-torhiddenservice", true);
|
||||
int socksPort = GetArg("-torsocks", 19099);
|
||||
int hsPort = GetArg("-torhsport", GetListenPort());
|
||||
int hsPort = enableHiddenService ? GetArg("-torhsport", GetListenPort()) : 0;
|
||||
|
||||
return CTorEmbedded::GetInstance()->Start(socksPort, hsPort);
|
||||
return CTorEmbedded::GetInstance()->Start(socksPort, hsPort, enableHiddenService);
|
||||
}
|
||||
|
||||
void StopEmbeddedTor()
|
||||
|
||||
@@ -16,6 +16,7 @@ private:
|
||||
std::atomic<bool> running;
|
||||
int socksPort;
|
||||
int hiddenServicePort;
|
||||
bool hiddenServiceEnabled;
|
||||
std::string torDataDir;
|
||||
std::string onionHostname;
|
||||
|
||||
@@ -26,7 +27,7 @@ public:
|
||||
~CTorEmbedded();
|
||||
|
||||
// Start Tor in a background thread (blocks that thread until shutdown)
|
||||
bool Start(int socksPort = 19099, int hsPort = 24112);
|
||||
bool Start(int socksPort = 19099, int hsPort = 24112, bool enableHiddenService = true);
|
||||
|
||||
// Request Tor to shut down
|
||||
void Stop();
|
||||
@@ -45,6 +46,7 @@ public:
|
||||
|
||||
// Get the hidden service port
|
||||
int GetHiddenServicePort() const { return hiddenServicePort; }
|
||||
bool IsHiddenServiceEnabled() const { return hiddenServiceEnabled; }
|
||||
};
|
||||
|
||||
// Global init/shutdown hooks (called from init.cpp)
|
||||
|
||||
+28
-17
@@ -48,6 +48,7 @@ CTorProcess* CTorProcess::GetInstance()
|
||||
CTorProcess::CTorProcess()
|
||||
: socksPort(19099)
|
||||
, hiddenServicePort(24112)
|
||||
, hiddenServiceEnabled(true)
|
||||
, running(false)
|
||||
#ifdef WIN32
|
||||
, hProcess(NULL)
|
||||
@@ -198,11 +199,13 @@ bool CTorProcess::WriteTorrc()
|
||||
fs::create_directories(torStateDir);
|
||||
torrc << "DataDirectory " << torStateDir.string() << "\n";
|
||||
|
||||
// V3 hidden service so this node is reachable via .onion
|
||||
torrc << "HiddenServiceDir " << hsDir.string() << "\n";
|
||||
torrc << "HiddenServiceVersion 3\n";
|
||||
torrc << "HiddenServicePort " << hiddenServicePort
|
||||
<< " 127.0.0.1:" << hiddenServicePort << "\n";
|
||||
if (hiddenServiceEnabled) {
|
||||
// V3 hidden service so this node is reachable via .onion
|
||||
torrc << "HiddenServiceDir " << hsDir.string() << "\n";
|
||||
torrc << "HiddenServiceVersion 3\n";
|
||||
torrc << "HiddenServicePort " << hiddenServicePort
|
||||
<< " 127.0.0.1:" << hiddenServicePort << "\n";
|
||||
}
|
||||
|
||||
// Reduce bandwidth/resource usage for wallet use
|
||||
torrc << "ClientOnly 1\n";
|
||||
@@ -213,15 +216,21 @@ bool CTorProcess::WriteTorrc()
|
||||
|
||||
torrc.close();
|
||||
|
||||
printf("Wrote torrc to %s (SOCKS %d, HS port %d)\n",
|
||||
torrcPath.c_str(), socksPort, hiddenServicePort);
|
||||
if (hiddenServiceEnabled) {
|
||||
printf("Wrote torrc to %s (SOCKS %d, HS port %d)\n",
|
||||
torrcPath.c_str(), socksPort, hiddenServicePort);
|
||||
} else {
|
||||
printf("Wrote torrc to %s (SOCKS %d, hidden service disabled)\n",
|
||||
torrcPath.c_str(), socksPort);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort)
|
||||
bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
socksPort = socks;
|
||||
hiddenServicePort = hsPort;
|
||||
hiddenServiceEnabled = enableHiddenService;
|
||||
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
||||
torDataDir = dataDir;
|
||||
|
||||
// Check if something is already listening on our SOCKS port
|
||||
@@ -314,12 +323,14 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort)
|
||||
printf("Tor SOCKS proxy ready on port %d (took %ds)\n", socksPort, i + 1);
|
||||
|
||||
// Read and display the hidden service hostname if available
|
||||
fs::path hsHostname = fs::path(torDataDir) / "hidden_service" / "hostname";
|
||||
if (fs::exists(hsHostname)) {
|
||||
std::ifstream f(hsHostname.string().c_str());
|
||||
std::string hostname;
|
||||
if (f.is_open() && std::getline(f, hostname)) {
|
||||
printf("Tor hidden service: %s\n", hostname.c_str());
|
||||
if (hiddenServiceEnabled) {
|
||||
fs::path hsHostname = fs::path(torDataDir) / "hidden_service" / "hostname";
|
||||
if (fs::exists(hsHostname)) {
|
||||
std::ifstream f(hsHostname.string().c_str());
|
||||
std::string hostname;
|
||||
if (f.is_open() && std::getline(f, hostname)) {
|
||||
printf("Tor hidden service: %s\n", hostname.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -402,9 +413,9 @@ std::string CTorProcess::GetSocksProxy() const
|
||||
}
|
||||
|
||||
// Global convenience functions
|
||||
bool StartTorProcess(const std::string& dataDir, int socksPort, int hsPort)
|
||||
bool StartTorProcess(const std::string& dataDir, int socksPort, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
return CTorProcess::GetInstance()->Start(dataDir, socksPort, hsPort);
|
||||
return CTorProcess::GetInstance()->Start(dataDir, socksPort, hsPort, enableHiddenService);
|
||||
}
|
||||
|
||||
void StopTorProcess()
|
||||
|
||||
@@ -21,6 +21,7 @@ private:
|
||||
std::string torrcPath;
|
||||
int socksPort;
|
||||
int hiddenServicePort;
|
||||
bool hiddenServiceEnabled;
|
||||
bool running;
|
||||
|
||||
#ifdef WIN32
|
||||
@@ -45,7 +46,7 @@ public:
|
||||
|
||||
// Start the Tor process
|
||||
// Returns true if Tor was started or is already running
|
||||
bool Start(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112);
|
||||
bool Start(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112, bool enableHiddenService = true);
|
||||
|
||||
// Stop the Tor process
|
||||
void Stop();
|
||||
@@ -64,7 +65,7 @@ public:
|
||||
};
|
||||
|
||||
// Global convenience functions
|
||||
bool StartTorProcess(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112);
|
||||
bool StartTorProcess(const std::string& dataDir, int socksPort = 19099, int hsPort = 24112, bool enableHiddenService = true);
|
||||
void StopTorProcess();
|
||||
|
||||
#endif // TRIANGLES_TOR_PROCESS_H
|
||||
|
||||
+31
-9
@@ -24,6 +24,7 @@
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
|
||||
@@ -429,7 +430,7 @@ static string HTTPReply(int nStatus, const string& strMsg, bool keepalive)
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Date: %s\r\n"
|
||||
"Connection: %s\r\n"
|
||||
"Content-Length: %"PRIszu"\r\n"
|
||||
"Content-Length: %" PRIszu "\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Server: Triangles-json-rpc/%s\r\n"
|
||||
"\r\n"
|
||||
@@ -739,7 +740,7 @@ void ThreadRPCServer(void* parg)
|
||||
|
||||
// Forward declaration required for RPCListen
|
||||
template <typename Protocol, typename SocketAcceptorService>
|
||||
static void RPCAcceptHandler(std::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
|
||||
static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
|
||||
ssl::context& context,
|
||||
bool fUseSSL,
|
||||
AcceptedConnection* conn,
|
||||
@@ -749,7 +750,7 @@ static void RPCAcceptHandler(std::shared_ptr< basic_socket_acceptor<Protocol, So
|
||||
* Sets up I/O resources to accept and handle a new connection.
|
||||
*/
|
||||
template <typename Protocol, typename SocketAcceptorService>
|
||||
static void RPCListen(std::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
|
||||
static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
|
||||
ssl::context& context,
|
||||
const bool fUseSSL)
|
||||
{
|
||||
@@ -771,7 +772,7 @@ static void RPCListen(std::shared_ptr< basic_socket_acceptor<Protocol, SocketAcc
|
||||
* Accept and handle incoming connection.
|
||||
*/
|
||||
template <typename Protocol, typename SocketAcceptorService>
|
||||
static void RPCAcceptHandler(std::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
|
||||
static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor,
|
||||
ssl::context& context,
|
||||
const bool fUseSSL,
|
||||
AcceptedConnection* conn,
|
||||
@@ -786,10 +787,16 @@ static void RPCAcceptHandler(std::shared_ptr< basic_socket_acceptor<Protocol, So
|
||||
|
||||
AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn);
|
||||
|
||||
// TODO: Actually handle errors
|
||||
if (error)
|
||||
{
|
||||
if (error != asio::error::operation_aborted)
|
||||
printf("RPC accept error from %s: %s (%d)\n",
|
||||
tcp_conn ? tcp_conn->peer.address().to_string().c_str() : "unknown peer",
|
||||
error.message().c_str(),
|
||||
error.value());
|
||||
delete conn;
|
||||
vnThreadsRunning[THREAD_RPCLISTENER]--;
|
||||
return;
|
||||
}
|
||||
|
||||
// Restrict callers by IP. It is important to
|
||||
@@ -874,7 +881,7 @@ void ThreadRPCServer2(void* parg)
|
||||
asio::ip::address bindAddress = loopback ? asio::ip::address_v6::loopback() : asio::ip::address_v6::any();
|
||||
ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", GetDefaultRPCPort()));
|
||||
boost::system::error_code v6_only_error;
|
||||
std::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(io_service));
|
||||
boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(io_service));
|
||||
|
||||
boost::signals2::signal<void ()> StopRequests;
|
||||
|
||||
@@ -939,9 +946,24 @@ void ThreadRPCServer2(void* parg)
|
||||
|
||||
vnThreadsRunning[THREAD_RPCLISTENER]--;
|
||||
while (!fShutdown)
|
||||
io_service.run_one();
|
||||
{
|
||||
// Use poll_one + sleep instead of blocking run_one so the thread
|
||||
// remains responsive to fShutdown and can exit promptly.
|
||||
if (!io_service.poll_one())
|
||||
{
|
||||
io_service.restart();
|
||||
MilliSleep(50);
|
||||
}
|
||||
}
|
||||
vnThreadsRunning[THREAD_RPCLISTENER]++;
|
||||
StopRequests();
|
||||
|
||||
// Safely shut down: close acceptors, then drain any remaining handlers
|
||||
try {
|
||||
StopRequests();
|
||||
} catch (...) {
|
||||
// Absorb bad_weak_ptr or other exceptions from stale tracked slots
|
||||
}
|
||||
io_service.poll(); // process cancellation callbacks so shared_ptrs are released
|
||||
}
|
||||
|
||||
class JSONRequest
|
||||
@@ -1063,7 +1085,7 @@ static void HandleSSEConnection(AcceptedConnection* conn)
|
||||
// Send events
|
||||
for (size_t i = 0; i < vEvents.size(); i++)
|
||||
{
|
||||
std::string strSSE = strprintf("id: %"PRIu64"\ndata: %s\n\n", nLastId - vEvents.size() + i + 1, vEvents[i].c_str());
|
||||
std::string strSSE = strprintf("id: %" PRIu64 "\ndata: %s\n\n", nLastId - vEvents.size() + i + 1, vEvents[i].c_str());
|
||||
try {
|
||||
conn->stream() << strSSE << std::flush;
|
||||
} catch (...) {
|
||||
|
||||
+11
-1
@@ -34,6 +34,12 @@ static leveldb::Options GetOptions() {
|
||||
int nCacheSizeMB = GetArg("-dbcache", 2048);
|
||||
options.block_cache = leveldb::NewLRUCache(nCacheSizeMB * 1048576);
|
||||
options.filter_policy = leveldb::NewBloomFilterPolicy(10);
|
||||
// Larger write buffer (64MB vs default 4MB) reduces the frequency of
|
||||
// memtable flushes and compactions, which is a big win during IBD
|
||||
// when millions of tx index entries are written sequentially.
|
||||
options.write_buffer_size = 64 * 1048576;
|
||||
// Allow more open files for better read performance on large chains
|
||||
options.max_open_files = 1000;
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -138,7 +144,11 @@ void CTxDB::Close()
|
||||
|
||||
bool CTxDB::TxnBegin()
|
||||
{
|
||||
assert(!activeBatch);
|
||||
// Allow calling TxnBegin when a batch is already active (no-op).
|
||||
// This lets callers like SetBestChain share a batch that was opened
|
||||
// earlier by AddToBlockIndex, merging two commits into one.
|
||||
if (activeBatch)
|
||||
return true;
|
||||
activeBatch = new leveldb::WriteBatch();
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -379,7 +379,7 @@ string FormatMoney(int64_t n, bool fPlus)
|
||||
int64_t n_abs = (n > 0 ? n : -n);
|
||||
int64_t quotient = n_abs/COIN;
|
||||
int64_t remainder = n_abs%COIN;
|
||||
string str = strprintf("%"PRId64".%08"PRId64, quotient, remainder);
|
||||
string str = strprintf("%"PRId64".%06"PRId64, quotient, remainder);
|
||||
|
||||
// Right-trim excess zeros before the decimal point:
|
||||
int nTrim = 0;
|
||||
|
||||
+1
-1
@@ -237,7 +237,7 @@ void runCommand(std::string strCommand);
|
||||
|
||||
inline std::string i64tostr(int64_t n)
|
||||
{
|
||||
return strprintf("%"PRId64, n);
|
||||
return strprintf("%" PRId64, n);
|
||||
}
|
||||
|
||||
inline std::string itostr(int n)
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ static const int MEMPOOL_GD_VERSION = 60002;
|
||||
|
||||
#define DISPLAY_VERSION_MAJOR 5
|
||||
#define DISPLAY_VERSION_MINOR 3
|
||||
#define DISPLAY_VERSION_REVISION 1
|
||||
#define DISPLAY_VERSION_REVISION 8
|
||||
#define DISPLAY_VERSION_BUILD 0
|
||||
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user